Можем ли мы использовать super() для проверки идентичности между методами класса в MRO?
Рассмотрим следующий пример:
class A:
def m():
pass
class B(A):
pass
И следующий вывод терминала:
>>> b = B()
>>> b.m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> super(b.__class__, b).m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> b.m is super(b.__class__, b).m
False
>>> b.m == super(b.__class__, b).m
True
Почему они равны, но не идентичны? Делается ли копия метода при его наследовании?
Есть ли лучшие способы проверить, переопределил ли дочерний класс родительский метод?
2 ответа
С помощью super(b.__class__, b)
создает объект, реализующий __getattr__
метод, который поднимет __mro__
атрибут, начинающийся с позиции 1 (пропуская текущий класс), и найдите первый класс, который имеет указанный атрибут. Затем он вернет этот связанный метод. Для лучшего объяснения см. Этот ответ.
Зная, что все функции также являются дескрипторами, следующие
class A:
def m(self):
pass
создает объект A
с атрибутом m
который будет функцией и дескриптором. Когда вы инициализируете объектa
класса A
, это в основном приведет к a.m = A.m.__get__(a)
который создает связанный метод, имеющий a
как первый аргумент self
.
Теперь, когда super
также извлекает связанные методы, что проверяется, это идентичность между двумя экземплярами A.m.__get__(a)
создание вывода вашего терминала:
>>> A.m.__get__(a)
<bound method A.m of <__main__.A object at 0x...>>
>>> A.m.__get__(a) is A.m.__get__(a)
False
Итак, 2 вызова дескриптора класса m
создают разные связанные экземпляры, и именно поэтому проверка личности не выполняется. Вместо этого вы должны проверить идентичность функций, которые создали связанные методы. К счастью, связанный метод содержит__func__
атрибут, возвращающий исходную функцию. Итак, чтобы узнать, переопределил ли какой-либо класс экземпляра унаследованную функцию, не зная больше, чем просто экземпляр и имя функции, вы можете:
>>> a.__class__.m is super(a.__class__, a).m.__func__
True
Вы можете использовать __dict__
атрибут, чтобы проверить, какие методы и атрибуты были переопределены:
>>> class A:
... def m():
... pass
...
>>> class B(A):
... pass
...
>>> class C(A):
... def m():
... pass
...
>>> 'm' in A.__dict__
True
>>> 'm' in B.__dict__
False # not overridden
>>> 'm' in C.__dict__
True # overridden