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

wat*_*wer 6 python properties decorator python-3.x python-decorators

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

我正在尝试为属性创建一个简单的getter和setter.我将引导你完成我得到的错误.

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

bob = Person('Bob Smith')
print(bob.name)
Run Code Online (Sandbox Code Playgroud)

这打印出我同意的第一个名字,我没有覆盖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)
Run Code Online (Sandbox Code Playgroud)

一旦我在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
Run Code Online (Sandbox Code Playgroud)

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

修复#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)
Run Code Online (Sandbox Code Playgroud)

这很好用,因为它正确打印输出.

修复#2:将 属性名称更改为to name(self),_name(self)并将变量名称从中恢复 _namename

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)
Run Code Online (Sandbox Code Playgroud)

现在,这可以按预期打印.

作为下一个步骤,我创建setter,getterdeleter使用装饰性质.它们遵循如上所述的类似命名约定 - 即_变量名称的前缀或方法名称:

@_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
Run Code Online (Sandbox Code Playgroud)

问题:我不确定为什么Python 3.x强制添加_变量名称或方法名称.

按照公共getter和setter方法私有财产的Python,什么是蟒蛇的区别在前面加上下划线属性和背部,并https://www.python.org/dev/peps/pep-0008/#naming-conventions,一下划线前缀是用户的一个弱指示,即该变量是一个私有变量,但是没有额外的机制(通过Python,类似于Java所做的)来检查或纠正这种行为.

所以,现在最大的问题是,为什么我需要使用属性的下划线?我相信那些下划线前缀只是让用户知道这是一个私有变量.


我正在使用Lutz的书来学习Python,上面的例子来自他的书.

Pat*_*ner 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)
Run Code Online (Sandbox Code Playgroud)
  • 您定义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()
Run Code Online (Sandbox Code Playgroud)

它还会调用您的 @property 方法,您可以通过'fetch...'输出进行检查。因此,每次调用都yourclassinstance.name将通过 @property 访问器 - 这就是为什么你不能将self.name“变量”与它一起使用。

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

这是纯粹的观察 - 如果您想了解到底发生了什么,您必须检查@property实施情况。

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


正如评论中指出的,使用 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
Run Code Online (Sandbox Code Playgroud)

用法:

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)
Run Code Online (Sandbox Code Playgroud)

输出:

# 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']
Run Code Online (Sandbox Code Playgroud)