问题:Python只读属性
我不知道何时属性应该是私有的,是否应该使用属性。
我最近读到,setter和getters不是pythonic,我应该使用属性装饰器。没关系。
但是,如果我有属性,该属性不能从类外部设置,而是可以读取的(只读属性)。这个属性应该是私有的self._x
吗?我所说的私有是指下划线吗?如果是,那么不使用getter怎么读?我现在知道的唯一方法是写
@property
def x(self):
return self._x
这样我就可以读取属性,obj.x
但是我无法设置它,obj.x = 1
所以很好。
但是,我真的应该在乎设置不应该设置的对象吗?也许我应该离开它。但是话又说回来,我不能使用下划线,因为阅读obj._x
对于用户来说很奇怪,所以我应该使用下划线obj.x
,然后用户又一次不知道他一定不能设置该属性。
您的看法和做法是什么?
回答 0
通常,在编写Python程序时应假定所有用户都同意成年人,因此他们有责任自己正确使用事物。但是,在极少数情况下,无法设置属性(例如派生值或从某个静态数据源读取的值)就没有意义,仅使用吸气剂的属性通常是首选模式。
回答 1
西拉斯·雷(Silas Ray)只是我的两分钱,走在正确的轨道上,但是我觉得自己想举个例子。;-)
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
回答 2
这是一种避免假设的方法
所有使用者都是成年人,因此有责任自行正确使用事物。
请在下面查看我的更新
使用@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
使用
没有下划线:这是一个公共变量。
一个下划线:这是一个受保护的变量。
有两个下划线:这是一个私有变量。
除了最后一个,这是一个约定。如果您确实努力尝试,仍然可以使用双下划线访问变量。
那么我们该怎么办?我们是否放弃使用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
在您的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
回答 3
这是一种对只读属性略有不同的方法,由于必须对它们进行初始化,因此应该将它们称为一次写入属性,不是吗?对于那些担心直接通过访问对象字典来修改属性的偏执狂,我引入了“极端”名称处理:
from uuid import uuid4
class Read_Only_Property:
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 = Read_Only_Property('x')
y = Read_Only_Property('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)
回答 4
我对创建只读属性的前两个答案不满意,因为第一个解决方案允许删除readonly属性,然后进行设置,并且不会阻止__dict__。第二种解决方案可以与测试一起解决-找到等于您将其设置为2的值并最终进行更改。
现在,获取代码。
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()
除非您编写库代码时使用只读属性,否则将这些属性设置为只读代码是为了增强他们的程序而将代码分发给其他人使用,而不是用于其他目的(例如应用程序开发)的代码。解决了__dict__问题,因为__dict__现在是不可变的类型。MappingProxyType,因此无法通过__dict__更改属性。设置或删除__dict__也被阻止。更改只读属性的唯一方法是更改类本身的方法。
尽管我认为我的解决方案比前两个解决方案要好,但可以改进。这些是此代码的弱点:
a)不允许在子类中添加设置或删除只读属性的方法。即使调用了超类的方法,也会自动禁止子类中定义的方法访问只读属性。
b)可以更改类的只读方法以克服只读限制。
但是,没有办法不编辑类来设置或删除只读属性。这不依赖于命名约定,这很好,因为Python与命名约定不太一致。这提供了一种方法,使只读属性无法通过隐藏的漏洞进行更改,而无需编辑类本身。只需在将装饰器作为参数调用时列出要只读的属性即可,它们将变为只读。
归功于Brice的回答:如何在python中另一个类的函数中获取调用方类名称?获取调用方的类和方法。
回答 5
注意,实例方法也是(类的)属性,如果您确实想成为坏蛋,则可以在类或实例级别设置它们。或者,您可以设置一个类变量(这也是该类的一个属性),在该变量中,方便的只读属性将无法立即使用。我要说的是,“只读属性”问题实际上比通常认为的要普遍得多。幸运的是,人们对工作的传统期望是如此强烈,以至于使我们在其他情况下视而不见(毕竟,几乎所有东西都是python中的某种属性)。
基于这些期望,我认为最通用,最轻便的方法是采用以下约定:“公开”(无前导下划线)属性是只读的,除非明确记录为可写。这包含了通常的期望,即不会对方法进行修补,而指示实例默认值的类变量则更不用说了。如果您真的对某些特殊属性感到偏执,请使用只读描述符作为最后的资源度量。
回答 6
尽管我喜欢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()
回答 7
那是我的解决方法。
@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
回答 8
有人提到使用代理对象,但我没有看到这样的示例,所以我最终尝试了一下,[可怜]。
/!\如果可能,请更喜欢类定义和类构造函数
这段代码可以有效地重写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))
回答 9
我知道我从头开始带回了这个线程,但是我正在研究如何使属性变为只读,并且在找到该主题之后,我对已经共享的解决方案不满意。
因此,如果您从以下代码开始,请回到最初的问题:
@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"