关于Python的super用法一处不解

描述问题

以前以为自己知道super怎么用,但是看到下面的代码,却是没有搞懂其作用原理

下面的代码,神奇地做到了"属性设置不能为int" (实现在父类里面)

查阅了super的用法,摘抄如下

super(type, obj) -> bound super object; requires isinstance(obj, type)

super(type) -> unbound super object

super(type, type2) -> bound super object; requires issubclass(type2, type) #这种用法没见过

Typical use to call a cooperative superclass method:

摘录官方文档如下 (就是第二个参数为type时,不太明白那一段英文的意思)

super(type[, object-or-type]) 

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.

The __mro__ attribute of the type lists the method resolution search order used by both getattr() and super(). The attribute is dynamic and can change whenever the inheritance hierarchy is updated.

If the second argument is omitted, the super object returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true (this is useful for classmethods).

上下文环境

  1. Python3

重现

相关代码

class Person:

def __init__(self, name):

self.name = name

# Getter function

@property

def name(self):

return self._name

# Setter function

@name.setter

def name(self, value):

if not isinstance(value, str):

raise TypeError('Expected a string')

self._name = value

# Deleter function

@name.deleter

def name(self):

raise AttributeError("Can't delete attribute")

class SubPerson(Person):

@property

def name(self):

print('Getting name')

return super().name

@name.setter

def name(self, value):

print('Setting name to', value)

super(SubPerson, SubPerson).name.__set__(self, value) ##??? 这种用法第一次见, 作用原理/作用流程是什么?

#返回一个父类的实例,然后调用其方法

#关键是: super(SubPerson, SubPerson) 如何使用的?

@name.deleter

def name(self):

print('Deleting name')

super(SubPerson, SubPerson).name.__delete__(self)

图片描述

报错信息

回答:

這個問題簡而言之, super(SubPerson, SubPerson).name 會調用 Person.name 的 setter, 而:

python">if not isinstance(value, str):

raise TypeError('Expected a string')

這個檢查將會引發 TypeError 當設定值並非 string 的時候。


如果這個地方沒問題, 我們再回頭來看 super 做了什麼, 要理解 super 並避免一些誤解, 簡單來說理解三件事:

  1. super 不一定指涉到父類, 他指涉的是 mro 中的下一個類

  2. 在 mro 中, 基礎類別一定出現在衍生類別之後, 若有多個基礎類別, 其相對順序保持不變

  3. super 對象並非其指涉的類別, 他的作用只是代理委託

super 不一定指涉到父類, 他指涉的是 mro 中的下一個類

那假設大家對 mro 都有一定程度的理解(若不太理解, 我們可以再討論)

super 在做的事情可用下面的代碼理解:

def super(cls, inst):

mro = inst.__class__.mro()

return mro[mro.index(cls) + 1]

這可以說是理解 super 的關鍵之處 (很多人覺得自己懂 super, 其實看完這段代碼會發現還不太懂XD)

  1. super 會去找出第二個參數(可以是類可以是對象) 的 類 的 mro

  2. 返回 mro 中在第一個參數 cls 的下一個類

舉例來說:

In [14]: import person

In [15]: from person import SubPerson

In [16]: sp = SubPerson('Guido')

Setting name to Guido

In [17]: sp.__class__.mro()

Out[17]: [person.SubPerson, person.Person, object]

  • super(SubPerson, sp) 指的是 Person (mro 中 sp的類:SubPerson 的下一個)

  • super(SubPerson, SubPerson) 指的是 Person (mro 中 SubPerson 的類:SubPerson 的下一個)

  • super(SubPerson, Person) 指的是 object (mro 中 Person 的類:Person 的下一個)

這邊可能還有兩個問題是:

  • super 可以在類別外部使用?

    • 是的, super 是一個內建函數, 不限制在 class 內部使用

  • super 可以省略參數嗎?

    • 在類的外部使用時, 不允許省略參數, 不然不知道是找誰的 mro, 也不知道找誰的下一個

    • 在類的內部使用時, 允許省略參數, 這個狀況下, 找的是自己的 mro, 找自己的下一個

那為什麼會說 super 不一定指涉到父類, 他指涉的是 mro 中的下一個類呢? 很簡單, 多重繼承的情況之下, mro 的下一個類很可能不是 父類(Parent) 是 兄弟類(Sibling)

但多數的情況下, 我們會希望 super 幫我們找到單繼承下的直接父類別, 所以我們會傾向在類別內部, 完全省略參數來調用 super()

另外兩點

  • 第二點是個重要的觀念, 但假設對 mro 有一定的認知的話, 這是個基本觀念

  • 第三點的話是一個迷思, 我們並不能透過 super 拿到指涉的那個類, 只能夠透過 super 對象的代理去委託指涉類的 method 做事情

不知道以上有沒有解決你的疑惑呢?


我回答過的問題: Python-QA

回答:

补充一点点,super在不同参数下可以代表unbound、bound到对象、bound到类型,这点在docstring当中其实写的比较清楚了,Python3省略所有参数时候相当于super(self, class),是bound到对象。而super(class, class)自然是绑定到了父类。实际上代码中super(SubPersion, SubPersion)是一种完全错误的写法,它应该被写成super(SubPersion, type(self)),如果存在多继承的话,后一种写法才能正确proxy到多继承的其他类上面去,实现菱形继承。只是在单继承的时候这种写法也正好可以用而已。

关于绑定的话,我们都知道类中的方法有staticmethod(无绑定)、method(绑定到对象)和classmethod(绑定到类)这三种,你不理解的第三种就是提供绑定到class的,它返回的类似于一个class对象。

接下来需要理解的是property的原理,property是在类的__dict__当中创建了一个descriptor对象,这个对象可以通过Person.name来获得,对于@property创建的对象来说,property自己是个类,也是这个descriptor对象的类型,所以调用Person.name.__set__就相当于通过descriptor机制设置属性。之所以要这么做是因为super()这个东西的代理并不是万能的,它差不多是个只读的代理,而不能写入,所以必须手工去调用__set__。

以上是 关于Python的super用法一处不解 的全部内容, 来源链接: utcz.com/a/157765.html

回到顶部