1

Let B inherit from A. Suppose that some of B's behavior depends on the class attribute cls_x and we want to set up this dependency during construction of B objects. Since it is not a simple operation, we want to wrap it in a class method, which the constructor will call. Example:

class B(A):
  cls_x = 'B'

  @classmethod
  def cm(cls):
    return cls.cls_x

  def __init__(self):
    self.attr = B.cm()

Problem: cm as well as __init__ will always be doing the same things and their behavior must stay the same in each derived class. Thus, we would like to put them both in the base class and not define it in any of the derived classes. The only difference will be the caller of cm - either A or B (or any of B1, B2, each inheriting from A), whatever is being constructed. So what we'd like to have is something like this:

class A:
  cls_x = 'A'

  @classmethod
  def cm(cls):
    return cls.cls_x

  def __init__(self):
    self.attr = ClassOfWhateverIsInstantiated.cm()  #how to do this?

class B(A):
  cls_x = 'B'

I feel like it's either something very simple I'm missing about Python's inheritance mechanics or the whole issue should be handled entirely differently.

This is different than this question as I do not want to override the class method, but move its implementation to the base class entirely.

Przemek D
  • 654
  • 6
  • 26

2 Answers2

1

For ClassOfWhateverIsInstantiated you can just use self:

class A:
  cls_x = 'A'

  @classmethod
  def cm(cls):
    return cls.cls_x

  def __init__(self):
    self.attr = self.cm()  # 'self' refers to B, if called from B

class B(A):
  cls_x = 'B'

a = A()
print(a.cls_x) # = 'A'
print(A.cls_x) # = 'A'

b = B()
print(b.cls_x) # = 'B'
print(B.cls_x) # = 'B'

To understand this, just remember that class B is inheriting the methods of class A. So when __init__() is called during B's instantiation, it's called in the context of class B, to which self refers.

Richard Inglis
  • 5,888
  • 2
  • 33
  • 37
  • This works but I'd like to understand why. Does it mean that `self` binds to the object as well as its class, depending on what is being requested (classmethod or normal)? – Przemek D May 29 '18 at 10:33
  • B inherit's A's methods. Just imagine that the `__init__` method was defined in B. – Richard Inglis May 29 '18 at 10:35
  • I understand that. I was confused by the fact that I can use classmethods both as `B.cm()` and `b.cm()`, and `self` will be automatically resolved for me, which @Aran-Fey explained in their answer. – Przemek D May 29 '18 at 10:43
  • Oh I see. Yeah, there's no difference between `b.cm()` and `B.cm()` – Richard Inglis May 29 '18 at 11:14
1

Look at it this way: Your question is essentially "How do I get the class of an instance?". The answer to that question is to use the type function:

ClassOfWhateverIsInstantiated = type(self)

But you don't even need to do that, because classmethods can be called directly through an instance:

def __init__(self):
    self.attr = self.cm()  # just use `self`

This works because classmethods automatically look up the class of the instance for you. From the docs:

[A classmethod] can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • I should have seen this link way earlier. To a programmer with a C/C++ background, the following is very enlightening: "*Class methods are different than C++ or Java static methods. If you want those, see [staticmethod()](https://docs.python.org/3/library/functions.html#staticmethod) in this section.*" – Przemek D May 29 '18 at 10:47