在继承的类中定义的变量和函数在基类中运行

时间:2018-08-05 18:15:31

标签: python python-3.x

我在Python 3.6中遇到了奇怪的行为。 我能够从基类方法调用仅在子类中定义的函数和访问变量。 我发现这在我的代码中很有用,但是我来自C ++,并且这段代码看起来很奇怪。 有人可以解释这种行为吗?

class a:
    def __init__(self):
        print(self.var)
        self.checker()

class b(a):
    def __init__(self):
        self.var=5
        super().__init__()
    def checker(self):
        print('inside B checker')

myB = b()

输出:

5
inside B checker

2 个答案:

答案 0 :(得分:2)

Python中的所有方法都是动态查找的。您正在self上调用一个方法,并且self是一个b实例,并且b实例具有一个checker方法,因此该方法被调用。

在模块顶层或顶层函数中考虑以下代码:

myB = b()
myB.checker()

显然,全局模块代码不是b类定义的一部分,但是,这显然是合法的。如果将代码放在class a定义中,并将myB重命名为welf,为什么会有什么不同? Python不在乎。您只是在问这个值-您是否将其命名为myBself-“您是否有一个名为checker的东西?”,答案是肯定的,因此可以调用它。

var更简单; self.var只需将var添加到self.__dict__,就可以了;它是一个b实例的事实在这里甚至都不相关(间接除外,因为一个b实例意味着它被称为b.__init___,而这就是var所在的位置创建)。

如果您想知道这种“索取价值”的方式,一个略为简化的版本是:

  • 每个对象都有一个__dict__。当您执行self.var=5时,实际上就执行了self.__dict__['var'] = 5。而当您print(self.var)时,它会print(self.__dict__['var'])
  • 当它引发KeyError时(如self.checker一样,Python会尝试type(self).__dict__['checker'],如果不起作用,它将在type(self).mro()上循环并尝试所有这些命令。
  • 当所有这些对象都提出KeyError时,就像使用self.spam一样,Python会调用self.__getattr__('spam')
  • 即使失败,您也会得到AttributeError

请注意,如果您尝试构造一个a实例,它将失败并显示一个AttributeError。这是因为现在selfa,而不是b。它没有checker方法,也没有经过添加__init__属性的var代码。


在C ++中无法执行此操作的原因是C ++方法是静态查找的。值在运行时不是什么类型,而是在编译时是什么类型。如果静态查找方法说它是虚拟的,则编译器将插入一些动态查找代码,否则,它不会插入。 1

通常被解释的一种方式是在Python(以及其他具有SmallTalk语义的语言,例如ObjC和Ruby)中,所有方法都是自动虚拟的。但这有点误导,因为在C ++中,即使使用虚拟方法,方法名称和签名仍然必须在基类上可以找到;在Python(和SmallTalk等)中,这不是必需的。

如果您认为这一定太慢了,那么Python每次调用一个方法时都必须做一些事情,例如通过名称搜索该方法的一些名称空间堆栈-可以,但是这样做并不慢如您所料。一方面,名称空间是字典,因此它是恒定时间搜索。然后将字符串嵌入并缓存其哈希值。如果需要的话,解释器甚至可以缓存查找结果。结果 仍然比通过vtable取消引用指针要慢,但幅度不大(此外,Python中的许多 other 东西可能慢20倍)比起C ++,就像for循环;当您需要每个细节尽可能快地工作时,您不使用纯Python)。


1。 C ++还存在另一个问题:即使您在var中定义了checker属性和a虚拟方法,也没有选择初始化程序的调用顺序。编译器会先自动调用a的构造函数,然后自动调用b的构造函数。在Python中,它调用b.__init__,您可以选择何时调用super().__init__(),无论是在方法的开始,结束,中间还是什至从不。

答案 1 :(得分:0)

首先,您要创建class b的实例,该实例将调用类b __init__的构造函数。

在构造函数内部,您将属性self.var设置为5。super().__init__()之后将调用父类A的构造函数。

class A的构造函数中,同时打印self.var和调用self.checker()

请注意,在调用super().__init__()时,默认情况下会将子类实例self作为第一个参数。