python、__slots__ 和“属性是只读的”
Posted
技术标签:
【中文标题】python、__slots__ 和“属性是只读的”【英文标题】:python, __slots__, and "attribute is read-only" 【发布时间】:2021-05-07 20:13:27 【问题描述】:我想在 python 中创建一个具有一些属性的对象,并且我想保护自己免于意外使用错误的属性名称。代码如下:
class MyClass( object ) :
m = None # my attribute
__slots__ = ( "m" ) # ensure that object has no _m etc
a = MyClass() # create one
a.m = "?" # here is a PROBLEM
但是在运行这个简单的代码之后,我得到了一个非常奇怪的错误:
Traceback (most recent call last):
File "test.py", line 8, in <module>
a.m = "?"
AttributeError: 'test' object attribute 'm' is read-only
有没有聪明的程序员可以抽出一点时间来告诉我“只读”错误?
【问题讨论】:
这似乎是一个不必要的用例。你为什么做这个?你为什么不使用属性? 我想证明自己不要用错字写一个属性名称,例如,如果我写 object.my_prperty = 1 而不是 object.my_property = 1(错过了 'o'),python 将不会显示任何错误,但我的程序中会出现逻辑错误。所以我想将一个属性限制为我的预定义集,所以只有它们可以被访问。我如何用属性解决这个问题? 通常在 python 中,我们接受作业中拼写错误的可能性,并编写好的测试来捕捉表面的和更多的语义错误。我谦虚地建议,如果你想写 python,你应该考虑用 python 的方式来做,而不是和语言打架。我要补充一点,在过去的 9 年中,我的 python 代码中的拼写错误使我可能花费了 20 分钟。 您也可以查看 Python IDE。我通常使用 IDLE,其中(在 shell 中,而不是在 .py 文件中)你可以这样做:a=MyClass(),然后键入 a。它会告诉你所有的属性。我还使用了 PyDev,它是 Eclipse 的 Python 插件,它甚至可以在 .py 文件中执行此操作。那里还有很多其他人。谷歌或在这个网站上搜索“Python IDE”,我相信你会找到你喜欢的。 错误应该是AttributeError: 'MyClass' object attribute 'm' is read-only
【参考方案1】:
当您使用__slots__
声明实例变量时,Python 会创建一个descriptor object 作为具有相同名称的类变量。在您的情况下,此描述符被您在以下行定义的类变量 m
覆盖:
m = None # my attribute
这里是你需要做的:不要定义一个名为m
的类变量,并在__init__
方法中初始化实例变量m
。
class MyClass(object):
__slots__ = ("m",)
def __init__(self):
self.m = None
a = MyClass()
a.m = "?"
附带说明,具有单个元素的元组在元素后需要一个逗号。两者都可以在您的代码中使用,因为 __slots__
接受单个字符串或字符串的可迭代/序列。通常,要定义包含元素 1
的元组,请使用 (1,)
或 1,
而不是 (1)
。
【讨论】:
你知道为什么如果我写了:slots = [ "m" ] m = 5 Python 会用变量覆盖描述符对象并且不会使用描述符对象的 set()方法来设置值吗? 因为 m 在这个上下文中是一个类变量,而不是一个实例变量。为了利用描述符的 set() 方法,您应该分配给对象的 m,而不是类。如果要初始化对象的实例变量,请在 init 方法中进行,就像我在代码 sn-p 中所做的那样。 文档中的密钥(顺便说一句移动了here)是__slots__ are implemented at the class level by creating descriptors for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment
。那么会发生什么(无论定义 m
和 __slots__
的顺序如何?)当尝试分配时,set 方法不再映射到任何东西 - 也不是 get 但它回退到 MyClass.m【参考方案2】:
你完全在滥用__slots__
。它阻止为实例创建__dict__
。这仅在您遇到许多小对象的内存问题时才有意义,因为摆脱 __dict__
可以减少占用空间。这是一项核心优化,99.9% 的情况下都不需要。
如果您需要您描述的那种安全性,那么 Python 确实是错误的语言。最好使用像 Java 这样严格的东西(而不是尝试用 Python 编写 Java)。
如果您自己无法弄清楚为什么类属性会在您的代码中导致这些问题,那么也许您应该三思而后行地引入这样的语言技巧。首先更熟悉该语言可能会更明智。
为了完整起见,这里是documentation link for slots。
【讨论】:
+1 表示完整性。正如到处提到的那样,slots 不是一个坏主意,但需要小心使用。它不仅仅是一项安全检查,而且会增加实现的复杂性,需要注意您如何使用对象。【参考方案3】:__slots__
适用于实例变量,而您所拥有的是类变量。你应该这样做:
class MyClass( object ) :
__slots__ = ( "m", )
def __init__(self):
self.m = None
a = MyClass()
a.m = "?" # No error
【讨论】:
【参考方案4】:考虑一下。
class SuperSafe( object ):
allowed= ( "this", "that" )
def __init__( self ):
self.this= None
self.that= None
def __setattr__( self, attr, value ):
if attr not in self.allowed:
raise Exception( "No such attribute: %s" % (attr,) )
super( SuperSafe, self ).__setattr__( attr, value )
更好的方法是使用单元测试进行这种检查。这是相当大的运行时开销。
【讨论】:
但与 slots 相比,这是更多的代码行吗?为什么要手动检查语言可以自动执行的操作。 为了避免“非常奇怪的错误”消息并避免花费大量时间调试 slots 的内部奥秘。我更喜欢明显而简单的解决方案,其中不需要秘密知识即可调试。 无法添加新的实例变量是 slots 的副作用,而不是目的。 (这是一种性能优化)。 @Ravi — 我认为这是问题的核心。【参考方案5】:class MyClass( object ) :
m = None # my attribute
这里的m
是类属性,而不是实例属性。您需要在__init__
中自行将其与您的实例连接。
【讨论】:
以上是关于python、__slots__ 和“属性是只读的”的主要内容,如果未能解决你的问题,请参考以下文章
Python:__slots__()方法和@property方法
了解 Python 3.7 中类、namedtuple 和 __slots__ 的大小