代码之家  ›  专栏  ›  技术社区  ›  Björn Pollex

如何在Python中创建只读类属性?

  •  59
  • Björn Pollex  · 技术社区  · 15 年前

    class foo:
        x = 4
        @property
        @classmethod
        def number(cls):
            return x
    

    那么我希望以下工作:

    >>> foo.number
    4
    

    不幸的是,上述方法不起作用。而不是给我 4 它给了我 <property object at 0x101786c58>

    5 回复  |  直到 15 年前
        1
  •  52
  •   bobince    15 年前

    property 描述符总是在从类访问时返回自身(即 instance None 在它的 __get__

    如果这不是您想要的,您可以编写一个始终使用class对象的新描述符( owner )而不是实例:

    >>> class classproperty(object):
    ...     def __init__(self, getter):
    ...         self.getter= getter
    ...     def __get__(self, instance, owner):
    ...         return self.getter(owner)
    ... 
    >>> class Foo(object):
    ...     x= 4
    ...     @classproperty
    ...     def number(cls):
    ...         return cls.x
    ... 
    >>> Foo().number
    4
    >>> Foo.number
    4
    
        2
  •  69
  •   Neuron MonoThreaded    5 年前

    这将使 Foo.number 只读 属性:

    class MetaFoo(type):
        @property
        def number(cls):
            return cls.x
    
    class Foo(object, metaclass=MetaFoo):
        x = 4
    
    print(Foo.number)
    # 4
    
    Foo.number = 6
    # AttributeError: can't set attribute
    

    解释 :使用 @property 看起来像这样:

    class Foo(object):
        @property
        def number(self):
            ...
    foo = Foo()
    

    中定义的属性 Foo 对于其实例是只读的。也就是说, foo.number = 6 会引起 AttributeError .

    类似地,如果你想 食品编号 提出异议 属性错误 您需要设置中定义的属性 type(Foo) . 因此需要一个元类。


    请注意,这种只读性不能免受黑客的攻击。 可以通过更改Foo的

    class Base(type): pass
    Foo.__class__ = Base
    
    # makes Foo.number a normal class attribute
    Foo.number = 6   
    print(Foo.number)
    

    6
    

    或者,如果你想 可设置属性,

    class WritableMetaFoo(type): 
        @property
        def number(cls):
            return cls.x
        @number.setter
        def number(cls, value):
            cls.x = value
    Foo.__class__ = WritableMetaFoo
    
    # Now the assignment modifies `Foo.x`
    Foo.number = 6   
    print(Foo.number)
    

    同时打印

    6
    
        3
  •  16
  •   Community CDub    8 年前

    我同意你的看法 unubtu's answer 蟒蛇3 (具体来说,python3.4是我一直在努力解决的问题)。下面是如何在Python3.4下形成模式以使事情正常工作的:

    class MetaFoo(type):
       @property
       def number(cls):
          return cls.x
    
    class Foo(metaclass=MetaFoo):
       x = 4
    
    print(Foo.number)
    # 4
    
    Foo.number = 6
    # AttributeError: can't set attribute
    
        4
  •  9
  •   Mikhail Gerasimov    9 年前

    print(Foo.number)
    # 4
    
    f = Foo()
    print(f.number)
    # 'Foo' object has no attribute 'number'
    

    property 装饰工。

    我试图解决这个问题。下面是它现在的工作原理:

    @classproperty_support
    class Bar(object):
        _bar = 1
    
        @classproperty
        def bar(cls):
            return cls._bar
    
        @bar.setter
        def bar(cls, value):
            cls._bar = value
    
    
    # @classproperty should act like regular class variable.
    # Asserts can be tested with it.
    # class Bar:
    #     bar = 1
    
    
    assert Bar.bar == 1
    
    Bar.bar = 2
    assert Bar.bar == 2
    
    foo = Bar()
    baz = Bar()
    assert foo.bar == 2
    assert baz.bar == 2
    
    Bar.bar = 50
    assert baz.bar == 50
    assert foo.bar == 50
    

    @classproperty 这和 @property 对于类变量。我们唯一需要的是额外的 @classproperty_support 班级装饰师。

    以下是实现:

    class classproperty:
        """
        Same as property(), but passes obj.__class__ instead of obj to fget/fset/fdel.
        Original code for property emulation:
        https://docs.python.org/3.5/howto/descriptor.html#properties
        """
        def __init__(self, fget=None, fset=None, fdel=None, doc=None):
            self.fget = fget
            self.fset = fset
            self.fdel = fdel
            if doc is None and fget is not None:
                doc = fget.__doc__
            self.__doc__ = doc
    
        def __get__(self, obj, objtype=None):
            if obj is None:
                return self
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(obj.__class__)
    
        def __set__(self, obj, value):
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(obj.__class__, value)
    
        def __delete__(self, obj):
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(obj.__class__)
    
        def getter(self, fget):
            return type(self)(fget, self.fset, self.fdel, self.__doc__)
    
        def setter(self, fset):
            return type(self)(self.fget, fset, self.fdel, self.__doc__)
    
        def deleter(self, fdel):
            return type(self)(self.fget, self.fset, fdel, self.__doc__)
    
    
    def classproperty_support(cls):
        """
        Class decorator to add metaclass to our class.
        Metaclass uses to add descriptors to class attributes, see:
        http://stackoverflow.com/a/26634248/1113207
        """
        class Meta(type):
            pass
    
        for name, obj in vars(cls).items():
            if isinstance(obj, classproperty):
                setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel))
    
        class Wrapper(cls, metaclass=Meta):
            pass
        return Wrapper
    

    注: 代码没有经过太多的测试,如果没有如您所期望的那样工作,请随时注意。

        5
  •  8
  •   Michael Reinhardt    7 年前

    米哈伊尔·格拉西莫夫的解决方案是相当完整的。不幸的是,这是一个缺点。如果有一个类使用他的classproperty,那么由于 TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases class Wrapper .

    幸运的是,这个问题可以解决。创建时只从给定类的元类继承 class Meta .

    def classproperty_support(cls):
      """
      Class decorator to add metaclass to our class.
      Metaclass uses to add descriptors to class attributes, see:
      http://stackoverflow.com/a/26634248/1113207
      """
      # Use type(cls) to use metaclass of given class
      class Meta(type(cls)): 
          pass
    
      for name, obj in vars(cls).items():
          if isinstance(obj, classproperty):
              setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel))
    
      class Wrapper(cls, metaclass=Meta):
          pass
      return Wrapper