Python只读属性

Raf*_*ski 74 python properties private readonly python-2.7

我不知道什么时候属性应该是私有的,如果我应该使用属性.

我最近读到,setter和getter不是pythonic,我应该使用属性装饰器.没关系.

但是,如果我有属性,那么不能从类外部设置但可以读取(只读属性).这个属性应该是私有的,私有的我是指下划线,就像那样self._x?如果是,那么如何在不使用getter的情况下阅读它?我现在唯一知道的方法是写

@property
def x(self):
    return self._x
Run Code Online (Sandbox Code Playgroud)

这样我可以读取属性obj.x但我无法设置它obj.x = 1所以它没关系.

但是我真的应该关心设置不能设置的对象吗?也许我应该离开它.但是我再也不能使用下划线,因为obj._x用户读取很奇怪,所以我应该使用obj.x然后再次用户不知道他不能设置这个属性.

你有什么看法和实践?

sie*_*z0r 56

只是我的两分钱,西拉斯雷在正确的轨道上,但我想添加一个例子.;-)

Python是一种类型不安全的语言,因此您始终必须信任代码的用户才能像合理(明智的)人一样使用代码.

PEP 8:

仅对非公共方法和实例变量使用一个前导下划线.

要在类中使用"只读"属性,您可以使用@property装饰,您需要object在执行此操作时继承以使用新样式类.

例:

>>> class A(object):
...     def __init__(self, a):
...         self._a = a
...
...     @property
...     def a(self):
...         return self._a
... 
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Run Code Online (Sandbox Code Playgroud)

  • Python不是类型不安全的,它是动态类型的.并且名称修改不是为了使欺骗更难,而是为了防止在继承可能有问题的情况下发生名称冲突(如果你不是大编程,你甚至不应该关心). (7认同)
  • 对我来说 _make 使用 @property 装饰,当你这样做时你需要从对象继承_是这个答案的重点。谢谢。 (3认同)
  • 但是您仍然应该记住,无论如何都可以使用此方法更改可变对象。例如,如果`self .__ a = []`,您仍然可以执行此`aaappend('anything')`,它将起作用。 (2认同)
  • 我不清楚"一个合理的(明智的)人"对这个答案有什么影响.您是否可以更明确地表达您认为合理的人会做什么和不做的事情? (2认同)
  • @kkm 永远不要让错误潜入代码的唯一方法就是永远不要编写代码。 (2认同)

Sil*_*Ray 55

通常,Python程序应该假设所有用户都同意成人,因此负责自己正确使用事物.但是,在极少数情况下,对于可设置的属性(例如派生值或从某些静态数据源读取的值)没有意义,getter-only属性通常是首选模式.

  • 看起来你的答案与自己相矛盾.你说用户应该负责并且正确地使用东西,然后你说有时候属性是可设置的并且getter属性是一种优先的方式.在我看来,你可以或不可以设置属性.唯一的问题是我是应该保护这个attr还是留下它.两者之间应该没有答案. (16认同)
  • 不,我说如果你真的不能设置一个值,那么拥有一个setter是没有意义的.例如,如果您有一个圆形对象,其半径成员和一个圆周属性是从半径派生的,或者您有一个对象包含一些只读实时api,其中包含许多仅具有getter的属性.什么都不矛盾. (15认同)
  • 但负责任的用户不会尝试设置字面上无法设置的attr.并且不负责任的用户会设置attr,字面上可以设置并且由于他的设置将在代码中的其他地方引发错误.所以最后两个attr都无法设置.我应该在两者上使用财产还是不在任何地方使用? (7认同)
  • 但是负责任的用户_不应该尝试设置无法设置的attr.在编程中,如果某些东西严格地是一个不可设置的值,那么负责任或明智的事情就是确保它不可能.这些小事都有助于可靠的计划. (7认同)
  • 这是许多人和语言所采取的立场.如果它是一个你认为不可协商的职位,你可能不应该使用Python. (4认同)

