名称以两个下划线开头的实例属性被奇怪地重命名
Instance attribute that has a name starting with two underscores is weirdly renamed
使用 class 的当前实现,当我尝试使用 class 方法获取私有属性的值时,我得到 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': {}}
这很奇怪!在我们的 class 定义中,我们没有给任何属性命名 _Catalog__product_names
。我们将一个属性命名为 __product_names
,但该属性似乎已重命名。
怎么回事
此行为不是错误 — 它实际上是 python 的一项功能,称为 private name mangling。对于您在 class 定义中定义的所有属性,如果属性名称以两个前导下划线开头 — 而不是 以两个尾随下划线结尾 — 那么该属性将被重命名像这样。 class Bar
中名为 __foo
的属性将重命名为 _Bar__foo
; class Breakfast
中名为 __spam
的属性将重命名为 _Breakfast__spam
;等等等等
仅当您尝试从 class 外部访问属性时才会发生名称重整。 class 中的方法仍然可以使用您在 __init__
.
中定义的“私有”名称访问该属性
为什么你会想要这个?
我个人从未发现此功能的用例,对此我有些怀疑。它的主要用例适用于您希望在 class 中可以私下访问方法或属性但不能通过相同名称访问 class 之外的函数的情况继承自此 class.
的其他 classes
这里有一些用例:
What is the benefit of private name mangling?
这是一个很好的 YouTube talk,其中包括一些用例
此功能大约需要 34 分钟。
(N.B。YouTube talk是2013年的,talk里面的例子都是用python2写的,所以例子里的一些语法和现在的python — print
仍然是语句而不是函数等)
下面是使用 class 继承时私有名称修饰如何工作的示例:
>>> 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'
基 class Foo
中定义的方法能够使用在 Foo
中定义的私有名称访问私有属性。但是,在 subclass Bar
中定义的方法只能通过使用其损坏的名称来访问私有属性;任何其他都会导致异常。
collections.OrderedDict
是标准库中 class 的 good example,它广泛使用名称修饰来确保 [=30] 的子 class =] 不要意外覆盖 OrderedDict
中对 OrderedDict
工作方式很重要的某些方法。
我该如何解决这个问题?
这里显而易见的解决方案是重命名您的属性,使其只有一个前导下划线,就像这样。这仍然向外部用户发出一个明确的信号,即这是一个私有属性,不应由 class 之外的函数或 classes 直接修改,但不会导致任何奇怪的名称修改行为:
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'))
或者 — 这可能更好,因为从 class 外部使用其损坏的名称访问属性有点奇怪 — 像这样:
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'))
双下划线的目的是避免与sub类定义的名称冲突。这不是表示某物是 'private' 的方法,因为 Python 没有阻止访问的概念。
__
有用的情况是:
class Product:
discount = 5
__init__(self, name, price):
self.name = name
self.price = price
class Item(Product):
def discount(self):
self.price = self.price * 0.9
discount
类 加上子类的方法名。如果使用 __discount
,则该变量的名称将变为 _Product__discount
.
如果没有sub类,那么使用__
就没有意义了
在你的代码中没有理由使用 ABC,并且可以很容易地用更 pythonic 的方式编写:
class Catalog:
def __init__(self, products=None):
self.products = products
def search_products(self, name):
return [item for item in self.products if name in item]
x = Catalog()
x.products = [{"x": 1}, {"y": 2}, {"x": 5}]
results = x.search_products(name="x")
# [{'x': 1}, {'x': 5}]
使用 class 的当前实现,当我尝试使用 class 方法获取私有属性的值时,我得到 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': {}}
这很奇怪!在我们的 class 定义中,我们没有给任何属性命名 _Catalog__product_names
。我们将一个属性命名为 __product_names
,但该属性似乎已重命名。
怎么回事
此行为不是错误 — 它实际上是 python 的一项功能,称为 private name mangling。对于您在 class 定义中定义的所有属性,如果属性名称以两个前导下划线开头 — 而不是 以两个尾随下划线结尾 — 那么该属性将被重命名像这样。 class Bar
中名为 __foo
的属性将重命名为 _Bar__foo
; class Breakfast
中名为 __spam
的属性将重命名为 _Breakfast__spam
;等等等等
仅当您尝试从 class 外部访问属性时才会发生名称重整。 class 中的方法仍然可以使用您在 __init__
.
为什么你会想要这个?
我个人从未发现此功能的用例,对此我有些怀疑。它的主要用例适用于您希望在 class 中可以私下访问方法或属性但不能通过相同名称访问 class 之外的函数的情况继承自此 class.
的其他 classes这里有一些用例: What is the benefit of private name mangling?
这是一个很好的 YouTube talk,其中包括一些用例 此功能大约需要 34 分钟。
(N.B。YouTube talk是2013年的,talk里面的例子都是用python2写的,所以例子里的一些语法和现在的python — print
仍然是语句而不是函数等)
下面是使用 class 继承时私有名称修饰如何工作的示例:
>>> 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'
基 class Foo
中定义的方法能够使用在 Foo
中定义的私有名称访问私有属性。但是,在 subclass Bar
中定义的方法只能通过使用其损坏的名称来访问私有属性;任何其他都会导致异常。
collections.OrderedDict
是标准库中 class 的 good example,它广泛使用名称修饰来确保 [=30] 的子 class =] 不要意外覆盖 OrderedDict
中对 OrderedDict
工作方式很重要的某些方法。
我该如何解决这个问题?
这里显而易见的解决方案是重命名您的属性,使其只有一个前导下划线,就像这样。这仍然向外部用户发出一个明确的信号,即这是一个私有属性,不应由 class 之外的函数或 classes 直接修改,但不会导致任何奇怪的名称修改行为:
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'))
或者 — 这可能更好,因为从 class 外部使用其损坏的名称访问属性有点奇怪 — 像这样:
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'))
双下划线的目的是避免与sub类定义的名称冲突。这不是表示某物是 'private' 的方法,因为 Python 没有阻止访问的概念。
__
有用的情况是:
class Product:
discount = 5
__init__(self, name, price):
self.name = name
self.price = price
class Item(Product):
def discount(self):
self.price = self.price * 0.9
discount
类 加上子类的方法名。如果使用 __discount
,则该变量的名称将变为 _Product__discount
.
如果没有sub类,那么使用__
在你的代码中没有理由使用 ABC,并且可以很容易地用更 pythonic 的方式编写:
class Catalog:
def __init__(self, products=None):
self.products = products
def search_products(self, name):
return [item for item in self.products if name in item]
x = Catalog()
x.products = [{"x": 1}, {"y": 2}, {"x": 5}]
results = x.search_products(name="x")
# [{'x': 1}, {'x': 5}]