以下划线开头的变量作为属性装饰器 [英] Variables starting with underscore for property decorator

查看:44
本文介绍了以下划线开头的变量作为属性装饰器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Python的新手.因此,如果这是一个基本问题,请原谅我.我在Internet和SO上对此主题进行了研究,但找不到解释.我正在使用Anaconda 3.6发行版.

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

  class Person:def __init __(self,name):self.name =名称鲍勃=人('鲍勃·史密斯')打印(bob.name) 

这将打印我同意没有覆盖 print getattribute 方法的名字.另外,这里没有财产.这是为了测试基本代码是否有效.

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

  class Person:def __init __(self,name):self.name =名称@财产def名称(自己):名称属性文档"打印('获取...')返回self.name鲍勃=人('鲍勃·史密斯')打印(bob.name) 

我在PyCharm中编写以上代码后,立即得到一个黄色的灯泡图标,说明该变量必须是私有的.我不明白这个理由.

忽略上面的代码,如果我运行上面的代码,则会得到:

 跟踪(最近一次通话最近):文件"C:\ ...,在run_code中exec(code_obj,self.user_global_ns,self.user_ns)文件< ipython-input-25-62e9a426d2a9>",< module>中的第2行bob = Person('Bob Smith')File< ipython-input-24-6c55f4b7326f>",第__init__行,第4行self.name = name AttributeError:无法设置属性 

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

修复#1::将变量 name 更改为 _name

  class Person:def __init __(self,name):self._name =名称#将名称更改为_name@财产def名称(自己):名称属性文档"打印('获取...')返回self._name#将名称更改为_name鲍勃=人('鲍勃·史密斯')打印(bob.name) 

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

修复#2::将属性名称从 name(self)更改为 _name(self),并将变量名称从还原_name name

  class Person:def __init __(self,name):self.name =名称#更改为名称@财产def _name(self):#更改为_name名称属性文档"打印('获取...')返回self.name#已更改为name鲍勃=人('鲍勃·史密斯')打印(bob.name) 

现在,可以正常打印了.

下一步,我使用装饰器创建了 setter getter deleter 属性.它们遵循上述类似的命名约定-即在变量名或方法名前加上 _ 前缀:

  @_ name.setterdef _name(self,value):名称属性设置器"打印('更改...')self.name =值@ _name.deleterdef _name(self):打印('删除')del self.name鲍勃=人('鲍勃·史密斯')打印(bob.name)bob.name ='鲍比·史密斯'打印(bob.name)del bob.name 

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

根据具有公共获取器和私有设置器的Python属性有什么区别在python属性中加上前后划线 https://www.python.org/dev/peps/pep-0008/#naming-conventions ,下划线前缀对用户来说是一个较弱的指示,表明该变量是私有变量,但是没有额外的机制就位(通过Python,类似于Java所做的)来检查或纠正这种行为.

因此,当前的主要问题是,为什么在使用属性时需要加下划线?我相信这些下划线前缀只是让用户知道这是一个私有变量.


我正在使用Lutz的书来学习Python,上面的示例是从他的书中得到启发的.

解决方案

让我们修改代码1:

  class Person:def __init __(self,name):self._name =名称#将名称更改为_name@财产def名称(自己):名称属性文档"打印('获取...')返回self._name#将名称更改为_name鲍勃=人('鲍勃·史密斯')打印(bob.name) 

  • 您定义 self._name = name -这就是您的支持字段.
  • 您定义方法 def name(self)-并将其赋予 @property .
  • 您通过 bob = Person('Bob Smith')
  • 创建类的实例

然后您执行 print(bob.name)-您在这里叫什么?

您的变量称为 self._name -和"non-property"方法将由 bob.name()调用..为什么 bob.name 仍然有效-由@property装饰器完成.

如果您定义以下内容,会发生什么事情

:

  def tata(self):print(self.name)#self.name之后也没有()鲍勃=人('鲍勃·史密斯')bob.tata() 

它还将调用@property方法,您可以通过'fetch ...'输出进行检查.因此,每次 yourclassinstance.name 的调用都将通过@property访问器进行-这就是为什么您不能拥有 self.name "variable"变量的原因.和它在一起.

如果您从 def name(self)内部访问 self.name ,则会收到循环调用-因此:堆栈溢出./p>

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

您可以在此处对主题进行更多的了解:


