以下划线开头的变量用于属性装饰器

2024-02-07

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

我正在尝试为属性创建一个简单的 getter 和 setter。我将引导您解决我遇到的错误。

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

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

这打印了我同意我没有覆盖的第一个名字print or getattribute方法。而且,这里没有房产。这是为了测试基本代码是否有效。

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

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

现在,我研究了这个主题,发现有两个修复(不知道为什么会这样):

Fix #1:改变变量name to _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)

这很有效,因为它可以正确打印输出。

Fix #2:将属性名称更改为 fromname(self) to _name(self)并恢复变量名称_name to 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)

现在,该作品按预期打印。

作为下一步,我创建了setter, getter, and deleter使用装饰器的属性。它们遵循如上所述的类似命名约定,即任一前缀_变量名或方法名:

@_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 强制添加_变量名或方法名。

As per 具有公共 getter 和私有 setter 的 Python 属性 https://stackoverflow.com/questions/42137210/python-property-with-public-getter-and-private-setter, python属性前后带下划线有什么区别 https://stackoverflow.com/questions/14671487/what-is-the-difference-in-python-attributes-with-underscore-in-front-and-back, and https://www.python.org/dev/peps/pep-0008/#naming-conventions https://www.python.org/dev/peps/pep-0008/#naming-conventions,下划线前缀对用户来说是一个弱指示,表明该变量是私有变量,但没有额外的机制(通过 Python,类似于 Java 所做的)来检查或纠正这种行为。

所以,眼前的一个大问题是,为什么我需要使用下划线来处理属性?我相信这些下划线前缀只是为了让用户知道这是一个私有变量。


我正在使用Lutz的书来学习Python,上面的例子是受到他的书的启发。


让我们使用修复 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“变量”与它一起。

如果您访问self.name从内部def name(self)- 你收到一个循环调用 - 因此:堆栈溢出.

这是纯粹的观察 - 如果你想看看到底发生了什么,你必须检查@property执行。

您可以在这里更深入地了解这些主题:

  • Python 属性如何工作? https://stackoverflow.com/questions/6193556/how-do-python-properties-work
  • @property 装饰器在 Python 中如何工作? https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work
  • 使用 getter 和 setter 的 Python 方式是什么? https://stackoverflow.com/questions/2627002/whats-the-pythonic-way-to-use-getters-and-setters

正如评论中指出的,使用 getter/setter 是一种反模式unless他们实际上做一点事:

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

Usage:

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)

Output:

# 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']
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以下划线开头的变量用于属性装饰器 的相关文章

随机推荐