Python 名称修改允许双向访问
Python name mangling allows access both ways
所以我遇到了一个非常有趣的行为 python name mangling。
考虑以下代码
class C:
def __init__(self):
self.__c = 1
@staticmethod
def change(instance):
print(dir(instance))
print(instance.__c)
print(instance._C__c)
这里我创建了一个私有字段 __c 并期望从 class 内部直接访问它,并从 class 外部通过 _C__c 访问它。因此,如果我们将 C 的实例传递给 C.change,那么第二次或第三次打印应该会失败。
让我们检查一下:
>>> c = C()
>>> dir(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
>>> C.change(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
1
1
首先,为了调试,我们使用 dir(c)
打印所有可用的 c 成员。然后我们调用 C.change 将变量 c
.
传递给它
嗯,出乎意料,没有错误。
因此,change
中的第一个打印向我们展示了 instance
对象的所有可用条目。在这里我们看到字段 __c
可用作 _C__c
。这似乎没问题,因为我们不是通过自身访问,而是通过另一个变量访问。
从 'dir' 获得这样的输出,我预计 print(instance.__c)
会因 AttributeError 而失败。
然而没想到,居然还好好的!
这真的让我感到困惑,因为我不明白,为什么 __c
可以访问,如果它是设计的,那么为什么它没有在 dir
输出中列出?
__
-前缀名称在 as-is class 自己的方法中工作正常。由于 name-mangling,只有 在 class 之外(包括在其子 class 中)需要修改名称才能访问该属性。
每当您在 class 中写入 __c
时,它将被文本替换为 _<classname>__c
。它不是动态执行的,它是在解析阶段完成的。因此,解释器永远不会看到 __c
,只会看到 _<classname>__c
。这就是为什么只有 _C__c
出现在 dir(instance)
.
中的原因
引用 the docs:
[...] Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier __spam
occurring in a class named Ham
will be transformed to _Ham__spam
. This transformation is independent of the syntactical context in which the identifier is used. [...]
因此,它仅适用于点属性访问 (x.y
),不适用于通过 (get|set)attr
:
的动态访问
>>> class Foo:
... def __init__(self):
... setattr(self, '__x', 'test')
...
>>> Foo().__x
'test'
所以我遇到了一个非常有趣的行为 python name mangling。 考虑以下代码
class C:
def __init__(self):
self.__c = 1
@staticmethod
def change(instance):
print(dir(instance))
print(instance.__c)
print(instance._C__c)
这里我创建了一个私有字段 __c 并期望从 class 内部直接访问它,并从 class 外部通过 _C__c 访问它。因此,如果我们将 C 的实例传递给 C.change,那么第二次或第三次打印应该会失败。
让我们检查一下:
>>> c = C()
>>> dir(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
>>> C.change(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
1
1
首先,为了调试,我们使用 dir(c)
打印所有可用的 c 成员。然后我们调用 C.change 将变量 c
.
传递给它
嗯,出乎意料,没有错误。
因此,change
中的第一个打印向我们展示了 instance
对象的所有可用条目。在这里我们看到字段 __c
可用作 _C__c
。这似乎没问题,因为我们不是通过自身访问,而是通过另一个变量访问。
从 'dir' 获得这样的输出,我预计 print(instance.__c)
会因 AttributeError 而失败。
然而没想到,居然还好好的!
这真的让我感到困惑,因为我不明白,为什么 __c
可以访问,如果它是设计的,那么为什么它没有在 dir
输出中列出?
__
-前缀名称在 as-is class 自己的方法中工作正常。由于 name-mangling,只有 在 class 之外(包括在其子 class 中)需要修改名称才能访问该属性。
每当您在 class 中写入 __c
时,它将被文本替换为 _<classname>__c
。它不是动态执行的,它是在解析阶段完成的。因此,解释器永远不会看到 __c
,只会看到 _<classname>__c
。这就是为什么只有 _C__c
出现在 dir(instance)
.
引用 the docs:
[...] Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier
__spam
occurring in a class namedHam
will be transformed to_Ham__spam
. This transformation is independent of the syntactical context in which the identifier is used. [...]
因此,它仅适用于点属性访问 (x.y
),不适用于通过 (get|set)attr
:
>>> class Foo:
... def __init__(self):
... setattr(self, '__x', 'test')
...
>>> Foo().__x
'test'