什么时候应该将属性设置为私有并设置为只读属性?

我不知道什么时候属性应该是私有的,也不知道是否应该使用 property

我最近读到 setter 和 getter 不是 pythonic,但是使用 property装饰器是可以的。

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

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

这样我可以读取属性的 obj.x,但我不能设置它的 obj.x = 1,所以它的罚款。

但是我真的应该关心设置不能设置的对象吗?也许我应该把它留下。但是我不能使用下划线,因为读取 obj._x对于用户来说是奇怪的,所以我应该使用 obj.x,而且用户也不知道他不能设置这个属性。

你的观点和做法是什么?

149728 次浏览

Generally, Python programs should be written with the assumption that all users are consenting adults, and thus are responsible for using things correctly themselves. However, in the rare instance where it just does not make sense for an attribute to be settable (such as a derived value, or a value read from some static datasource), the getter-only property is generally the preferred pattern.

仅仅我个人的意见,Silas Ray是在正确的轨道上,但是我想添加一个例子。 ; -)

Python 是一种类型不安全的语言,因此您必须始终信任代码的用户,让他们像一个理智(明智)的人那样使用代码。

每间 PEP 8:

Use one leading underscore only for non-public methods and instance variables.

为了在类中具有“只读”属性,您可以使用 @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

请注意,实例方法也是属性(类的属性) ,如果您真的想成为一个坏蛋,您可以在类或实例级别设置它们。或者您可以设置一个 class 变量(它也是 class 的一个属性) ,其中方便的只读属性不能灵活地工作。我想说的是,“ readonly 属性”问题实际上比通常认为的更一般。幸运的是,工作中的常规期望是如此强烈,以至于我们无法写出这些其他情况(毕竟,几乎所有事情都是 python 中的某种属性)。

在这些期望的基础上,我认为最通用和轻量级的方法是采用“ public”(没有前导下划线)属性是只读的约定,除非明确地记录为可写的。这包含了通常的期望,即方法不会被修补,指示实例默认值的类变量更好,更不用说。如果您对某些特殊属性非常偏执,可以使用只读描述符作为最后的资源度量。

Here is a way to avoid the assumption that

所有用户都是成人协议,因此有责任使用 事情本身是正确的。

请看下面我的更新

使用 @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

吸毒

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

除了最后一个,它是一个约定。您仍然可以,如果您真的努力尝试,访问具有双下划线的变量。

So what do we do? Do we give up on having read only properties in 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

更新

我从没想到这个答案会得到如此多的关注。出乎意料的是,确实如此。这鼓励我创建一个可以使用的包。

$ pip install read-only-properties

在你的蟒蛇壳里:

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

虽然我喜欢 Oz123中的类装饰器,但是您也可以执行以下操作,它使用一个显式的类包装器和 _ _ new _ _,通过一个类 Factory 方法返回闭包中的类:

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

我知道我死而复生这个线程,但我正在寻找如何使一个属性只读,在找到这个主题后,我不满意的解决方案已经分享。

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

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

如果你想让 X 只读,你可以加上:

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

Then, if you run the following:

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

Here is a slightly different approach to read-only properties, which perhaps should be called write-once properties since they do have to get initialized, don't they? For the paranoid among us who worry about being able to modify properties by accessing the object's dictionary directly, I've introduced "extreme" name mangling:

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)

这是我的变通方法。

@property
def language(self):
return self._language
@language.setter
def language(self, value):
# WORKAROUND to get a "getter-only" behavior
# set the value only if the attribute does not exist
try:
if self.language == value:
pass
print("WARNING: Cannot set attribute \'language\'.")
except AttributeError:
self._language = value

我对前两个创建只读属性的答案不满意,因为第一个解决方案允许删除只读属性,然后设置,并且不阻止 __dict__。第二种解决方案可以通过测试来解决——找到与设置值相等的值,并最终改变它。

现在,说说密码。

def final(cls):
clss = cls
@classmethod
def __init_subclass__(cls, **kwargs):
raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__))
cls.__init_subclass__ = __init_subclass__
return cls




def methoddefiner(cls, method_name):
for clss in cls.mro():
try:
getattr(clss, method_name)
return clss
except(AttributeError):
pass
return None
            

            

def readonlyattributes(*attrs):
"""Method to create readonly attributes in a class
    

Use as a decorator for a class. This function takes in unlimited
string arguments for names of readonly attributes and returns a
function to make the readonly attributes readonly.
    

The original class's __getattribute__, __setattr__, and __delattr__ methods
are redefined so avoid defining those methods in the decorated class
    

You may create setters and deleters for readonly attributes, however
if they are overwritten by the subclass, they lose access to the readonly
attributes.
    

Any method which sets or deletes a readonly attribute within
the class loses access if overwritten by the subclass besides the __new__
or __init__ constructors.
    

This decorator doesn't support subclassing of these classes
"""
def classrebuilder(cls):
def __getattribute__(self, name):
if name == '__dict__':
from types import MappingProxyType
return MappingProxyType(super(cls, self).__getattribute__('__dict__'))
return super(cls, self).__getattribute__(name)
def __setattr__(self, name, value):
if name == '__dict__' or name in attrs:
import inspect
stack = inspect.stack()
try:
the_class = stack[1][0].f_locals['self'].__class__
except(KeyError):
the_class = None
the_method = stack[1][0].f_code.co_name
if the_class != cls:
if methoddefiner(type(self), the_method) != cls:
raise AttributeError("Cannot set readonly attribute '{}'".format(name))
return super(cls, self).__setattr__(name, value)
def __delattr__(self, name):
if name == '__dict__' or name in attrs:
import inspect
stack = inspect.stack()
try:
the_class = stack[1][0].f_locals['self'].__class__
except(KeyError):
the_class = None
the_method = stack[1][0].f_code.co_name
if the_class != cls:
if methoddefiner(type(self), the_method) != cls:
raise AttributeError("Cannot delete readonly attribute '{}'".format(name))
return super(cls, self).__delattr__(name)
clss = cls
cls.__getattribute__ = __getattribute__
cls.__setattr__ = __setattr__
cls.__delattr__ = __delattr__
#This line will be moved when this algorithm will be compatible with inheritance
cls = final(cls)
return cls
return classrebuilder


