【问题标题】:Customizing functions across a Python class hierarchy跨 Python 类层次结构自定义函数
【发布时间】:2021-02-18 05:29:21
【问题描述】:

我有一个无法直接编辑其定义的类层次结构。我想包装层次结构中的每个类,以便自定义一些功能。

举一个非常简单的例子,假设我们有一个动物等级:

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)

实现这一目标的最佳/最 Pythonic 方式是什么?语法不必与上面完全相同。我在想元类在这里可能有用,但我对这些没有太多经验,我也会对其他解决方案感到满意。

【问题讨论】:

  • 您可以将其用作混音 - class LoudDog(Loud, Dog): pass

标签: python class metaclass


【解决方案1】:

你可以试试这个方法,它会根据给定的类创建一个新类,并调整它的“声音”方法

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"

【讨论】:

  • 在混合解决方案中,我是否必须替换这样的功能,或者我可以以某种方式使用super
  • 由于Loud 类没有“超级”,你不能。这是一种 hack,因为它依赖于已经具有 sound 方法的 self 对象,如果您尝试在没有 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 将首先定位 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-23
      • 2021-01-10
      • 1970-01-01
      • 1970-01-01
      • 2014-11-12
      • 1970-01-01
      相关资源
      最近更新 更多