Instance variables vs. class variables in Python

2020-01-22 12:04发布

问题:

I have Python classes, of which I need only one instance at runtime, so it would be sufficient to have the attributes only once per class and not per instance. If there would be more than one instance (which won't happen), all instance should have the same configuration. I wonder which of the following options would be better or more "idiomatic" Python.

Class variables:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Instance variables:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass

回答1:

If you have only one instance anyway, it's best to make all variables per-instance, simply because they will be accessed (a little bit) faster (one less level of "lookup" due to the "inheritance" from class to instance), and there are no downsides to weigh against this small advantage.



回答2:

further echoing mike's and alex's advice and adding my own color...

using instance attributes are the typical, more idiomatic Python. class attributes are not oft-used -- at least not in production code in my last 13+ consecutive years of Python. the same is true for static and class methods... just not very common unless there's a specific use case or an aberrant programmer wanting to show off they know some obscure corner of Python programming.

alex mentions in his reply that access will be (a little bit) faster due to one less level of lookup... let me further clarify for those who don't know about how this works yet, it is very similar to variable access -- the search happens in this order:

  1. locals
  2. nonlocals
  3. globals
  4. built-ins

for attribute access, the order is:

  1. instance
  2. class
  3. base classes as determined by the MRO (method resolution order)

in your example above, let's say you're looking up the path attribute. when it encounters a reference like "self.path", Python will look at the instance attributes first for a match; when that fails, it checks the class from which the object was instantiated from. finally, it will search the base classes. as alex has stated, if your attribute is found in the instance, it won't defer to the class, hence your little bit of time savings.

however, if you insist on class attributes, you will have to give up this tiny bit of performance, or, your other alternative is to refer to the object via the class instead of the instance, e.g., MyController.path instead of self.path. that's a direct lookup which will get around the deferred lookup, but as alex mentions below, that's a global variable, so you lose that bit that you thought you were going to save (unless you create a local reference to the [global] class name).



回答3:

When in doubt, you probably want an instance attribute.

Class attributes are best reserved for special cases where they make sense. The only very-common use case is methods. It isn't uncommon to use class attributes for read-only constants that instances need to know (though the only benefit to this is if you also want access from outside the class), but you should certainly be cautious about storing any state in them, which is seldom what you want. Even if you will only have one instance, you should write the class like you would any other, which usually means using instance attributes.



回答4:

Same question at Performance of accessing class variables in Python - the code here adapted from @Edward Loper

Local Variables are the fastest to access, pretty much tied with Module Variables, followed by Class Variables, followed by Instance Variables.

There are 4 scopes you can access variables from:

  1. Instance Variables (self.varname)
  2. Class Variables (Classname.varname)
  3. Module Variables (VARNAME)
  4. Local Variables (varname)

The test:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

The result:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199