继承带有双下划线的类属性

Inheriting class attribute with double underscore

提问人:aviro 提问时间:10/31/2017 最后编辑:jonrsharpeaviro 更新时间:4/26/2020 访问量:2461

问:

我想我理解 python 中“名称修改”的概念,但我可能错过了一些东西。请看以下代码:

#!/usr/bin/env python

class Base(object):
    __data = "Base"

    @classmethod
    def func(cls):
        return "Class name is {}, data is {}".format(cls.__name__, cls.__data)


class A(Base):
    __data = "A"


class B(A):
    __data = "B"


print Base.func()
print A.func()
print B.func()

这是我得到的输出:

Class name is Base, data is Base
Class name is A, data is Base
Class name is B, data is Base

现在,我了解到对于每个类,类属性的实际名称被修改为 .例如,对于 Base,它将是 ,对于 A,它将是 ,等等。_<Class name>__data_Base__data_A__data

我的问题是,在它内部正确标识了继承类(Base、A 和 B)的名称,但总是导致 .为什么?我的意思是,如果是 A 或 B,那么我知道我属于 A 或 B 类,所以我希望分别成为 A 或 B 类之一。我在这里错过了什么?funccls.__datacls._Base__data__name__cls.__data

Python 继承

评论

3赞 jonrsharpe 10/31/2017
你错过了这正是它应该做的。在 base 方法中,它总是会被 修改,以避免与同名的子类属性发生冲突。'_Base'

答:

6赞 jsbueno 10/31/2017 #1

你并没有“失踪”,相反,你只是“发现”了名称修改的作用:它是为了确保方法内带有双下划线的变量将始终看到与该方法在同一类中定义的属性,并且不在其子类中。

如果您只想使用该属性,因为它在每个子类中被覆盖,这是所有其他属性的正常行为,但对于以两个下划线为前缀的属性。

因此,在编译时,内部使用的名称本身被篡改为 ..__datafunc_base__data

OrderedDict

Python 的集合。OrderedDict 有一个额外的陷阱:Python 既提供了一个纯 python 实现,它使用 the 作为其“私有属性”,如上所述,但也有一个 C 语言的原生代码实现,以及不向 Python 公开的私有结构。__

该模块用以下几行结束 OrderedDict 代码块:collections

try:
    from _collections import OrderedDict
except ImportError:
    # Leave the pure Python version in place.
    pass

也就是说:正常的“集合。OrderedDict“是用 C 语言编写的,有很多不透明的结构,子类无法利用。

访问 Python 定义的 OrderedDict 的唯一方法是 删除属性(在 ,而不是模块中),然后重新加载该模块。_collections.OrderedDict_collectionscollectionscollections

如果你这样做,并实例化一个有序的字典,私有数据结构可以看起来是:


from imp import reload
import _collections, collections
backup = _collections.OrderedDict
del _collections.OrderedDict
collections = reload(collections)
PyOrderedDict = collections.OrderedDict
_collections.OrderedDict = backup
a  = PyOrderedDict()
dir(a)

Out[xx]: 
['_OrderedDict__hardroot',
 '_OrderedDict__map',
 '_OrderedDict__marker',
 '_OrderedDict__root',
 '_OrderedDict__update',
 '__class__',
 '__contains__',
 '__delattr__',
 ...
]

评论

0赞 wwii 4/23/2020
那么,如何在重写的方法中使用它呢?您是否必须在子类中创建一个单独的属性并将 super 的 medged 属性同化到它身上?
0赞 jsbueno 4/23/2020
双下划线及其名称被广泛误用为“私有属性”,并在许多非官方文档中被错误地列出。如果你需要从一个子类中访问一个被破坏名称的属性,那么你首先做错了 - 它不应该是一个双下划线属性。但是您需要做的就是写下属性的完整名称即可获得它。
0赞 wwii 4/23/2020
Thnx,我曾经/正在尝试对集合进行子类化。OrderedDict 并且有很多损坏的属性。如果我搞不通,我会问一个问题。
0赞 jsbueno 4/25/2020
在实例化的 OrderedDict 上使用一个简单的 (dir),您可以看到损坏的属性最终名称,并且,如果您知道自己在做什么,则可以直接访问它们。
0赞 wwii 4/25/2020
这就是我的想法,但当我尝试时,我没有看到任何被破坏的名字。我尝试使用我认为最终损坏的名称(使用基类),但这不起作用。我通过从源代码复制 Whole 类并进行更改来解决它。我以为我已经结束了,但现在你又提起来了,我又好奇了。...也许这部分适用于这里。if you know what you are doing
1赞 Arthur Tacca 10/31/2017 #2

正如您所注意到的,用于名称更改的名称是声明方法的类的名称,而不是当前对象的派生类型。

此功能的文档显式地给出了一个示例,说明如何保护变量免受派生类(而不是使用实例变量的外部代码)的影响。

名称修改有助于让子类在不中断类内方法调用的情况下重写方法。例如:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)