Python只读属性

我不知道什么时候属性应该是私人的,如果我应该使用财产。

我最近读到setter和getters不pythonic,我应该使用属性装饰。 没关系。

但是,如果我有属性,不能从课外设置,但可以读取(只读属性)。 如果这个属性是私人的,私人的,我指的是下划线,就像self._x ? 如果是的话,我怎样才能读取它,而不使用getter? 我现在唯一知道的方法是写

 @property def x(self): return self._x 

这样我可以通过obj.x读取属性,但我不能设置它obj.x = 1所以没关系。

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

你的意见和实践是什么?

一般来说,Python程序应该是假设所有的用户都是成年人自愿的,并且自己负责正确地使用东西。 但是,在极less数情况下,对于可设置的属性(例如派生值或从某个静态数据源读取的值)没有意义,getter-only属性通常是首选模式。

就我的两分钱来说, Silas Ray正走在正确的轨道上,但是我想增加一个例子。 😉

Python是一种types不安全的语言,因此您必须始终相信代码的用户才能像合理(合理)的人一样使用代码。

根据PEP 8 :

只使用一个前导下划线用于非公共方法和实例variables。

要在类中拥有“只读”属性,您可以使用@property装饰,您需要从objectinheritance来使用新样式的类。

例:

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

这是避免这种假设的一种方法

所有用户都是成年人自愿的,因此有责任正确使用自己的东西。

请参阅下面的更新

使用@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 b(self): return self._c # you get this ... it's long 

运用

没有下划线:这是一个公共variables。
一个下划线:这是一个受保护的variables。
两个下划线:这是一个私有variables。

除了最后一个,这是一个惯例。 你仍然可以,如果你真的努力,用双下划线访问variables。

那么我们该怎么办? 我们放弃在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 

你问:

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 

更新

我从来没有料到这个答案会得到如此多的关注。 令人惊讶的是它。 这鼓励我创build一个你可以使用的包。

 $ pip install read-only-properties 

在你的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]: fb = 5 In [5]: fa = 'boom' --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-5-a5226072b3b4> in <module>() ----> 1 fa = '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 

请注意,实例方法也是属性(类的),如果你真的想成为一个坏人,你可以在类或实例级别设置它们。 或者你可以设置一个类variables(也是类的一个属性),方便只读属性不能很好地工作。 我想说的是,“只读属性”问题事实上比通常所认为的更普遍。 幸运的是,在工作中有传统的期望是如此强大,以至于让我们对这些其他情况一无所知(毕竟,几乎所有东西都是Python中的某种属性)。

基于这些期望,我认为最通用和最轻量级的方法是采用“公共”(无前导下划线)属性只读的约定,除非明确logging为可写。 这包含了通常的期望,即方法不会被修补,而指示实例默认的类variables更好。 如果您对某些特殊属性感到非常偏执,则使用只读描述符作为最后的资源度量。

虽然我喜欢Oz123的类装饰器,但您也可以执行以下操作,它使用显式类封装器和带类Factory方法的__new__,在闭包中返回类:

 class B(object): def __new__(cls, val): return cls.factory(val) @classmethod def factory(cls, val): private = {'var': 'test'} class InnerB(object): def __init__(self): self.variable = val pass @property def var(self): return private['var'] return InnerB() 

我知道我正在从死亡中带回这个线索,但是我正在研究如何使一个属性只读,并且在发现这个主题之后,我对已经分享的解决scheme并不满意。

所以,回到最初的问题,如果你从这个代码开始:

 @property def x(self): return self._x 

而且你想让X只读,你可以添加:

 @x.setter def x(self, value): raise Exception("Member readonly") 

那么,如果你运行以下:

 print (x) # Will print whatever X value is x = 3 # Will raise exception "Member readonly"