Using property() on classmethods

后端 未结 15 759
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

    I hope this dead-simple read-only @classproperty decorator would help somebody looking for classproperties.

    class classproperty(object):
    
        def __init__(self, fget):
            self.fget = fget
    
        def __get__(self, owner_self, owner_cls):
            return self.fget(owner_cls)
    
    class C(object):
    
        @classproperty
        def x(cls):
            return 1
    
    assert C.x == 1
    assert C().x == 1
    
    0 讨论(0)
  • 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
    <class '__main__.FooClassProperty'>
    

    and the class

    >>> FooClassProperty.foo
    <class '__main__.FooClassProperty'>
    

    have access to the class property.

    0 讨论(0)
  • 2020-11-22 17:31

    Here's my suggestion. Don't use class methods.

    Seriously.

    What's the reason for using class methods in this case? Why not have an ordinary object of an ordinary class?


    If you simply want to change the value, a property isn't really very helpful is it? Just set the attribute value and be done with it.

    A property should only be used if there's something to conceal -- something that might change in a future implementation.

    Maybe your example is way stripped down, and there is some hellish calculation you've left off. But it doesn't look like the property adds significant value.

    The Java-influenced "privacy" techniques (in Python, attribute names that begin with _) aren't really very helpful. Private from whom? The point of private is a little nebulous when you have the source (as you do in Python.)

    The Java-influenced EJB-style getters and setters (often done as properties in Python) are there to facilitate Java's primitive introspection as well as to pass muster with the static language compiler. All those getters and setters aren't as helpful in Python.

    0 讨论(0)
  • 2020-11-22 17:32

    Here's a solution which should work for both access via the class and access via an instance which uses a metaclass.

    In [1]: class ClassPropertyMeta(type):
       ...:     @property
       ...:     def prop(cls):
       ...:         return cls._prop
       ...:     def __new__(cls, name, parents, dct):
       ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
       ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
       ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
       ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
       ...:
    
    In [2]: class ClassProperty(object):
       ...:     __metaclass__ = ClassPropertyMeta
       ...:     _prop = 42
       ...:     def __getattr__(self, attr):
       ...:         raise Exception('Never gets called')
       ...:
    
    In [3]: ClassProperty.prop
    Out[3]: 42
    
    In [4]: ClassProperty.prop = 1
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-4-e2e8b423818a> in <module>()
    ----> 1 ClassProperty.prop = 1
    
    AttributeError: can't set attribute
    
    In [5]: cp = ClassProperty()
    
    In [6]: cp.prop
    Out[6]: 42
    
    In [7]: cp.prop = 1
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-7-e8284a3ee950> in <module>()
    ----> 1 cp.prop = 1
    
    <ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
          6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
          7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
    ----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
          9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
    
    AttributeError: can't set attribute
    

    This also works with a setter defined in the metaclass.

    0 讨论(0)
  • 2020-11-22 17:34

    Python 3.9 2020 UPDATE

    You can just use them together (taken from the 3.9 docs):

    class G:
        @classmethod
        @property
        def __doc__(cls):
            return f'A doc for {cls.__name__!r}'
    
    

    See https://docs.python.org/3/howto/descriptor.html#id27

    0 讨论(0)
  • 2020-11-22 17:37

    Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

    Do you have access to at least one instance of the class? I can think of a way to do it then:

    class MyClass (object):
        __var = None
    
        def _set_var (self, value):
            type (self).__var = value
    
        def _get_var (self):
            return self.__var
    
        var = property (_get_var, _set_var)
    
    a = MyClass ()
    b = MyClass ()
    a.var = "foo"
    print b.var
    
    0 讨论(0)
提交回复
热议问题