名称以两个下划线开头的实例属性被奇怪地重命名 [英] Instance attribute that has a name starting with two underscores is weirdly renamed
问题描述
使用我的类的当前实现,当我尝试使用类方法获取私有属性的值时,我得到None
作为输出。你知道我哪里错了吗?
代码
from abc import ABC, abstractmethod
class Search(ABC):
@abstractmethod
def search_products_by_name(self, name):
print('found', name)
class Catalog(Search):
def __init__(self):
self.__product_names = {}
def search_products_by_name(self, name):
super().search_products_by_name(name)
return self.__product_names.get(name)
x = Catalog()
x.__product_names = {'x': 1, 'y':2}
print(x.search_products_by_name('x'))
推荐答案
此代码中发生了什么?
上面的代码看起来很好,但有一些行为可能看起来不寻常。如果我们在交互式控制台中键入以下内容:
c = Catalog()
# vars() returns the instance dict of an object,
# showing us the value of all its attributes at this point in time.
vars(c)
则结果如下:
{'_Catalog__product_names': {}}
这太奇怪了!在我们的类定义中,我们没有给任何属性命名为_Catalog__product_names
。我们命名了一个属性__product_names
,但该属性似乎已被重命名。
发生什么事
这种行为不是错误--它实际上是一种称为private name mangling的Python特性。对于在类定义中定义的所有属性,如果属性名称以两个前导下划线开头-而不是以两个尾随下划线结尾-则属性将重命名为如下所示。类Bar
中名为__foo
的属性将重命名为_Bar__foo
;类Breakfast
中名为__spam
的属性将重命名为_Breakfast__spam
;依此类推。
仅当您尝试从类外部访问属性时才会发生名称损坏。类中的方法仍然可以使用您在__init__
中定义的私有名称访问该属性。
您为什么想要这个?
我个人从未找到此功能的用例,并对此表示怀疑。它的主要用例是这样的情况:您希望方法或属性在类内可以私有访问,但不能由类外部的函数或从该类继承的其他类以相同的名称访问。
这里有一个很好的YouTube talk,其中包括以下几个用例 此功能大约需要34分钟。
(注:YouTube Talk是2013年的,Talk中的示例是用python2编写的,所以示例中的一些语法与现代的python略有不同-print
仍然是一条语句,而不是一个函数等)
下面是使用类继承时私有名称损坏如何工作的图示:
>>> class Foo:
... def __init__(self):
... self.__private_attribute = 'No one shall ever know'
... def baz_foo(self):
... print(self.__private_attribute)
...
>>> class Bar(Foo):
... def baz_bar(self):
... print(self.__private_attribute)
...
>>>
>>> b = Bar()
>>> b.baz_foo()
No one shall ever know
>>>
>>> b.baz_bar()
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 3, in baz_bar
AttributeError: 'Bar' object has no attribute '_Bar__private_attribute'
>>>
>>> vars(b)
{'_Foo__private_attribute': 'No one shall ever know'}
>>>
>>> b._Foo__private_attribute
'No one shall ever know'
基类Foo
中定义的方法能够使用Foo
中定义的私有名称访问私有属性。然而,在子类Bar
中定义的方法只能通过使用其损坏的名称访问私有属性;否则将导致异常。
collections.OrderedDict
是标准库中一个类的good example,它广泛使用名称损坏,以确保OrderedDict
的子类不会意外覆盖OrderedDict
中对OrderedDict
工作方式非常重要的某些方法。
如何修复此问题?
这里最明显的解决方案是重命名属性,使其只有一个前导下划线,如下所示。这仍然向外部用户发出了一个明确的信号,即这是一个私有属性,不应该被类外部的函数或类直接修改,但不会导致任何奇怪的名称损坏行为:from abc import ABC, abstractmethod
class Search(ABC):
@abstractmethod
def search_products_by_name(self, name):
print('found', name)
class Catalog(Search):
def __init__(self):
self._product_names = {}
def search_products_by_name(self, name):
super().search_products_by_name(name)
return self._product_names.get(name)
x = Catalog()
x._product_names = {'x': 1, 'y':2}
print(x.search_products_by_name('x'))
另一个解决方案是使用名称mangling,如下所示:
from abc import ABC, abstractmethod
class Search(ABC):
@abstractmethod
def search_products_by_name(self, name):
print('found', name)
class Catalog(Search):
def __init__(self):
self.__product_names = {}
def search_products_by_name(self, name):
super().search_products_by_name(name)
return self.__product_names.get(name)
x = Catalog()
# we have to use the mangled name when accessing it from outside the class
x._Catalog__product_names = {'x': 1, 'y':2}
print(x.search_products_by_name('x'))
或者-这可能更好,因为从类外部使用损坏的名称访问属性有点奇怪-如下所示:
from abc import ABC, abstractmethod
class Search(ABC):
@abstractmethod
def search_products_by_name(self, name):
print('found', name)
class Catalog(Search):
def __init__(self):
self.__product_names = {}
def search_products_by_name(self, name):
super().search_products_by_name(name)
return self.__product_names.get(name)
def set_product_names(self, product_names):
# we can still use the private name from within the class
self.__product_names = product_names
x = Catalog()
x.set_product_names({'x': 1, 'y':2})
print(x.search_products_by_name('x'))
这篇关于名称以两个下划线开头的实例属性被奇怪地重命名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!