Как обнаружить перегрузку метода в подклассах в Python?
У меня есть класс, который является суперклассом для многих других классов. Я хотел бы знать (в init() моего суперкласса, переопределил ли подкласс определенный метод).
Я пытался сделать это с помощью метода класса, но результаты были неправильными:
class Super:
def __init__(self):
if self.method == Super.method:
print 'same'
else:
print 'different'
@classmethod
def method(cls):
pass
class Sub1(Super):
def method(self):
print 'hi'
class Sub2(Super):
pass
Super() # should be same
Sub1() # should be different
Sub2() # should be same
>>> same
>>> different
>>> different
Есть ли способ для суперкласса узнать, переопределил ли подкласс метод?
10 ответов
Вы можете использовать свой собственный декоратор. Но это хитрость и будет работать только на классах, где вы контролируете реализацию.
def override(method):
method.is_overridden = True
return method
class Super:
def __init__(self):
if hasattr(self.method, 'is_overridden'):
print 'different'
else:
print 'same'
@classmethod
def method(cls):
pass
class Sub1(Super):
@override
def method(self):
print 'hi'
class Sub2(Super):
pass
Super() # should be same
Sub1() # should be different
Sub2() # should be same
>>> same
>>> different
>>> same
Если вы хотите проверить переопределенный метод экземпляра в Python 3, вы можете сделать это, используя тип self:
class Base:
def __init__(self):
if type(self).method == Base.method:
print('same')
else:
print('different')
def method(self):
print('Hello from Base')
class Sub1(Base):
def method(self):
print('Hello from Sub1')
class Sub2(Base):
pass
Теперь Base() и Sub2() должны печатать "одинаково", а Sub1() печатать "разные". Декоратор classmethod заставляет первый параметр быть привязанным к типу self, и поскольку тип подкласса по определению отличается от его базового класса, два метода класса будут сравниваться как не равные. Сделав метод методом экземпляра и используя тип self, вы сравниваете простую функцию с другой простой функцией и предполагаете, что функции (или несвязанные методы в этом случае, если вы используете Python 2) сравнивают себя равными (что они делают в реализации C Python), будет получено желаемое поведение.
Это кажется наиболее простым и достаточным для этого путем сравнения общего подмножества словарей экземпляра и самого базового класса, например:
def detect_overridden(cls, obj):
common = cls.__dict__.keys() & obj.__class__.__dict__.keys()
diff = [m for m in common if cls.__dict__[m] != obj.__class__.__dict__[m]]
print(diff)
def f1(self):
pass
class Foo:
def __init__(self):
detect_overridden(Foo, self)
def method1(self):
print("Hello foo")
method2=f1
class Bar(Foo):
def method1(self):
print("Hello bar")
method2=f1 # This is pointless but not an override
# def method2(self):
# pass
b=Bar()
f=Foo()
Работает и дает:
['method1']
[]
В ответ на ответ /questions/5790660/kak-obnaruzhit-peregruzku-metoda-v-podklassah-v-python/5790671#5790671, поскольку у меня еще недостаточно кредитов, чтобы комментировать, он не будет работать в Python 3, если вы не замените im_func
с __func__
и также не будет работать в Python 3.4(и, скорее всего, вперед), так как функции больше не имеют __func__
атрибут, только связанные методы.
РЕДАКТИРОВАТЬ: Вот решение исходного вопроса (который работал на 2.7 и 3.4, и я предполагаю, что все другие версии между ними):
class Super:
def __init__(self):
if self.method.__code__ is Super.method.__code__:
print('same')
else:
print('different')
@classmethod
def method(cls):
pass
class Sub1(Super):
def method(self):
print('hi')
class Sub2(Super):
pass
Super() # should be same
Sub1() # should be different
Sub2() # should be same
И вот вывод:
same
different
same
Я использую следующий метод, чтобы определить, переопределяется ли данный связанный метод или происходит ли его из родительского класса.
class A():
def bla(self):
print("Original")
class B(A):
def bla(self):
print("Overridden")
class C(A):
pass
def isOverriddenFunc(func):
obj = func.__self__
prntM = getattr(super(type(obj), obj), func.__name__)
return func.__func__ != prntM.__func__
b = B()
c = C()
b.bla()
c.bla()
print(isOverriddenFunc(b.bla))
print(isOverriddenFunc(c.bla))
Результат:
Overridden
Original
True
False
Конечно, чтобы это работало, метод должен быть определен в базовом классе.
Вы можете сравнить все, что есть в классе __dict__, с функцией внутри метода, которую вы можете извлечь из объекта - функция-функция "detect_overriden" делает это - хитрость заключается в том, чтобы передать "родительский класс" для его имени, как это делается в вызовите "super" - иначе не так просто извлечь атрибуты из самого родительского класса, а не из подкласса:
# -*- coding: utf-8 -*-
from types import FunctionType
def detect_overriden(cls, obj):
res = []
for key, value in cls.__dict__.items():
if isinstance(value, classmethod):
value = getattr(cls, key).im_func
if isinstance(value, (FunctionType, classmethod)):
meth = getattr(obj, key)
if not meth.im_func is value:
res.append(key)
return res
# Test and example
class A(object):
def __init__(self):
print detect_overriden(A, self)
def a(self): pass
@classmethod
def b(self): pass
def c(self): pass
class B(A):
def a(self): pass
#@classmethod
def b(self): pass
Отредактируйте измененный код, чтобы он работал нормально с методами классов: если он обнаруживает метод класса в родительском классе, перед продолжением извлекает базовую функцию.
- Другой способ сделать это без необходимости жесткого кодирования имени класса - это следовать классу экземпляра (self.__class__
) порядок разрешения метода (определяется __mro__
атрибут) и поиск дубликатов методов и атрибутов, определенных в каждом классе по цепочке наследования.
Вы также можете проверить, не переопределено ли что-то от его родителей, не зная ни одного из участвующих классов, используя super
:
class A:
def fuzz(self):
pass
class B(A):
def fuzz(self):
super().fuzz()
class C(A):
pass
>>> b = B(); c = C()
>>> b.__class__.fuzz is super(b.__class__, b).fuzz.__func__
False
>>> c.__class__.fuzz is super(c.__class__, c).fuzz.__func__
True
См. Этот вопрос для получения дополнительных сведений.
Общая функция:
def overrides(instance, function_name):
return getattr(instance.__class__, function_name) is not getattr(super(instance.__class__, instance), function_name).__func__
>>> overrides(b, "fuzz")
True
>>> overrides(c, "fuzz")
False
Вы можете проверить, была ли переопределена функция, посмотрев, указывает ли дескриптор функции на функцию суперкласса или нет. Обработчик функции в объекте подкласса указывает либо на функцию суперкласса, либо на переопределенную функцию в подклассе. Например:
class Test:
def myfunc1(self):
pass
def myfunc2(self):
pass
class TestSub(Test):
def myfunc1(self):
print('Hello World')
>>> test = TestSub()
>>> test.myfunc1.__func__ is Test.myfunc1
False
>>> test.myfunc2.__func__ is Test.myfunc2
True
Если дескриптор функции не указывает на функцию в суперклассе, значит, он был переопределен.
Не уверен, что это то, что вы ищете, но мне помогло, когда я искал подобное решение.
class A:
def fuzz(self):
pass
class B(A):
def fuzz(self):
super().fuzz()
assert 'super' in B.__dict__['fuzz'].__code__.co_names
Самый популярный ответ и несколько других используют ту или иную формуSub.method == Base.method
. Однако это сравнение может вернуть ложноотрицательный результат, если и не используется один и тот же синтаксис импорта. Например, см. обсуждение здесь , объясняющее сценарий, в которомissubclass(Sub, Base) -> False
.
Эта тонкость не очевидна при запуске многих минимальных примеров, но может проявиться в более сложной кодовой базе. Более надежным подходом является сравнение метода, определенного вSub.__bases__
запись, соответствующаяBase
потому что__bases__
гарантированно использует тот же путь импорта, что иSub
import inspect
def method_overridden(cls, base, method):
"""Determine if class overriddes the implementation of specific base class method
:param type cls: Subclass inheriting (and potentially overriding) the method
:param type base: Base class where the method is inherited from
:param str method: Name of the inherited method
:return bool: Whether ``cls.method != base.method`` regardless of import
syntax used to create the two classes
:raises NameError: If ``base`` is not in the MRO of ``cls``
:raises AttributeError: If ``base.method`` is undefined
"""
# Figure out which base class from the MRO to compare against
base_cls = None
for parent in inspect.getmro(cls):
if parent.__name__ == base.__name__:
base_cls = parent
break
if base_cls is None:
raise NameError(f'{base.__name__} is not in the MRO for {cls}')
# Compare the method implementations
return getattr(cls, method) != getattr(base_cls, method)