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__ 的大小

python进阶五(定制类)5-7 python中__slots__

python限定类属性的类属性:__slots__

python限定类属性的类属性:__slots__