2

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?

user24343
  • 892
  • 10
  • 19
  • This is a pretty straightforward use-case for a classmethod, imo. I don't really see it as appreciably more complicated than the alternatives. Using functions to split your code up into smaller, composable parts makes your code *less complicated* overall, the way I see it. – juanpa.arrivillaga Feb 10 '19 at 18:42
  • @juanpa.arrivillaga well, it makes a method do what one line of code does otherwise; it obviously separates well, but adds a layer of complexity. If I had more stuff to change, I'd certainly use a classmethod. ---- I'd be happy to see an answer based on your comment. – user24343 Feb 10 '19 at 18:58
  • Are you inheriting from this class anywhere? – Davis Herring Feb 10 '19 at 23:38
  • @DavisHerring in this specific case not, but I might in the future, so I'd love an answer which addresses both and explains why a different approach might be applicable – user24343 Feb 11 '19 at 16:25

1 Answers1

2

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.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76