正如评论中指出的那样,使用getters/setters是一种反模式除非他们实际上是做某事:

  class Person:"用于执行某项操作的属性和二传手/deleter的傻例子.def __init __(self,name):self._name = name#通过直接设置来绕过名称设置器self._name_access_counter = 0self._name_change_counter = 0self._name_history = [名称]@财产def名称(自己):"计数任何访问并返回名称+计数".self._name_access_counter + = 1返回f'{self._name}({self._name_access_counter})'@ name.setterdef名称(自身,值):"仅允许3个名称更改,并将名称强制为大写.如果值== self._name:返回new_value = str(值).upper()如果self._name_change_counter<3:self._name_change_counter + = 1print(f'({self._name_change_counter}/3个更改:{self._name} => {new_value}')self._name_history.append(new_value)self._name = new_value别的:print(f不允许更改:{self._name} => {new_value}未设置!")@ name.deleterdef名称(自己):"del"的滥用-出于示例目的重置计数器/历史记录.self._name_access_counter = 0self._name_change_counter = 0self._name_history = self._name_history [:1]#保留初始名称self._name = self._name_history [0]#重置为初始名称打印(已删除的历史记录并重置更改")@财产def历史(自己):返回self._name_history 

用法:

  p = Person("Maria")打印(列表(_的p.name在范围(5)中)在["Luigi","Mario",42,"King"]中的名称:p.name =名称print(p.name)#计数器将对ANY进行计数打印(p.history)del(p.name)打印(p.name)打印(p.history) 

输出:

 #获得5次并打印为列表['Maria(1)','Maria(2)','Maria(3)','Maria(4)','Maria(5)']#尝试更改4次(1/3的变化:玛丽亚=>路易吉路易吉(6)(2/3更改:LUIGI => MARIO马里奥(7)(3/3更改:MARIO => 4242(8)不允许更改:42 =>KING没有设置!42(9)#到目前为止的打印历史['Maria','LUIGI','MARIO','KING']#删除后删除名称,打印名称和历史记录删除历史记录并重置更改玛丽亚(1)['Maria'] 

I am new to Python. So, please forgive me if this is a basic question. I researched this topic on the Internet and SO, but I couldn't find an explanation. I am using Anaconda 3.6 distribution.

I am trying to create a simple getter and setter for an attribute. I will walk you through the errors I get.

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

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

This prints the first name I agree that I haven't overridden print or getattribute method. Also, there is no property here. This was to test whether the basic code works.

Let's modify the code to add property:

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)

As soon as I write above code in PyCharm, I get a yellow bulb icon, stating that the variable must be private. I don't understand the rationale.

Ignoring above, if I run above code, I get:

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

Now, I researched this topic, and I found that there are two fixes (without knowing why this works):

Fix #1: Change the variable 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)

This works well in that it prints the output correctly.

Fix #2: Change property name to from name(self) to _name(self) and revert variable name from _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)

Now, this works prints as expected.

As a next step, I created setter, getter, and deleter properties using decorators. They follow similar naming conventions as described above--i.e. either prefix _ to the variable name or the method name:

@_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

Question: I am not really sure why Python 3.x is enforcing adding _ to variable name or method name.

As per Python property with public getter and private setter, What is the difference in python attributes with underscore in front and back, and https://www.python.org/dev/peps/pep-0008/#naming-conventions, an underscore prefix is a weak indicator to the user that this variable is a private variable, but there is no extra mechanism in place (by Python, similar to what Java does) to check or correct such behavior.

So, the big question at hand is that why is it that I need to have underscores for working with properties? I believe those underscore prefixes are just for users to know that this is a private variables.


I am using Lutz's book to learn Python, and above example is inspired from his book.

解决方案

Lets take your code Fix 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)

  • You define self._name = name - thats your backing field.
  • You define a method def name(self) - and attribute it with @property.
  • You create an instance of your class by bob = Person('Bob Smith')

Then you do print(bob.name) - what are you calling here?

Your variable is called self._name - and a "non-property" method would be called by bob.name() .. why does bob.name still work - its done by the @property decorator.

What happens if you define:

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

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

It will also call your @property method as you can inspect by your 'fetch...' output. So each call of yourclassinstance.name will go through the @property accessor - thats why you can not have a self.name "variable" together with it.

If you access self.name from inside def name(self) - you get a circular call - hence: stack overflow.

This is pure observation - if you want to see what exactly happens, you would have to inspect the @property implementation.

You can get more insight into the topics here:


As pointed out in the comment, using getters/setters is an anti-pattern unless they actually do something:

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']

这篇关于以下划线开头的变量作为属性装饰器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