无法设置与已删除属性同名的属性

0 投票
2 回答
1633 浏览
提问于 2025-04-17 21:30

我一直在努力理解Python中的@property是怎么回事。我在这个网站上查阅了类似的问题,但还是有些地方搞不清楚它是怎么运作的。比如说下面这个情况:

class MyClass(object):
  def __init__(self):
    self.name = 'x'
    del self.name

  @property
  def name(self):
    return 'y'

my_class = MyClass()
print my_class.name # AttributeError: can't set attribute (self.name = 'x')

为什么这样不行呢?据我理解,你不能用和已有属性同名的属性来设置一个新属性,所以应该在前面加个_,比如在我的例子中用self._name。可是我已经删除了self.name,为什么这还会有影响呢?或者说Python的执行顺序并不是我想的那样?

2 个回答

1

这个属性在调用 __init__ 之前就已经存在了,并不是在 __init__ 之后才出现的。当你尝试执行

self.name = 'x'

时,你会调用 name 属性的 __set__ 方法,但会出现 AttributeError 错误,因为你没有定义一个设置器。

当你调用

del self.name

时,你会调用 name 属性的 __delete__ 方法(如果你真的执行到了那一行)。这也会出现 AttributeError 错误,因为你没有定义一个删除器。

3

任何self.name 的访问在 __init__ 方法中都会和你的属性产生互动。你并没有绕过这个属性。

你的属性没有定义设置器(setter)或删除器(deleter),所以不能给 self.name 赋值,也不能用 del self.name 删除它。

如果你添加了设置器或删除器,那么在 __init__ 中的代码就能正常工作了:

class MyClass(object):
    def __init__(self):
        self.name = 'x'
        del self.name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @name.deleter
    def name(self):
        del self._name

我把属性改成使用一个不同的实例属性,叫做 _name,这样就不会和属性本身冲突了。self._name 不是一个属性,所以你可以直接给它赋值和删除。

更详细的技术解释是,property 对象是一个 描述符对象,而且是特定类型的描述符。它是一个 数据描述符,这意味着它总是在任何实例属性之前被使用。

普通的非数据描述符(比如函数、classmethodstaticmethod 对象)只有一个 __get__ 方法。而数据描述符至少还定义了一个 __set____delete__ 方法。当 Python 查找实例上的属性时,会进行以下步骤:

  1. 在类(及其基类)中查找数据描述符
  2. 在实例上查找属性
  3. 在类(及其基类)中查找属性,包括非数据描述符。

当你尝试访问 self.name 时,实际上是在查找 self 上的一个属性。Python 在类中找到了 name 属性,这就是一个数据描述符。所以这里使用了第一步,调用 MyClass.name.__get__(self, MyClass) 来(间接地)调用你的 name 函数。

当你尝试给 self.name 赋值时,会调用 MyClass.name.__set__(self, newvalue),而对于 del self.name,则会调用 MyClass.name.__delete__(self)。如果你没有在类中定义设置器或删除器,property 对象的默认行为就是抛出一个 AttributeError 错误。

撰写回答