#python #class #metaclass
#python #класс #метакласс
Вопрос:
У меня есть иерархия классов, определения которых я не могу редактировать напрямую. Я хотел бы обернуть каждый класс в иерархии таким образом, чтобы некоторые функции были настроены.
Чтобы привести упрощенный пример, предположим, у нас есть иерархия животных:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def sound(self):
pass
class Cat(Animal):
def sound(self):
return "Meow"
class Dog(Animal):
def sound(self):
return "Woof"
class Cow(Animal):
def sound(self):
return "Moo"
Теперь, если я хочу изменить sound
метод одного из животных, я мог бы просто наследовать класс:
class LoudDog(Dog):
def sound(self):
return super().sound().upper()
Но в идеале я должен иметь возможность создавать оболочку, которая позволяет мне создавать громкую версию любого Animal
класса, что-то вроде этого:
LoudDog = Loud(Dog)
LoudCat = Loud(Cat)
LoutCow = Loud(Cow)
Какой был бы лучший / самый питонический способ добиться этого? Синтаксис не обязательно должен быть точно таким, как указано выше. Я думал, что здесь могут быть полезны метаклассы, но у меня нет большого опыта работы с ними, и я буду доволен и другими решениями.
Комментарии:
1. Вы могли бы использовать его как mix-in —
class LoudDog(Loud, Dog): pass
.
Ответ №1:
Вы можете попробовать этот подход, который создаст новый класс на основе заданного и настроит его метод «sound»
from copy import deepcopy
def loud(animal_class):
loud_class = deepcopy(animal_class) # to avoid modifying the real class
old_sound = animal_class.sound # to avoid recursion
loud_class.sound = lambda self: old_sound(self).upper()
return loud_class
LoudCow = loud(Cow)
LoudCow().sound() # prints "MOO'
Вы также можете попробовать, как было предложено, смешанный:
class Loud():
def __init__(self):
old_sound = self.sound
self.sound = lambda: old_sound.upper()
class LoudDog(Dog, Loud): pass
LoudDog().sound() # prints "WOOF"
Комментарии:
1. Должен ли я заменять подобные функции в решении mix-in или я могу как-то использовать
super
?2. Поскольку
Loud
класс не имеет «super», вы не можете. Это своего рода хак, поскольку он полагается наself
объект, уже имеющийsound
метод, если вы попытаетесь использовать его безAnimal
него, он потерпит неудачу.
Ответ №2:
Проще говоря, если вам не нужны дополнительные классы, просто другое поведение, ваш вызываемый объект может просто привязывать вызываемые объекты, которые будут работать как привязанные методы к экземплярам:
def bound(instance: Animal):
original_sound = instance.sound
def loud(*args):
new = original_sound()
return new.upper()
instance.sound = loud
# not needed, but can be used to curry various such calls:
return instance
Если вам действительно нужны классы, вы можете просто использовать множественное наследование:
class Loud:
def sound(self):
original = super.sound()
return original.upper()
class LoudCat(Loud, Cat):
pass
В этом случае в новом классе Python сначала найдет метод «sound» в классе Loud и super()
выберет следующий класс __mro__
(порядок разрешения метода), который будет другим базовым классом.
Ответ №3:
Поскольку я упоминал в своем посте, что вы могли бы решить эту проблему с помощью метаклассов, вот решение, использующее их. После определения классов в моем исходном сообщении вы можете сделать следующее:
from abc import ABCMeta
class Loud(ABCMeta):
def __new__(metacls, name, bases, namespace):
instance = super().__new__(metacls, name, bases, namespace)
if not issubclass(instance, Animal):
raise AttributeError("The base class must derive from Animal.")
def sound(self):
return super(instance, self).sound().upper()
instance.sound = sound
return instance
class LoudCat(Cat, metaclass=Loud):
pass
class LoudDog(Dog, metaclass=Loud):
pass
class LoudCow(Cow, metaclass=Loud):
pass
Теперь мы получаем:
>>> LoudCat().sound()
MEOW
>>> LoudDog().sound()
WOOF
>>> LoudCow().sound()
MOO