Oz1*_*123 45

这是一种避免假设的方法

所有用户都同意成年人,因此负责自己正确使用事物.

请参阅下面的更新

使用@property,非常详细,例如:

   class AClassWithManyAttributes:
        '''refactored to properties'''
        def __init__(a, b, c, d, e ...)
             self._a = a
             self._b = b
             self._c = c
             self.d = d
             self.e = e

        @property
        def a(self):
            return self._a
        @property
        def b(self):
            return self._b
        @property
        def c(self):
            return self._c
        # you get this ... it's long
Run Code Online (Sandbox Code Playgroud)

运用

没有下划线:它是一个公共变量.
一个下划线:它是一个受保护的变量.
两个下划线:它是一个私有变量.

除了最后一个,它是一个惯例.如果你真的努力,你仍然可以使用双下划线访问变量.

那么我们该怎么办?我们是否放弃在Python中只读属性?

唉,read_only_properties装修工来救援!

@read_only_properties('readonly', 'forbidden')
class MyClass(object):
    def __init__(self, a, b, c):
        self.readonly = a
        self.forbidden = b
        self.ok = c

m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK 
print(m.ok, m.readonly) 
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4
Run Code Online (Sandbox Code Playgroud)

你问:

read_only_properties来自哪里?

很高兴你问,这是read_only_properties的来源:

def read_only_properties(*attrs):

    def class_rebuilder(cls):
        "The class decorator"

        class NewClass(cls):
            "This is the overwritten class"
            def __setattr__(self, name, value):
                if name not in attrs:
                    pass
                elif name not in self.__dict__:
                    pass
                else:
                    raise AttributeError("Can't modify {}".format(name))

                super().__setattr__(name, value)
        return NewClass
    return class_rebuilder
Run Code Online (Sandbox Code Playgroud)

更新

我从没想过这个答案会受到如此多的关注.令人惊讶的是.这鼓励我创建一个可以使用的包.

$ pip install read-only-properties
Run Code Online (Sandbox Code Playgroud)

在你的python shell中:

In [1]: from rop import read_only_properties

In [2]: @read_only_properties('a')
   ...: class Foo:
   ...:     def __init__(self, a, b):
   ...:         self.a = a
   ...:         self.b = b
   ...:         

In [3]: f=Foo('explodes', 'ok-to-overwrite')

In [4]: f.b = 5

In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'

/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
    116                     pass
    117                 else:
--> 118                     raise AttributeError("Can't touch {}".format(name))
    119 
    120                 super().__setattr__(name, value)

AttributeError: Can't touch a
Run Code Online (Sandbox Code Playgroud)

  • 这里有一个改进和三个问题。改进:“if..elif..else”块可以只是“if name in attrs and name in self.__dict__: raise Attr...”,不需要“pass”。问题 1:如此修饰的类最终都具有相同的“__name__”,并且它们类型的字符串表示形式也被同质化。问题 2:此装饰会覆盖任何自定义 `__setattr__`。问题 3:用户可以使用“del MyClass.__setattr__”来解决这个问题。 (2认同)

Boo*_*boo 5

这是只读属性的一种稍微不同的方法,它可能应该被称为一次写入属性,因为它们必须被初始化,不是吗?对于我们中间那些担心能够通过直接访问对象的字典来修改属性的偏执狂,我引入了“极端”名称修改:

from uuid import uuid4

class ReadOnlyProperty:
    def __init__(self, name):
        self.name = name
        self.dict_name = uuid4().hex
        self.initialized = False

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.dict_name]

    def __set__(self, instance, value):
        if self.initialized:
            raise AttributeError("Attempt to modify read-only property '%s'." % self.name)
        instance.__dict__[self.dict_name] = value
        self.initialized = True

class Point:
    x = ReadOnlyProperty('x')
    y = ReadOnlyProperty('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)
Run Code Online (Sandbox Code Playgroud)