【问题标题】:Variables starting with underscore for property decorator属性装饰器下划线开头的变量
【发布时间】:2018-12-22 19:20:45
【问题描述】:

我是 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 property with public getter and private setterWhat is the difference in python attributes with underscore in front and backhttps://www.python.org/dev/peps/pep-0008/#naming-conventions,下划线前缀对于用户来说是一个弱指标,表明此变量是私有变量,但没有额外的机制到位(通过 Python,类似于Java 做了什么)来检查或纠正这种行为。

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


我正在使用 Lutz 的书来学习 Python,以上示例的灵感来自他的书。

【问题讨论】:

  • 如果你有一个名为foo的属性,你认为当它的实现访问foo时会发生什么?
  • 为什么你会同时拥有一个“公共”属性和一个getter/setter?如果您不需要额外的逻辑,我只会使用该属性并访问它。如果您有其他逻辑,我会将其创建为“私有”并将 getter/setter 作为封装我的附加逻辑的入口点。我永远不会将 both 呈现给外部,这只是令人困惑,并提供了一种从外部规避我的 getter/setter 逻辑的方法。这与创建它们的初衷背道而驰……您为什么要这样做?
  • @PatrickArtner - 我不知道为什么普通的 name 变量或方法不起作用。事实上,如果我删除所有_,那么代码会编译,但我会得到 stackoverflow 错误。我真的不确定。我在 Internet 和 Lutz 的书中看到的所有示例都使用以 _ 作为前缀的变量或以 _ 作为前缀的方法。我不知道为什么。
  • 下划线没有什么特别之处。编辑可能会建议它,但 python 本身并不关心。正在发生的事情是“名称”访问将获取所谓的“名称”。如果那是财产,那就太好了。如果该属性然后尝试访问“名称”,它会命中任何名称。即本身。该属性需要自行完全计算。或者它需要访问其他一些实例变量。可能是“NAME”,大写。但是,Python conventions 通常说 _ 表示私有,因此 _name 是一个很好的缓存。编辑警告说这是个傻瓜,但也太规范了。
  • 在 Chandler(一个失败的开源 PIM)时代,有人写了一篇文章 Python is not Java- getter 和 setter 是 Python 的反模式,除非他们主动某事。这可能是从所有奥斯卡奖得主中随机选择一个名字。它可能会阻止名称被更改。但是直接获取/设置本身没有什么理由,包括离开公开课以进行进一步的私人修改

标签: python python-3.x properties decorator python-decorators


【解决方案1】:

让您的代码修复 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 实现。

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


正如评论中所指出的,使用 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']

【讨论】:

    猜你喜欢
    • 2018-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-26
    • 1970-01-01
    • 2012-03-24
    • 2013-09-26
    • 1970-01-01
    相关资源
    最近更新 更多