python - 名称以两个下划线开头的实例属性被奇怪地重命名
问题描述
当我尝试使用类方法获取私有属性的值时,使用我的类的当前实现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': {}}
这很奇怪!在我们的类定义中,我们没有给任何属性 name _Catalog__product_names
。我们命名了一个属性__product_names
,但该属性似乎已被重命名。
这是怎么回事
这种行为不是错误——它实际上是 python 的一个特性,称为private name mangling。对于您在类定义中定义的所有属性,如果属性名称以两个前导下划线开头——并且不以两个尾随下划线结尾——那么该属性将像这样重命名。__foo
在类中命名的属性Bar
将被重命名_Bar__foo
;__spam
类中命名的属性Breakfast
将被重命名_Breakfast__spam
;等等等等
只有当您尝试从类外部访问属性时,才会发生名称修改。类中的方法仍然可以使用您在 中定义的“私有”名称访问属性__init__
。
为什么你会想要这个?
我个人从未找到此功能的用例,对此我有些怀疑。它的主要用例是您希望方法或属性在类中可私有访问,但不能通过相同名称访问类外的函数或继承自此类的其他类的情况。
这里有一些用例: 私有名称修改有什么好处?
这是一个很好的YouTube 演讲,其中包括大约 34 分钟后此功能的一些用例。
(注意 YouTube 演讲是从 2013 年开始的,演讲中的示例是用 python 2 编写的,所以示例中的一些语法与现代 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
是标准库中类的一个很好的例子,它广泛使用名称修饰来确保子类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'))
推荐阅读
- python - 爬虫卡在最后一页
- android - 命名空间组织和/或自定义视图属性别名?
- java - java - 如何在带有lambda表达式的Java中使用Comparator?
- python - 错误:(-215:断言失败)bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32) in function 'FillBitmapInfo
- python - Discord Python Bot 错误消息 client.command
- ruby-on-rails - 使用grape api上传文件 | ArgumentError(缺少关键字:io)
- nginx - Nginx代理同时保留路径
- excel - 工作表上的两个命令按钮以显示相同的用户窗体
- r - 如何传播计数数据
- python - RuntimeError:在评估测试数据时,形状“[4, 512]”对于大小为 1024 的输入无效