1

Suppose we have a base class, A, that contains some class variables. This class also has a class method foo that does something with those variables. Since this behavior shouldn't be hard-coded (e.g. we don't want to have to modify foo when adding new class variables), foo reads cls.__dict__ instead of directly referencing the variables.
Now we introduce a derived class: B extends A with some more class variables, as well as inherits foo. Code example:

class A:
  x = 0
  y = 1
  @classmethod
  def foo(cls):
    print([name for name, prop in cls.__dict__.items() if type(prop) is int])

class B(A):
  z = 3

print(B.x)  # prints "0"
A.foo()     # prints "['x', 'y']"
B.foo()     # prints "['z']" -- why not "['x', 'y', 'z']"?

Therefore, my question is: why B.__dict__ does not contain the variables inherited from A, and if not there, then where are they?

This is not a duplicate of Accessing class attributes from parents in instance methods, because I don't just want to query specific variables that happen to be in the base class - I want to list them without knowing their names. The answers related to MRO given in this question might happen to also apply here, but the original problem is in my view different.

Przemek D
  • 654
  • 6
  • 26
  • 1
    They are in `A.__dict__` of course. If you *really* want to do this, then you are going to have to iterate through the `mro` explicitly – juanpa.arrivillaga Oct 29 '18 at 20:16
  • 2
    Possible duplicate of [Accessing class attributes from parents in instance methods](https://stackoverflow.com/questions/52532937/accessing-class-attributes-from-parents-in-instance-methods) – juanpa.arrivillaga Oct 29 '18 at 20:20
  • In that proposed duplicate, it is dealing with an instance method, but it should be easy to modify dealing with the classmethod, just careful with name clashes, so something like `[name for kls in cls.mro() for name, prop in vars(kls).items() if isinstance(prop, int)]` – juanpa.arrivillaga Oct 29 '18 at 20:21
  • This solution with MRO works, thank you. I will post my alternative approach using a metaclass, which I ended up using because it gives me some additional benefits (unrelated to the original question). – Przemek D Oct 30 '18 at 14:27

1 Answers1

1

I solved this with a metaclass, using it to redefine the way derived classes are created. In this approach, all class variables that we are interested in get copied from the base classes to the derived one:

class M(type):
  def __new__(cls, name, bases, dct):
    for base in bases:
      for name, prop in base.__dict__.items():
        if type(prop) is int:
          dct[name] = prop
    return super(M, cls).__new__(cls, name, bases, dct)

class A(metaclass=M):
  x = 0
  y = 1
  @classmethod
  def foo(cls):
    print([name for name, prop in cls.__dict__.items() if type(prop) is int])

class B(A):
  z = 3

A.foo()  # prints "['y', 'x']"
B.foo()  # prints "['y', 'x', 'z']"

Besides, the metaclass can be defined in such a way that foo will not even have to query the __dict__ - variables of interest could be put into a list instead of modifying __dict__, should this for some reason had to be avoided.

Przemek D
  • 654
  • 6
  • 26