【问题标题】:Inheriting class attribute with double underscore用双下划线继承类属性
【发布时间】:2017-10-30 16:14:44
【问题描述】:

我想我理解 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

现在,我知道对于每个类,类属性的实际名称都被修改为_<Class name>__data。例如,对于 Base,它将是 _Base__data,对于 A,它将是 _A__data,等等。

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

【问题讨论】:

  • 你错过了这正是它应该做的事情。在基础方法中,它将总是'_Base' 混淆,以避免与同名的子类属性冲突。

标签: python inheritance


【解决方案1】:

您并没有“遗漏”,相反,您只是“发现”了名称修饰的作用:它确保方法内带有双下划线的变量将始终看到与该方法在同一类中定义的属性,并且在它的任何子类中都没有。

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

因此,在func 中使用的.__data 名称本身在编译时被修改为_base__data

OrderedDict

Python 的 collections.OrderedDict 有一个额外的陷阱:Python 提供了一个纯 Python 实现,它使用__ 作为其“私有属性”,如上所述,但也有一个 C 中的本机代码实现,以及其私有结构不会暴露给 Python。

collections 模块以这些行结束 OrderedDict 代码块:

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

即:普通的“collections.OrderedDict”是用C编写的,有很多不透明的结构,子类无法接入。

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

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


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__',
 ...
]

【讨论】:

  • 那么你如何在被覆盖的方法中使用它呢?您是否必须在子类中创建一个单独的属性并将 super 的 重整属性分配给它?
  • 双下划线及其名称修饰被广泛滥用为“私有属性”,并在许多非官方文档中被错误列出。如果你需要从一个子类中访问一个 name-mangled 属性,那你一开始就做错了——它不应该是一个双下划线属性。但是你需要做的就是写下完整的 mangled-name获取它的属性。
  • Thnx,我曾经/正在尝试继承 collections.OrderedDict 并且有很多 mangled 属性。如果我无法弄清楚,我会问一个问题。
  • 在实例化的 OrderedDict 上使用简单的 (dir),您可以看到损坏的属性最终名称,如果您知道自己在做什么,则可以直接访问它们。
  • 这就是我的想法,当我尝试没有看到任何损坏的名称时。我尝试使用我认为最终损坏的名称应该是(使用基类),但这不起作用。我通过从源代码复制整个类并进行更改来解决它。我以为我已经结束了,但现在你又提出来了,我又好奇了。 ...也许if you know what you are doing 部分适用于此。
【解决方案2】:

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

The documentation for this feature 明确给出了一个关于保护变量免受派生类(而不是使用实例变量的外部代码)的示例。

名称修改有助于让子类覆盖方法而不会破坏类内方法调用。例如:

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)

【讨论】:

    猜你喜欢
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-17
    • 2014-10-06
    • 2013-04-13
    • 2015-05-08
    • 2013-09-02
    相关资源
    最近更新 更多