以下划线开头的变量用于属性修饰符的作用是什么?

12

我是Python的新手,如果这是一个基础问题,请原谅我。我在互联网和SO上研究了这个主题,但是我找不到解释。我正在使用Anaconda 3.6发行版。

我想为属性创建一个简单的getter和setter。我将向您展示我遇到的错误。

class Person:
    def __init__(self,name):
        self.name=name

bob = Person('Bob Smith')
print(bob.name)

这将打印出第一个名字。我同意我没有覆盖printgetattribute方法。另外,这里没有属性。这是为了测试基本代码是否正常工作。

让我们修改代码以添加属性:

class Person:
    def __init__(self,name):
        self.name=name

    @property
    def name(self):
        "name property docs"
        print('fetch...')
        return self.name


bob = Person('Bob Smith')
print(bob.name)

在PyCharm中编写上述代码后,我会得到一个黄色的小灯泡图标,提示变量必须是私有的。我不明白这个要求的原因。

如果忽略上述警告运行代码,将会得到以下结果:

Traceback (most recent call last):   File "C:\..., in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)   File "<ipython-input-25-62e9a426d2a9>", line 2, in <module>
    bob = Person('Bob Smith')   File "<ipython-input-24-6c55f4b7326f>", line 4, in __init__
    self.name=name AttributeError: can't set attribute

现在,我研究了这个话题,发现有两种解决方法(不知道为什么有效):

解决方法 #1: 将变量name更改为_name

class Person:
    def __init__(self,name):
        self._name=name #Changed name to _name

    @property
    def name(self):
        "name property docs"
        print('fetch...')
        return self._name #Changed name to _name


bob = Person('Bob Smith')
print(bob.name)

这个做法很好,它可以正确地输出结果。

修复方法2:将属性名从name(self)改为_name(self),并将变量名从_name恢复为name

class Person:
    def __init__(self,name):
        self.name=name #changed to name

    @property
    def _name(self): #Changed to _name
        "name property docs"
        print('fetch...')
        return self.name #changed to name


bob = Person('Bob Smith')
print(bob.name)

现在,这个代码像预期的那样打印出结果。

接下来,我使用装饰器创建了settergetterdeleter属性。它们遵循与上述类似的命名约定——即在变量名或方法名前缀添加_

@_name.setter
def _name(self,value):
    "name property setter"
    print('change...')
    self.name=value

@_name.deleter
def _name(self):
    print('remove')
    del self.name


bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Bobby Smith'
print(bob.name)
del bob.name

问题:我不太确定为什么Python 3.x要强制在变量名或方法名中添加_
根据Python属性公共getter和私有setterPython带下划线前后的属性有什么区别https://www.python.org/dev/peps/pep-0008/#naming-conventions,下划线前缀是向用户表明这个变量是私有变量的一种弱指示符,但没有额外的机制(类似于Java所做的)来检查或纠正这种行为。
因此,现在的关键问题是为什么我需要在属性中使用下划线?我认为这些下划线前缀只是为了让用户知道这是一个私有变量。
我正在使用Lutz的书学习Python,上面的例子是受他的书启发的。

如果您有一个名为foo的属性,当其实现访问foo时,您认为会发生什么? - Ignacio Vazquez-Abrams
@PatrickArtner - 我不知道为什么普通的“name”变量或方法不起作用。实际上,如果我删除所有“_”,那么代码将编译,但我会得到stackoverflow错误。我真的不确定。我在互联网和Lutz的书中看到的所有示例都使用具有“_”作为前缀的变量或具有“_”作为前缀的方法。我不确定为什么。 - watchtower
考虑Ignacio的评论。如果foo调用foo调用foo调用foo调用foo调用foo调用foo调用foo ad infinum(或直到堆栈溢出...)-您必须深入了解在函数上放置@property作为装饰器的操作。 - Patrick Artner
1
下划线并没有什么特别的含义。编辑器可能会建议使用,但Python本身并不关心。发生的情况是,“name”访问将获取所谓的“name”。如果那是属性,那就太好了。如果该属性然后尝试访问“name”,它会命中任何名称。即它自己。该属性需要完全独立计算。或者它需要访问某些其他实例变量。也许是“NAME”,大写。然而,Python约定通常表示_表示私有,因此_name是一个很好的缓存。编辑器警告有错误,但也过于规范化。 - JL Peyret
1
在 Chandler(一个失败的开源 PIM)时代,有人写了一篇文章《Python 不是 Java》——getter 和 setter 是 Python 的反模式,除非它们积极地 些什么。这可能是从所有奥斯卡奖获得者中随机选择一个名字,也可能是防止名称被更改。但是,一个简单的 get/set 操作本身很难自圆其说,包括为进一步的私有修改留下开放类。 - JL Peyret
显示剩余2条评论
1个回答

