Using property() on classmethods

后端 未结 15 943
Happy的楠姐
Happy的楠姐 2020-11-22 16:55

I have a class with two class methods (using the classmethod() function) for getting and setting what is essentially a static variable. I tried to use the property() functi

15条回答
  •  南方客
    南方客 (楼主)
    2020-11-22 17:27

    Is it possible to use the property() function with classmethod decorated functions?

    No.

    However, a classmethod is simply a bound method (a partial function) on a class accessible from instances of that class.

    Since the instance is a function of the class and you can derive the class from the instance, you can can get whatever desired behavior you might want from a class-property with property:

    class Example(object):
        _class_property = None
        @property
        def class_property(self):
            return self._class_property
        @class_property.setter
        def class_property(self, value):
            type(self)._class_property = value
        @class_property.deleter
        def class_property(self):
            del type(self)._class_property
    

    This code can be used to test - it should pass without raising any errors:

    ex1 = Example()
    ex2 = Example()
    ex1.class_property = None
    ex2.class_property = 'Example'
    assert ex1.class_property is ex2.class_property
    del ex2.class_property
    assert not hasattr(ex1, 'class_property')
    

    And note that we didn't need metaclasses at all - and you don't directly access a metaclass through its classes' instances anyways.

    writing a @classproperty decorator

    You can actually create a classproperty decorator in just a few lines of code by subclassing property (it's implemented in C, but you can see equivalent Python here):

    class classproperty(property):
        def __get__(self, obj, objtype=None):
            return super(classproperty, self).__get__(objtype)
        def __set__(self, obj, value):
            super(classproperty, self).__set__(type(obj), value)
        def __delete__(self, obj):
            super(classproperty, self).__delete__(type(obj))
    

    Then treat the decorator as if it were a classmethod combined with property:

    class Foo(object):
        _bar = 5
        @classproperty
        def bar(cls):
            """this is the bar attribute - each subclass of Foo gets its own.
            Lookups should follow the method resolution order.
            """
            return cls._bar
        @bar.setter
        def bar(cls, value):
            cls._bar = value
        @bar.deleter
        def bar(cls):
            del cls._bar
    

    And this code should work without errors:

    def main():
        f = Foo()
        print(f.bar)
        f.bar = 4
        print(f.bar)
        del f.bar
        try:
            f.bar
        except AttributeError:
            pass
        else:
            raise RuntimeError('f.bar must have worked - inconceivable!')
        help(f)  # includes the Foo.bar help.
        f.bar = 5
    
        class Bar(Foo):
            "a subclass of Foo, nothing more"
        help(Bar) # includes the Foo.bar help!
        b = Bar()
        b.bar = 'baz'
        print(b.bar) # prints baz
        del b.bar
        print(b.bar) # prints 5 - looked up from Foo!
    
        
    if __name__ == '__main__':
        main()
    

    But I'm not sure how well-advised this would be. An old mailing list article suggests it shouldn't work.

    Getting the property to work on the class:

    The downside of the above is that the "class property" isn't accessible from the class, because it would simply overwrite the data descriptor from the class __dict__.

    However, we can override this with a property defined in the metaclass __dict__. For example:

    class MetaWithFooClassProperty(type):
        @property
        def foo(cls):
            """The foo property is a function of the class -
            in this case, the trivial case of the identity function.
            """
            return cls
    

    And then a class instance of the metaclass could have a property that accesses the class's property using the principle already demonstrated in the prior sections:

    class FooClassProperty(metaclass=MetaWithFooClassProperty):
        @property
        def foo(self):
            """access the class's property"""
            return type(self).foo
    

    And now we see both the instance

    >>> FooClassProperty().foo
    
    

    and the class

    >>> FooClassProperty.foo
    
    

    have access to the class property.

提交回复
热议问题