Python中私有和受保护方法的继承


76

我知道,Python中没有“真正的”私有/受保护的方法。这种方法并不意味着要隐藏任何东西。我只想了解Python的功能。

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        pass

class Child(Parent):
    def foo(self):
        self._protected()   # This works

    def bar(self):
        self.__private()    # This doesn't work, I get a AttributeError:
                            # 'Child' object has no attribute '_Child__private'

那么,这种行为是否意味着“受保护”的方法将被继承,但“私有”的方法将不会被继承呢?
还是我错过了什么?


“这行不通”是什么意思?
tyteen4a03

我编辑了原始帖子。
uloco

6
您必须这样称呼,假设c是Child的一个实例c._Parent__private()
vahid abdi

它不是应该正常工作吗?AFAIK私有方法不会被继承。stackoverflow.com/questions/8241462/…–
Qback

Answers:


110

Python没有隐私模型,没有像C ++,C#或Java这样的访问修饰符。没有真正的“受保护”或“私有”属性。

带有前导双下划线但无尾随双下划线的名称将被重编,以防止继承时出现冲突。子类可以定义自己的__private()方法,并且不会干扰父类上的相同名称。这些名称被认为是私人的; 他们仍然可以从班级外部访问,但是发生意外冲突的可能性要小得多。

通过在此类名称前加上一个额外的下划线和类名称(无论名称如何使用或是否存在),来进行改型,从而有效地为它们提供一个命名空间。在Parent类中,任何__private标识符都将由name替换(在编译时)_Parent__private,而在Child类中_Child__private,在类定义中的任何地方,标识符均由替换。

以下将起作用:

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self._Parent__private()

请参阅词法分析文档中的标识符的保留类

__*
类私有名称。当在类定义的上下文中使用时,此类别中的名称将被重写以使用变形的形式,以帮助避免基类和派生类的“私有”属性之间的名称冲突。

以及有关名称的参考文档

私有名称修饰:当在类定义中以文本形式出现的标识符以两个或多个下划线字符开头且不以两个或多个下划线结尾时,则被视为该类的私有名称。在为专用名称生成代码之前,专用名称会转换为更长的格式。转换将在该类的名称前面插入类名,并删除前导下划线,并插入单个下划线。例如,__spam出现在名为Ham的类中的标识符将转换为_Ham__spam。此转换独立于使用标识符的句法上下文。

不要使用类专用名称,除非您特别想避免告诉那些想要对您的类进行子类化的开发人员,他们不能使用某些名称或冒着破坏您的类的风险。在已发布的框架和库之外,此功能很少使用。

PEP 8 Python的风格指南是这样说的关于私人名字改编:

如果您的类打算被子类化,并且您具有不希望使用子类的属性,请考虑使用双引号和下划线来命名它们。这将调用Python的名称修改算法,其中将类的名称修改为属性名称。这有助于避免属性名称冲突,如果子类无意中包含相同名称的属性。

注1:请注意,整齐的名称中仅使用了简单的类名,因此,如果子类同时选择了相同的类名和属性名,则仍会发生名称冲突。

注意2:名称修饰可以使某些用途(如调试和 __getattr__())变得不太方便。但是,名称修饰算法已被详细记录,并且易于手动执行。

注意3:并非每个人都喜欢名称修饰。尝试在避免意外名称冲突与高级呼叫者潜在使用之间进行权衡。


我知道,这与名称修改有关。我知道我可以通过_Parent__private从类外部访问该方法,但是我不明白为什么不能在继承的类中调用它。我不知道,子类有自己的功能__private(),非常感谢您的明确回答!
uloco

怎么样__getstate__()__setstate__()?一个孩子将无法从其父母那里继承这些方法?
BoltzmannBrain

@BoltzmannBrain:这些名称在末尾也要包含下划线,而不仅仅是在名称的开头。那是另一类名字。请参阅我链接到文档
马丁·皮特斯

谢谢,但是那里的文档并没有给我更多有用的信息。我已经将其移至另一个问题:stackoverflow.com/questions/44444733/…–
BoltzmannBrain

@Martijn:谢谢你的回答。pythonic style尝试在类中将所有变量设为私有,然后将getterssetters用于这些私有变量时,也许我没有使用a 。好吧,我应该用谷歌查询有关私有变量用法的问题。
FooBar167

12

将double__属性更改为_ClassName__method_name,使其比隐含的语义隐私更加私有_method_name

从技术上讲,如果您确实愿意,也可以使用它,但是大概没人会这样做,因此出于维护代码抽象的原因,该方法在那时也可能是私有的。

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        print("Is it really private?")

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self.__private()

c = Child()
c._Parent__private()

这具有允许方法不与子类方法名称冲突的其他好处(或有人说主要的好处)。


如果在子级中使用“父级”私人方法进行呼叫,则此方法不起作用
Nico Coallier

8

另外PEP8

仅对非公共方法和实例变量使用一个前划线

为避免名称与子类冲突,请使用两个前导下划线来调用Python的名称处理规则。

Python用类名来修饰这些名称:如果class Foo具有名为的属性__a,则不能通过进行访问Foo.__a。(坚持的用户仍然可以通过调用获得访问权限Foo._Foo__a。)通常,双引号下划线仅应用于避免名称与设计为子类的类中的属性发生冲突。

_such_methods按照惯例,您也应该远离。我的意思是你应该把他们当成private


7

通过将您的数据成员声明为私有:

__private()

您根本无法从班级外部访问它

Python支持一种称为名称修改的技术。

此功能将以两个下划线为前缀的类成员变为:

_className.memberName

如果要从中访问它,Child()可以使用:self._Parent__private()


3

尽管这是一个老问题,但我遇到了,并找到了一个不错的解决方法。

在这种情况下,您希望在父类上进行命名更改,因为您想模仿一个受保护的函数,但仍想以简单的方式在子类上访问该函数。

parent_class_private_func_list = [func for func in dir(Child) if func.startswith ('_Parent__')]

for parent_private_func in parent_class_private_func_list:
        setattr(self, parent_private_func.replace("_Parent__", "_Child"), getattr(self, parent_private_func))        

这个想法是手动将父函数名称替换为适合当前名称空间的一种。在子类的init函数中添加此函数之后,您可以轻松地调用该函数。

self.__private()

2

AFAIK,在第二种情况下,Python执行“名称修改”,因此父类的__private方法的名称实际上是:

_Parent__private

而且您也不能以这种形式在儿童中使用它

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.