user24343
user24343

Reputation: 912

MyClass.attr VS type(self).attr VS separate @classmethod

Say I have a (normal bound) method in my class that has to access class variables (like a counter in __init__). If I want to modify class varaibles, I see three possibilities:

  1. Use type(self) or self.__class__ (after reading this, I chose type over __class__, as I'm using Python 3 so "old-style" classes don't apply)
  2. Use the name of the class. This works well unless the name is rebound.
  3. write a @classmethod specifically for changing class variables. This is the most complicated, but also the most clear (IMO) approach.

The three methods would be written like this:

class MyClass:
    counter = 0

    def f1(self):
        type(self).counter += 1

    def f2(self):
        MyClass.counter += 1

    def f3(self):
        self._count()
    @classmethod
    def _count(cls):
        cls.counter += 1

Is there a clear "best" option and which one is it or are they all more or less equivalent and it's just "what I like most"? Does inheritance change anything?

Upvotes: 2

Views: 250

Answers (1)

Davis Herring
Davis Herring

Reputation: 39758

If you derive classes from the one defining your variable (and presumably the method assigning to it), using type(self) stores (the new value of) the attribute on the object’s actual class. That can be quite useful, but in your example is surely wrong.

Using a class method is just the same, except that the subclass could override the method: this might make the complex case work, but does nothing to fix the simple inheritance case.

Using the name of the class is the simple, idiomatic answer and avoids any confusion from subclasses. In the absence of concern over rebinding that name, this should be the default even without any inheritance. If that is a concern, you can stash the class object in a couple of different ways:

class A:
  counter=0

  # use the magic behind super():
  def _count1(): __class__.counter+=1

  # inject the class object:
  @staticmethod
  def _count2(cls): cls.counter+=1
A._count2.__defaults__=A,

# inject via a closure:
def capture(cls):
  def _count(): cls.counter+=1
  return _count
A._count3=staticmethod(capture(A))

The __class__ trick can of course be used directly, without a wrapper method, but it’s a bit ugly; internally, it’s very similar to the last approach with capture. One thing that doesn’t work is trying to make a function that refers to the class’s dictionary, since those are protected for optimization reasons.

Upvotes: 2

Related Questions