Using property() on classmethods

后端 未结 15 760
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:42

    Here is my solution that also caches the class property

    class class_property(object):
        # this caches the result of the function call for fn with cls input
        # use this as a decorator on function methods that you want converted
        # into cached properties
    
        def __init__(self, fn):
            self._fn_name = fn.__name__
            if not isinstance(fn, (classmethod, staticmethod)):
                fn = classmethod(fn)
            self._fn = fn
    
        def __get__(self, obj, cls=None):
            if cls is None:
                cls = type(obj)
            if (
                self._fn_name in vars(cls) and
                type(vars(cls)[self._fn_name]).__name__ != "class_property"
            ):
                return vars(cls)[self._fn_name]
            else:
                value = self._fn.__get__(obj, cls)()
                setattr(cls, self._fn_name, value)
                return value
    
    0 讨论(0)
  • 2020-11-22 17:43

    A property is created on a class but affects an instance. So if you want a classmethod property, create the property on the metaclass.

    >>> class foo(object):
    ...     _var = 5
    ...     class __metaclass__(type):  # Python 2 syntax for metaclasses
    ...         pass
    ...     @classmethod
    ...     def getvar(cls):
    ...         return cls._var
    ...     @classmethod
    ...     def setvar(cls, value):
    ...         cls._var = value
    ...     
    >>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
    >>> foo.var
    5
    >>> foo.var = 3
    >>> foo.var
    3
    

    But since you're using a metaclass anyway, it will read better if you just move the classmethods in there.

    >>> class foo(object):
    ...     _var = 5
    ...     class __metaclass__(type):  # Python 2 syntax for metaclasses
    ...         @property
    ...         def var(cls):
    ...             return cls._var
    ...         @var.setter
    ...         def var(cls, value):
    ...             cls._var = value
    ... 
    >>> foo.var
    5
    >>> foo.var = 3
    >>> foo.var
    3
    

    or, using Python 3's metaclass=... syntax, and the metaclass defined outside of the foo class body, and the metaclass responsible for setting the initial value of _var:

    >>> class foo_meta(type):
    ...     def __init__(cls, *args, **kwargs):
    ...         cls._var = 5
    ...     @property
    ...     def var(cls):
    ...         return cls._var
    ...     @var.setter
    ...     def var(cls, value):
    ...         cls._var = value
    ...
    >>> class foo(metaclass=foo_meta):
    ...     pass
    ...
    >>> foo.var
    5
    >>> foo.var = 3
    >>> foo.var
    3
    
    0 讨论(0)
  • 2020-11-22 17:43

    Give this a try, it gets the job done without having to change/add a lot of existing code.

    >>> class foo(object):
    ...     _var = 5
    ...     def getvar(cls):
    ...         return cls._var
    ...     getvar = classmethod(getvar)
    ...     def setvar(cls, value):
    ...         cls._var = value
    ...     setvar = classmethod(setvar)
    ...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
    ...
    >>> f = foo()
    >>> f.var
    5
    >>> f.var = 3
    >>> f.var
    3
    

    The property function needs two callable arguments. give them lambda wrappers (which it passes the instance as its first argument) and all is well.

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

    Reading the Python 2.2 release notes, I find the following.

    The get method [of a property] won't be called when the property is accessed as a class attribute (C.x) instead of as an instance attribute (C().x). If you want to override the __get__ operation for properties when used as a class attribute, you can subclass property - it is a new-style type itself - to extend its __get__ method, or you can define a descriptor type from scratch by creating a new-style class that defines __get__, __set__ and __delete__ methods.

    NOTE: The below method doesn't actually work for setters, only getters.

    Therefore, I believe the prescribed solution is to create a ClassProperty as a subclass of property.

    class ClassProperty(property):
        def __get__(self, cls, owner):
            return self.fget.__get__(None, owner)()
    
    class foo(object):
        _var=5
        def getvar(cls):
            return cls._var
        getvar=classmethod(getvar)
        def setvar(cls,value):
            cls._var=value
        setvar=classmethod(setvar)
        var=ClassProperty(getvar,setvar)
    
    assert foo.getvar() == 5
    foo.setvar(4)
    assert foo.getvar() == 4
    assert foo.var == 4
    foo.var = 3
    assert foo.var == 3
    

    However, the setters don't actually work:

    foo.var = 4
    assert foo.var == foo._var # raises AssertionError
    

    foo._var is unchanged, you've simply overwritten the property with a new value.

    You can also use ClassProperty as a decorator:

    class foo(object):
        _var = 5
    
        @ClassProperty
        @classmethod
        def var(cls):
            return cls._var
    
        @var.setter
        @classmethod
        def var(cls, value):
            cls._var = value
    
    assert foo.var == 5
    
    0 讨论(0)
  • 2020-11-22 17:46

    Setting it only on the meta class doesn't help if you want to access the class property via an instantiated object, in this case you need to install a normal property on the object as well (which dispatches to the class property). I think the following is a bit more clear:

    #!/usr/bin/python
    
    class classproperty(property):
        def __get__(self, obj, type_):
            return self.fget.__get__(None, type_)()
    
        def __set__(self, obj, value):
            cls = type(obj)
            return self.fset.__get__(None, cls)(value)
    
    class A (object):
    
        _foo = 1
    
        @classproperty
        @classmethod
        def foo(cls):
            return cls._foo
    
        @foo.setter
        @classmethod
        def foo(cls, value):
            cls.foo = value
    
    a = A()
    
    print a.foo
    
    b = A()
    
    print b.foo
    
    b.foo = 5
    
    print a.foo
    
    A.foo = 10
    
    print b.foo
    
    print A.foo
    
    0 讨论(0)
  • 2020-11-22 17:49

    Python 3!

    Old question, lots of views, sorely in need of a one-true Python 3 way.

    Luckily, it's easy with the metaclass kwarg:

    class FooProperties(type):
    
        @property
        def var(cls):
            return cls._var
    
    class Foo(object, metaclass=FooProperties):
        _var = 'FOO!'
    

    Then, >>> Foo.var

    'FOO!'

    0 讨论(0)
提交回复
热议问题