def setreadonlyattributes(cls, *readonlyattrs):
return readonlyattributes(*readonlyattrs)(cls)




if __name__ == '__main__':
#test readonlyattributes only as an indpendent module
@readonlyattributes('readonlyfield')
class ReadonlyFieldClass(object):
def __init__(self, a, b):
#Prevent initalization of the internal, unmodified PrivateFieldClass
#External PrivateFieldClass can be initalized
self.readonlyfield = a
self.publicfield = b
            



attr = None
def main():
global attr
pfi = ReadonlyFieldClass('forbidden', 'changable')
###---test publicfield, ensure its mutable---###
try:
#get publicfield
print(pfi.publicfield)
print('__getattribute__ works')
#set publicfield
pfi.publicfield = 'mutable'
print('__setattr__ seems to work')
#get previously set publicfield
print(pfi.publicfield)
print('__setattr__ definitely works')
#delete publicfield
del pfi.publicfield
print('__delattr__ seems to work')
#get publicfield which was supposed to be deleted therefore should raise AttributeError
print(pfi.publlicfield)
#publicfield wasn't deleted, raise RuntimeError
raise RuntimeError('__delattr__ doesn\'t work')
except(AttributeError):
print('__delattr__ works')
        

        

try:
###---test readonly, make sure its readonly---###
#get readonlyfield
print(pfi.readonlyfield)
print('__getattribute__ works')
#set readonlyfield, should raise AttributeError
pfi.readonlyfield = 'readonly'
#apparently readonlyfield was set, notify user
raise RuntimeError('__setattr__ doesn\'t work')
except(AttributeError):
print('__setattr__ seems to work')
try:
#ensure readonlyfield wasn't set
print(pfi.readonlyfield)
print('__setattr__ works')
#delete readonlyfield
del pfi.readonlyfield
#readonlyfield was deleted, raise RuntimeError
raise RuntimeError('__delattr__ doesn\'t work')
except(AttributeError):
print('__delattr__ works')
try:
print("Dict testing")
print(pfi.__dict__, type(pfi.__dict__))
attr = pfi.readonlyfield
print(attr)
print("__getattribute__ works")
if pfi.readonlyfield != 'forbidden':
print(pfi.readonlyfield)
raise RuntimeError("__getattr__ doesn't work")
try:
pfi.__dict__ = {}
raise RuntimeError("__setattr__ doesn't work")
except(AttributeError):
print("__setattr__ works")
del pfi.__dict__
raise RuntimeError("__delattr__ doesn't work")
except(AttributeError):
print(pfi.__dict__)
print("__delattr__ works")
print("Basic things work")




main()
        

There is no point to making read only attributes except when your writing 图书馆代码, code which is being distributed to others as code to use in order to enhance their programs, not code for any other purpose, like app development. The __dict__ problem is solved, because the __dict__ is now of the immutable types.MappingProxyType, so attributes cannot be changed through the __dict__. Setting or deleting __dict__ is also blocked. The only way to change read only properties is through changing the methods of the class itself.

Though I believe my solution is better than of the previous two, it could be improved. These are this code's weaknesses:

  1. 不允许在子类中添加设置或删除只读属性的方法。子类中定义的方法被自动禁止访问 readonly 属性,即使通过调用该方法的超类版本也是如此。

  2. 可以更改该类的只读方法以避免只读限制。

However, there is not way without editing the class to set or delete a read only attribute. This isn't dependent on naming conventions, which is good because Python isn't so consistent with naming conventions. This provides a way to make read only attributes that cannot be changed with hidden loopholes without editing the class itself. Simply list the attributes to be read only when calling the decorator as arguments and they will become read only.

这要归功于 Brice 的回答获得了调用方类和方法。

有人提到使用代理对象,我没有看到这样的例子,所以我最终尝试了一下,[很糟糕]。

/! 如果可能,请选择类定义和类构造函数

这段代码有效地重写了 class.__new__(类构造函数) ,除了在各个方面都很糟糕。如果可以的话,请不要使用这种模式。

def attr_proxy(obj):
""" Use dynamic class definition to bind obj and proxy_attrs.
If you can extend the target class constructor that is
cleaner, but its not always trivial to do so.
"""
proxy_attrs = dict()


class MyObjAttrProxy():
def __getattr__(self, name):
if name in proxy_attrs:
return proxy_attrs[name]  # overloaded


return getattr(obj, name)  # proxy


def __setattr__(self, name, value):
""" note, self is not bound when overloading methods
"""
proxy_attrs[name] = value


return MyObjAttrProxy()




myobj = attr_proxy(Object())
setattr(myobj, 'foo_str', 'foo')


def func_bind_obj_as_self(func, self):
def _method(*args, **kwargs):
return func(self, *args, **kwargs)
return _method


def mymethod(self, foo_ct):
""" self is not bound because we aren't using object __new__
you can write the __setattr__ method to bind a self
argument, or declare your functions dynamically to bind in
a static object reference.
"""
return self.foo_str + foo_ct


setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))