Python 对象属性 - 访问方法

Python object attributes - methodology for access

提问人:Eli Bendersky 提问时间:10/3/2008 最后编辑:userEli Bendersky 更新时间:6/10/2012 访问量:43773

问:

假设我有一个具有某些属性的类。如何最好(在 Pythonic-OOP 中)访问这些属性?就像 ?或者也许写 get 访问器? 这些东西的公认命名风格是什么?obj.attr

编辑:您能否详细说明使用单前导下划线或双前导下划线命名属性的最佳实践?我看到在大多数模块中都使用单个下划线。


如果这个问题已经被问过了(我有一种预感,尽管搜索没有带来结果),请指出它 - 我将关闭这个问题。

Python OOP 对象 属性

评论

0赞 S.Lott 10/3/2008
您说的是属于类(和所有对象)的类级属性还是类中定义的实例级属性?我认为你的问题混淆了类和对象。
0赞 Eli Bendersky 10/4/2008
我的意思是对象属性(实例级),而不是类属性

答:

3赞 monkut 10/3/2008 #1

我认为大多数人只是直接访问它们,不需要获取/设置方法。

>>> class myclass:
...     x = 'hello'
...
>>>
>>> class_inst = myclass()
>>> class_inst.x
'hello'
>>> class_inst.x = 'world'
>>> class_inst.x
'world'

顺便说一句,您可以使用 dir() 函数查看哪些属性/方法附加到您的实例:

>>> dir(class_inst)
['__doc__', '__module__', 'x']

两个前导下划线“__”用于使属性或函数私有化。 有关其他约定,请参阅 PEP 08:http://www.python.org/dev/peps/pep-0008/

1赞 Ignacio Vazquez-Abrams 10/3/2008 #2

Python 不需要从一开始就定义访问器,因为将属性转换为属性既快速又轻松。有关生动的演示,请参见以下内容:

从成瘾中恢复

-2赞 Vasil 10/3/2008 #3

有些人使用 getter 和 setter。根据您使用的编码样式,您可以将它们命名为 getSpam 和 seteggs。但您也可以将属性设置为只读或仅分配。这样做有点尴尬。一种方法是覆盖

> __getattr__

> __setattr__

方法。

编辑:

虽然我的答案仍然是正确的,但这是不对的,正如我逐渐意识到的那样。在 python 中制作访问器有更好的方法,而且不是很尴尬。

评论

0赞 Thomas Wouters 10/3/2008
getattrsetattr 实际上是最不方便的方法。属性和插槽通常更可取。
0赞 Eli Bendersky 10/3/2008
我想使用更简单property
23赞 willurd 10/3/2008 #4

普遍接受的做事方式只是使用简单的属性,就像这样

>>> class MyClass:
...     myAttribute = 0
... 
>>> c = MyClass()
>>> c.myAttribute 
0
>>> c.myAttribute = 1
>>> c.myAttribute
1

如果你发现自己需要能够编写 getter 和 setter,那么你要寻找的是“python 类属性”和 Ryan Tomayko 的文章 Getters/Setters/Fuxors 是一个很好的起点(尽管有点长)

评论

0赞 Eli Bendersky 10/3/2008
感谢您的链接 - 这是一篇与我的问题非常相关的优秀文章。
0赞 Anders Waldenborg 10/3/2008 #5

在 python 中做 getter/setter 没有真正的意义,无论如何你都无法保护东西,如果你在获取/设置属性时需要执行一些额外的代码,请查看内置的 property() (python -c 'help(property)')

8赞 Anders Waldenborg 10/3/2008 #6

编辑:您能详细说明一下使用单前导下划线或双前导下划线命名属性的最佳实践吗?我看到在大多数模块中都使用单个下划线。

单个下划线对 python 来说没有任何特别的意义,它只是最佳实践,告诉“嘿,除非你知道自己在做什么,否则你可能不想访问它”。然而,双下划线使 python 在内部破坏名称,使其只能从定义它的类中访问。

双前导和尾随下划线表示特殊函数,例如使用 + 运算符时调用的函数。__add__

PEP 8 中阅读更多内容,尤其是“命名约定”部分。

58赞 Thomas Wouters 10/3/2008 #7

