How does determining subclass through hasattr trig

2019-09-05 13:10发布

For example, I am using multi-table inheritance for a class Node with sub-classes ConceptNode and DerivedNode. To determine the type of Node I am dealing with and distribute a function call down to the appropriate subclass, I often have to call hasattr like this:

test_node = Node.objects.all()[0]

if hasattr( test_node, "conceptnode"):
    test_node.conceptnode.myFunction()
elif hasattr( test_node, "derivednode"):
    test_node.derivednode.myFunction()
else:
   raise Exception("Not a valid type.")

I've noticed that this results in multiple db queries, which add up to really slow down some functions I've written.

I've tried another approach using try...catch which does not decrease the number of queries.

test_node = Node.objects.all()[0]

try:
    test_node.conceptnode.myFunction()
except ObjectDoesNotExist:
    test_node.derivednode.myFunction()

My main question is: how does django determine what queries to execute here? I don't see how hasattr is getting translated into a db query.

Also, if anyone can suggest a more efficient way to handle this (esp. from a query count perspective), that would be great too!

EDIT: To dump the sqlite queries performed I did the following:

from django.db import connection
from django import db
db.reset_queries()
hasattr(nds[0],'conceptnode')
hasattr(nds[0],'derivednode')
connection.queries

And I got the following results:

{'sql': u'SELECT "nodes_node"."id", "nodes_node"."name", "nodes_node"."description", "nodes_node"."node_tree_id", "nodes_node"."unique_name", "nodes_node"."last_updated_timestamp", "nodes_node"."order", "nodes_node"."data_json", "nodes_node"."data_json_synchronized" FROM "nodes_node" LIMIT 1',  'time': '0.001'}
{'sql': u'SELECT "nodes_node"."id", "nodes_node"."name", "nodes_node"."description", "nodes_node"."node_tree_id", "nodes_node"."unique_name", "nodes_node"."last_updated_timestamp", "nodes_node"."order", "nodes_node"."data_json", "nodes_node"."data_json_synchronized", "nodes_conceptnode"."node_ptr_id", "nodes_conceptnode"."node_parent_id" FROM "nodes_conceptnode" INNER JOIN "nodes_node" ON ("nodes_conceptnode"."node_ptr_id" = "nodes_node"."id") WHERE "nodes_conceptnode"."node_ptr_id" = 1 ',  'time': '0.000'}
{'sql': u'SELECT "nodes_node"."id", "nodes_node"."name", "nodes_node"."description", "nodes_node"."node_tree_id", "nodes_node"."unique_name", "nodes_node"."last_updated_timestamp", "nodes_node"."order", "nodes_node"."data_json", "nodes_node"."data_json_synchronized" FROM "nodes_node" LIMIT 1',  'time': '0.001'}
{'sql': u'SELECT "nodes_node"."id", "nodes_node"."name", "nodes_node"."description", "nodes_node"."node_tree_id", "nodes_node"."unique_name", "nodes_node"."last_updated_timestamp", "nodes_node"."order", "nodes_node"."data_json", "nodes_node"."data_json_synchronized", "nodes_derivednode"."node_ptr_id", "nodes_derivednode"."node_source_id", "nodes_derivednode"."node_target_id" FROM "nodes_derivednode" INNER JOIN "nodes_node" ON ("nodes_derivednode"."node_ptr_id" = "nodes_node"."id") WHERE "nodes_derivednode"."node_ptr_id" = 1 ',  'time': '0.000'}

The first and third of these are fetching the original node object.

1条回答
劫难
2楼-- · 2019-09-05 13:39

You have two instances of the simple node lookup (the LIMIT 1 query) because you are accessing a queryset as a list multiple times.

nodes = Node.objects.all()
nodes[0]
nodes[0]

triggers two queries, whereas:

node = Node.objects.all()[0]
node
node

triggers one. This can seem a bit strange at first, but the key is to remember that Node.objects.all() is not evaluated (no queries are made) until you access it.

As for why a single object lookup runs two queries, you are using multi-table inheritance. If you just have this in your model:

class Node(models.Model):
    pass

class ConceptNode(Node):
    pass

Django will create two tables, with ConceptNode rows having a parent Node.

What you're probably looking for is an abstract base class, which will allow you to share methods and properties between multiple classes while just using a single table for each. Just add abstract = True to the parent class meta:

class Node(models.Model):
    class Meta:
        abstract = True

Finally, note that both of these queries should take almost no time, so I wouldn't worry about it too much.

查看更多
登录 后发表回答