5

让我们来看一下你的代码修复1:

class Person:
    def __init__(self,name):
        self._name=name #Changed name to _name

    @property
    def name(self):
        "name property docs"
        print('fetch...')
        return self._name #Changed name to _name

bob = Person('Bob Smith')
print(bob.name)
  • 你定义了 self._name = name - 这是你的后备字段。
  • 你定义了一个方法 def name(self) - 并将其属性与 @property 关联。
  • 你通过 bob = Person('Bob Smith') 创建了一个类的实例。

然后你执行 print(bob.name) - 你在这里调用什么?

你的变量被称为 self._name - "非属性" 方法可以通过 bob.name() 调用..为什么 bob.name 仍然有效 - 这是由 @property 装饰器完成的。

如果你定义:

def tata(self):
    print(self.name)  # also no () after self.name

bob = Person('Bob Smith') 
bob.tata()

它还会调用您的@property方法,您可以通过'fetch...'输出进行检查。因此,每次调用yourclassinstance.name都将通过@property访问器 - 这就是为什么您不能与其一起使用self.name“变量”的原因。
如果您从def name(self)内部访问self.name,则会得到循环调用 - 因此:堆栈溢出
这是纯观察 - 如果要查看发生了什么,您需要检查@property实现。
您可以在此处深入了解以下主题:
- Python属性如何工作? - @property装饰器在Python中如何工作? - 使用getter和setter的Pythonic方式是什么?
正如评论中指出的那样,除非getter/setter确实有所作为,否则使用它们是一个反模式。
class Person:
    """Silly example for properties and setter/deleter that do something."""
    def __init__(self,name):
        self._name = name  # bypass name setter by directly setting it
        self._name_access_counter = 0
        self._name_change_counter = 0
        self._name_history = [name]

    @property
    def name(self):
        """Counts any access and returns name + count"""
        self._name_access_counter += 1
        return f'{self._name} ({self._name_access_counter})'

    @name.setter
    def name(self, value):
      """Allow only 3 name changes, and enforce names to be CAPITALs"""
      if value == self._name:
        return
      new_value = str(value).upper()
      if self._name_change_counter < 3:
        self._name_change_counter += 1
        print(f'({self._name_change_counter}/3 changes: {self._name} => {new_value}')
        self._name_history.append(new_value)
        self._name = new_value
      else:
        print(f"no change allowed: {self._name} => {new_value} not set!")

    @name.deleter
    def name(self):
        """Misuse of del - resets counters/history for example purposes"""
        self._name_access_counter = 0
        self._name_change_counter = 0
        self._name_history = self._name_history[:1]  # keep initial name
        self._name = self._name_history[0] # reset to initial name
        print("deleted history and reset changes")

    @property
    def history(self):
      return self._name_history

使用方法:

p = Person("Maria")

print(list(p.name for _ in range(5)))

for name in ["Luigi", "Mario", 42, "King"]:
  p.name = name
  print(p.name)  # counter will count ANY get access
  
print(p.history)
del (p.name)
print(p.name)
print(p.history)

输出:

# get 5 times and print as list
['Maria (1)', 'Maria (2)', 'Maria (3)', 'Maria (4)', 'Maria (5)']

# try to change 4 times
(1/3 changes: Maria => LUIGI
LUIGI (6)
(2/3 changes: LUIGI => MARIO
MARIO (7)
(3/3 changes: MARIO => 42
42 (8)
no change allowed: 42 => KING not set!
42 (9)

# print history so far
['Maria', 'LUIGI', 'MARIO', 'KING']

# delete name, print name and history after delete
deleted history and reset changes
Maria (1)
['Maria']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接