代码之家  ›  专栏  ›  技术社区  ›  Mark Roddy

在类方法上使用property()

  •  143
  • Mark Roddy  · 技术社区  · 17 年前

    classmethod() property() 函数,但会导致错误。我能够在解释器中再现以下错误:

    class Foo(object):
        _var = 5
        @classmethod
        def getvar(cls):
            return cls._var
        @classmethod
        def setvar(cls, value):
            cls._var = value
        var = property(getvar, setvar)
    

    >>> f = Foo()
    >>> f.getvar()
    5
    >>> f.setvar(4)
    >>> f.getvar()
    4
    >>> f.var
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: 'classmethod' object is not callable
    >>> f.var=5
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: 'classmethod' object is not callable
    

    是否可以使用 作用于 @classmethod 装饰功能?

    13 回复  |  直到 4 年前
        1
  •  135
  •   wim    4 年前

    Python>=3.9

    只需同时使用两个装饰器。看见 this answer

    Python<3.9

    属性是在类上创建的,但会影响实例。因此,如果需要classmethod属性,请在元类上创建该属性。

    >>> 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
    

    >>> 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
    

    或者,使用Python3的 metaclass=... 语法,以及在 foo 类主体,以及负责设置 _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
    
        2
  •  74
  •   Jason R. Coombs    7 年前

    阅读 Python 2.2 release

    在以下情况下不会调用[属性的]get方法: 该属性作为类进行访问 属性(C.x),而不是作为 实例属性(C().x)。如果你 要覆盖uuu get uuu操作吗 用于用作类时的属性 属性,则可以将属性子类化- 它本身就是一种新型的 扩展它的_uget _uu方法,或者您可以 从头定义描述符类型 通过创建一个新样式的类 定义获取、设置和 __删除方法。

    因此,我认为规定的解决方案是创建一个ClassProperty作为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
    

    但是,二传手实际上不起作用:

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

    foo._var 如果未更改,则只需使用新值覆盖属性。

    你也可以使用 ClassProperty

    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
    
        3
  •  70
  •   Denis Ryzhkov    12 年前

    我希望这个死简单只读 @classproperty decorator会帮助寻找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
    
        4
  •  57
  •   user2357112    4 年前

    Python 3.9 2020更新

    您可以将它们一起使用:

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

    顺序问题-由于描述符如何相互作用, @classmethod 必须在上面。

    看见 https://docs.python.org/3.9/library/functions.html#classmethod

        5
  •  40
  •   Community CDub    5 年前

    可以将property()函数与classmethod修饰函数一起使用吗?

    然而,classmethod只是一个绑定方法(部分函数),它位于一个可从该类的实例访问的类上。

    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
    

    此代码可用于测试-它应在不引发任何错误的情况下通过:

    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')
    

    请注意,我们根本不需要元类——而且您也不能通过元类的实例直接访问元类。

    写一篇 @classproperty 室内装修设计师

    实际上,您可以创建一个 classproperty 通过子类化,只需几行代码即可实现装饰器 所有物 (它是用C实现的,但您可以看到与之相当的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))
    

    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
    

    这段代码应该可以正常工作:

    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()
    

    但我不确定这是否明智。旧邮件列表 article 表明它不应该起作用。

    __dict__ .

    __口述__ . 例如:

    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
    

    然后元类的类实例可以有一个属性,该属性使用前面几节中已经演示的原理访问类的属性:

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

    现在我们看到两个实例

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

    班级呢

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

        6
  •  34
  •   OJFord    4 年前

    Python 3!

    [更新:见 @Amit Portnoy's answer 对于版本中更干净的方法>=3.9]

    老问题,很多视图,非常需要一个真正的Python 3方式。

    幸运的是,这很容易和 metaclass 夸格:

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

    >>> Foo.var

    “福!”

        7
  •  18
  •   ddaa    17 年前

    没有合理的方法使这个“类属性”系统在Python中工作。

    class ClassProperty(object):
        def __init__(self, getter, setter):
            self.getter = getter
            self.setter = setter
        def __get__(self, cls, owner):
            return getattr(cls, self.getter)()
        def __set__(self, cls, value):
            getattr(cls, self.setter)(value)
    
    class MetaFoo(type):
        var = ClassProperty('getvar', 'setvar')
    
    class Foo(object):
        __metaclass__ = MetaFoo
        _var = 5
        @classmethod
        def getvar(cls):
            print "Getting var =", cls._var
            return cls._var
        @classmethod
        def setvar(cls, value):
            print "Setting var =", value
            cls._var = value
    
    x = Foo.var
    print "Foo.var = ", x
    Foo.var = 42
    x = Foo.var
    print "Foo.var = ", x
    

    问题的症结在于属性是Python所称的“描述符”。没有简单明了的方法来解释这种元编程是如何工作的,所以我必须指出 descriptor howto

    只有在实现一个相当高级的框架时,才需要理解这类事情。比如透明对象持久化或RPC系统,或者一种特定于域的语言。

    然而,在对之前答案的评论中,你说你

    需要修改一个属性,该属性以一个类的所有实例都可以看到的方式,并且在调用这些类方法的作用域中没有对该类的所有实例的引用。

    在我看来,你真正想要的是 Observer

        8
  •  7
  •   Nils Philippsen    15 年前

    如果您希望通过实例化对象访问class属性,那么仅在meta类上设置它是没有帮助的,在这种情况下,您还需要在对象上安装一个普通属性(它将分派给class属性)。我认为以下几点更为明确:

    #!/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
    
        9
  •  4
  •   Florian Bösch    17 年前

    class ClassProperty(object):
        def __init__(self, fget, fset):
            self.fget = fget
            self.fset = fset
    
        def __get__(self, instance, owner):
            return self.fget()
    
        def __set__(self, instance, value):
            self.fset(value)
    
    class Foo(object):
        _bar = 1
        def get_bar():
            print 'getting'
            return Foo._bar
    
        def set_bar(value):
            print 'setting'
            Foo._bar = value
    
        bar = ClassProperty(get_bar, set_bar)
    
    f = Foo()
    #__get__ works
    f.bar
    Foo.bar
    
    f.bar = 2
    Foo.bar = 3 #__set__ does not
    
        10
  •  3
  •   John Millikin    17 年前

    因为我需要修改一个属性,该属性可以被类的所有实例看到,并且在调用这些类方法的作用域中没有对类的所有实例的引用。

    您是否至少有权访问该类的一个实例?我可以想出一个办法:

    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
    
        11
  •  2
  •   Sufian    17 年前

    尝试一下,它可以在不必更改/添加大量现有代码的情况下完成任务。

    >>> 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
    

    这个 property callable 论据。给他们lambda包装器(它将实例作为第一个参数传递给他们),一切正常。

        12
  •  2
  •   papercrane    8 年前

    这里有一个解决方案,它既可以通过类进行访问,也可以通过使用元类的实例进行访问。

    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
    

    这也适用于元类中定义的setter。

        13
  •  1
  •   Emma Brown    4 年前

    我找到了一个解决这个问题的干净办法。这是一个叫做 类别实用程序 ( pip install classutilities ),请参阅文档 here on PyPi .

    考虑例子:

    import classutilities
    
    class SomeClass(classutilities.ClassPropertiesMixin):
        _some_variable = 8  # Some encapsulated class variable
    
        @classutilities.classproperty
        def some_variable(cls):  # class property getter
            return cls._some_variable
    
        @some_variable.setter
        def some_variable(cls, value):  # class property setter
            cls._some_variable = value
    

    您可以在类级别和实例级别上使用它:

    # Getter on class level:
    value = SomeClass.some_variable
    print(value)  # >>> 8
    # Getter on instance level
    inst = SomeClass()
    value = inst.some_variable
    print(value)  # >>> 8
    
    # Setter on class level:
    new_value = 9
    SomeClass.some_variable = new_value
    print(SomeClass.some_variable)   # >>> 9
    print(SomeClass._some_variable)  # >>> 9
    # Setter on instance level
    inst = SomeClass()
    inst.some_variable = new_value
    print(SomeClass.some_variable)   # >>> 9
    print(SomeClass._some_variable)  # >>> 9
    print(inst.some_variable)        # >>> 9
    print(inst._some_variable)       # >>> 9
    

        14
  •  1
  •   user2290820    3 年前

    https://stackoverflow.com/a/1800999/2290820

    
    class MetaProperty(type):
    
        def __init__(cls, *args, **kwargs):
            super()
    
        @property
        def praparty(cls):
            return cls._var
    
        @praparty.setter
        def praparty(cls, val):
            cls._var = val
    
    
    class A(metaclass=MetaProperty):
        _var = 5
    
    
    print(A.praparty)
    A.praparty = 6
    print(A.praparty)
    
        15
  •  0
  •   Andrew Myers Sanchit Panchwatikar    7 年前

    对Python2和Python3有效。

    from future.utils import with_metaclass
    
    class BuilderMetaClass(type):
        @property
        def load_namespaces(self):
            return (self.__sourcepath__)
    
    class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
        __sourcepath__ = 'sp'        
    
    print(BuilderMixin.load_namespaces)
    

        16
  •  -1
  •   spacether    4 年前

    这是我的解决方案,它也缓存类属性

    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
    
        17
  •  -35
  •   S.Lott    17 年前

    这是我的建议。不要使用类方法。

    在这种情况下使用类方法的原因是什么?为什么没有一个普通类的普通对象呢?


    只有在有需要隐藏的内容时才应该使用属性,这些内容可能在将来的实现中发生更改。

    也许你的例子很简单,还有一些地狱般的计算。但它看起来并没有带来显著的价值。

    受Java影响的“隐私”技术(在Python中,属性名称以_开头)实际上没有多大帮助。谁的私人信件?当您拥有源代码时,private的意义有点模糊(就像在Python中一样)

    受Java影响的EJB风格的getter和setter(通常在Python中作为属性完成)用于促进Java的基本自省,以及通过静态语言编译器。所有这些getter和setter在Python中都没有那么有用。