关于单前下划线和双前导下划线:两者都表示相同的“私密性”概念。也就是说,人们会知道属性(无论是方法还是“正常”数据属性或其他任何属性)不是对象的公共 API 的一部分。人们会知道,直接触摸它就是招致灾难。

最重要的是,双前导下划线属性(但不是单前导下划线属性)的名称被篡改,以便从子类或当前类之外的任何其他地方意外访问它们的可能性较小。您仍然可以访问它们,但不是那么简单。例如:

>>> class ClassA:
...     def __init__(self):
...         self._single = "Single"
...         self.__double = "Double"
...     def getSingle(self):
...         return self._single
...     def getDouble(self):
...         return self.__double
... 
>>> class ClassB(ClassA):
...     def getSingle_B(self):
...         return self._single
...     def getDouble_B(self):
...         return self.__double
... 
>>> a = ClassA()
>>> b = ClassB()

现在,您可以轻松地访问并获取由以下人员创建的属性:a._singleb._single_singleClassA

>>> a._single, b._single
('Single', 'Single')
>>> a.getSingle(), b.getSingle(), b.getSingle_B()
('Single', 'Single', 'Single')

但是尝试直接访问 or 实例上的属性是行不通的:__doubleab

>>> a.__double
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: ClassA instance has no attribute '__double'
>>> b.__double
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: ClassB instance has no attribute '__double'

尽管 中定义的方法可以直接处理它(在任一实例上调用时):ClassA

>>> a.getDouble(), b.getDouble()
('Double', 'Double')

在 上定义的方法不能:ClassB

>>> b.getDouble_B()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in getDouble_B
AttributeError: ClassB instance has no attribute '_ClassB__double'

就在那个错误中,你会得到关于正在发生的事情的提示。在类内访问属性名称时,将对属性名称进行修改,以包含正在访问该属性名称的类的名称。当尝试访问时,它实际上在编译时会变成 的访问,同样,对于 。(如果 中的方法 要分配给 ,为简洁起见,代码中未包含,则它不会触及 ,而是创建一个新属性。此属性没有其他保护,因此,如果您知道正确的名称,您仍然可以直接访问它:__doubleClassAself.__doubleself._ClassA__doubleClassBClassB__doubleClassA__double

>>> a._ClassA__double, b._ClassA__double
('Double', 'Double')

那么为什么这是一个问题呢?

好吧,每当你想继承和更改处理此属性的任何代码的行为时,这都是一个问题。你要么必须重新实现直接接触这个双下划线属性的所有内容,要么你必须猜测类名并手动修改名称。当这个双下划线属性实际上是一个方法时,问题会变得更糟:重写该方法或在子类中调用该方法意味着手动执行名称修改,或者重新实现调用该方法的所有代码以不使用双下划线名称。更不用说动态访问属性了,使用 : 您也必须手动修改。getattr()

另一方面,由于该属性只是微不足道地重写,因此它只能提供表面的“保护”。任何一段代码仍然可以通过手动修改来获取属性,尽管这会使它们的代码依赖于您的类的名称,并且您这边重构代码或重命名类的努力(同时仍然保持相同的用户可见名称,这是 Python 中的常见做法)会不必要地破坏他们的代码。他们还可以通过将他们的类命名为与您的类相同的来“欺骗”Python 为他们进行名称更改:请注意,损坏的属性名称中没有包含模块名称。最后,双下划线属性在所有属性列表和所有形式的内省中仍然可见,这些内省不会注意跳过以()下划线开头的属性。

因此,如果您使用双下划线名称,请非常谨慎地使用它们,因为它们可能会变得非常不方便,并且永远不要将它们用于方法或子类可能想要重新实现、重写或直接访问的任何其他内容。并意识到双前导下划线名称篡改并不能提供真正的保护。最后,使用一个前导下划线可以为你赢得同样多的胜利,并给你带来更少的(潜在的、未来的)痛苦。使用单个前导下划线。

评论

1赞 Eli Bendersky 10/3/2008
谢谢你的精彩回答。我希望我能给你投两次票:-)
0赞 imiric 7/21/2009
这是一个很好的解释。谢谢你的提示!
0赞 Lie Ryan 4/16/2010
很好的答案,除了 Python 中的“getter 和 setter 是邪恶的”。
3赞 Thomas Wouters 4/16/2010
我不确定你在说什么。吸气者和二传手有其目的;这就是为什么我们有.访问控制不是其中之一。property()