0

I have defined a class property by defining an attribute with @property decorator in a metaclass, as proposed in this SO answer.

As I have only access at the class definition (that will inherits from the metaclass) after it has been defined, I am making it inheriting afterwards, thanks to the trick given in above mentioned SO answer.

I would like the class property defined to be available in both the inheriting class and its instances. But I don't succeed to have it in its instances.

So, let's review my current code:

class TopLevel(type):
    """ TopLevel metaclass defining class properties. """
    @property
    def depth(cls) -> int:
        return cls._depth

class OtherClass:
    """ Class that will inherit from 'TopLevel' after it has been defined.
        Dummy implementation."""
    def __init__(self, var):
        self.var = var


# Making 'Otherclass' inheriting from metaclass 'TopLevel' to have available
# as class properties 'depth'.
# As per https://stackoverflow.com/a/5121381/4442753
OtherClass = TopLevel(OtherClass.__name__,
                      OtherClass.__bases__,
                      dict(OtherClass.__dict__))
# Set a value to '_depth'
OtherClass._depth = 3

# Check OtherClass has 'depth' class property
OtherClass.depth
Out[6]: 3

# Instanciate OtherClass: instance has no 'depth' attribute?
otherclass = OtherClass(4)
otherclass.depth
Traceback (most recent call last):

  File "/tmp/ipykernel_748533/2343605532.py", line 1, in <module>
    otherclass.depth

AttributeError: 'OtherClass' object has no attribute 'depth'

Can this be achieved?

Edit

Code snippet illustrating my answer to @martineau' 1st comment:

class A:
    a=2

class B(A):
    def __init__(self,var):
        self.var = var
        
b = B(42)

b.a
Out[26]: 2

b.var
Out[27]: 42
pierre_j
  • 895
  • 2
  • 11
  • 26
  • Why would you expect a class property to available to an instance of that class in the first place — regardless of whether the property is inherited or not? – martineau Dec 05 '21 at 18:11
  • Hi martineau. I think I have not a clear understanding about what @property does. I am picturing that whatever it does, result is kept as a class attribute in inheriting class. When instancing a class, you find then the class attribute into its instance, this is 'normal' behavior. I am giving a code snippet of in initial post. In this code snippet, if I 'protect' 'b' as a class property, I expect it to become protected in class instance. Answer below makes this possible, it fits my need. – pierre_j Dec 05 '21 at 18:33
  • Your update is confusing because both `a` and `var` are being given the same value. If you change it so `b = B(42)` the resulting `b.var` value will not surprisingly be `42` — which doesn't mean the `a` class `B` attribute is being accessed through the `b` instance of class `B`. – martineau Dec 05 '21 at 18:46
  • I am sorry martineau, I don't understand your comment. I changed as per your comment to make it less confusing, and it still shows what I think is the answer to your initial question: because leaving the 'class property' stuff apart, an instance of a class A inheriting from a class B keeps class B attribute. This is why I am expecting this class B attribute, if it is changed into a property, remains in the instance of A. I am expecting the 'normal python behavior' to remain, that the attribute is a property or not in B. – pierre_j Dec 05 '21 at 19:53
  • OK, that makes more sense. – martineau Dec 05 '21 at 21:48

1 Answers1

1

Here is a solution:

class TopLevel(type):
    """ TopLevel metaclass defining class properties. """
    @property
    def depth(cls) -> int:
        return cls._depth

class OtherClass:
    """ Class that will inherit from 'TopLevel' after it has been defined.
        Dummy implementation."""
    def __init__(self, var):
        self.var = var

# explicitely add property to OtherClass.__dict__
d = dict(OtherClass.__dict__)
d.update({"depth": TopLevel.depth})

OtherClass = TopLevel(OtherClass.__name__,
                      OtherClass.__bases__,
                      d)

OtherClass._depth = 3

otherclass = OtherClass(4)
print(otherclass.depth)
Nechoj
  • 1,512
  • 1
  • 8
  • 18