将用户选择的方法添加到 python 中的元基类

Posted

技术标签:

【中文标题】将用户选择的方法添加到 python 中的元基类【英文标题】:Add a user selected method to a meta base class in python 【发布时间】:2018-05-10 01:15:23 【问题描述】:

我有无法实例化的基类(在我的简化示例中为BaseFruit)和一些派生类(例如在我的示例中为Apple)应该共享相同的方法(printfuture)。但是,它们有很多可能的变体,我想让用户选择应该使用什么变体(例如sadfuturesaddestfuture)。

由于我的printfuture 方法对我的所有派生类都是通用的,我认为应该使用基类的__new__ 方法捕获变体的用户参数并将该方法分配给基类本身。如下例所示:

# my custom methods

def sadfuture(self):
    """A sad future"""
    print 'It looks !'.format(self.aspect)
    print 'Mmm yummy !'

def saddestfuture(self):
    """A saddest future"""
    print 'It looks '.format(self.aspect)
    print 'Garbage !'

# my base class

class BaseFruit(object):
    """Fruit base class"""
    def __new__(cls, *args, **kwargs):
        setattr(cls, 'printfuture', kwargs['usermethod'])
        return object.__new__(cls)

# My class

class Apple(BaseFruit):
   """An apple class"""
   def __init__(self, aspect, usermethod=sadfuture):
        self.aspect = aspect


if __name__ == '__main__':
    goodapple = Apple(aspect='delicious', usermethod=sadfuture)
    goodapple.printfuture() # ==> ok
    badapple = Apple(aspect='rotten', usermethod=saddestfuture)
    goodapple.printfuture() # ==> not ok anymore
    badapple.printfuture() # ==> ok

哪些打印:

It looks delicious!
Mmm yummy !
It looks delicious
Garbage !
It looks rotten
Garbage !

而不是预期的行为:

It looks delicious!
Mmm yummy !
It looks delicious!
Mmm yummy !
It looks rotten
Garbage !

我明白我已经覆盖了我的基类并且我的第一个对象已经改变了它的行为。所以,我的主要问题是:如何在将自定义方法排除在基类之外的同时实现预期行为?

也欢迎就此类问题的最佳做法和适当设计发表评论。

【问题讨论】:

请说明预期的行为是什么? @monamona 完成! 我非常怀疑问题出在哪里,但需要一点时间才能输入答案 不要选择方法;为每个方法定义一个子类并让用户选择子类。或者,简单地围绕可能的方法定义一个字典,让“真实”方法简单地从dict 调用用户选择的函数。 您是否正在研究类和对象创建的高级方面并使用已经过时 8 年以上的 Python 2.x 来做到这一点? 【参考方案1】:

“预期”行为确实是实际打印的内容。因此,行为不是“您所期望的”,这是另一回事。让我们看看为什么:

每次创建 Apple 的新实例时,您所做的是在实例化类(在本例中为 Apple)上创建一个新方法。每次创建 BaseFruit 或其任何子类的新实例时,setattr(cls, 'printfuture', kwargs['usermethod']) 行就是这样做的。 (顺便说一句,这行可以简单地是cls.printfuture = kwargs['usermethod'],如果属性名称是硬编码的,则不需要setattr)。

因此,当您创建Apple 的第二个实例时,调用badapple = Apple(aspect='rotten', usermethod=saddestfuture) 只需将Apple 类的printfuture 设置为saddestfuture,而不仅仅是badapple 的方法,但对于 Apple 的任何实例。

修复不需要元类 - 您可以使用 __new__ 本身中的代码创建一个附加到实例的“伪方法” - 如您所愿。但是你必须在实例上做这件事,在它被创建之后,当你引用实例时,而不是在实例化之前,当你只有对类本身的引用时。

由于在实例化类之前没有需要运行的实际代码,您不妨在__init__ 中绑定类似方法的函数,并在真正需要时保留自定义__new__。同时,使用super 而不是硬编码超类的调用不会有什么坏处:

...
# my base class

class BaseFruit(object):
    """Fruit base class"""
    def __init__(self, *args, **kwargs):
        printfuture = kwargs.pop('usermethod')
        super(BaseFruit, self).__init__(*args, **kwargs)
        # Wrap the call to the passed method in a function
        # that captures "self" from here, since Python do not 
        # insert "self" in calls to functions 
        # attributed to instances.
        self.printfuture = lambda: printfuture(self)


# My class

class Apple(BaseFruit):
   """An apple class"""
   def __init__(self, aspect, usermethod=sadfuture):
        super(Apple, self).__init__(usermethod)
        self.aspect = aspect

对于元类,这不需要它们 - 相反,您必须在创建每个实例时对其进行自定义。当我们必须自定义类本身时,我们通常会使用元类。您的原始代码正在执行此操作(自定义类本身),但是在创建每个实例时运行该代码,这导致了您不期望的行为。如果在元类__new__ 方法上创建printfuture 方法的代码与在超类中不同,那只会发生一次,当每个子类被声明时(并且该子类的所有实例都将共享相同的printifuture 方法)。

现在,一旦您掌握了它的工作原理,请转到 Python 3 继续学习这些东西。 Python 2 将在 2 年后完全结束,并且在任何项目中都将毫无用处。一件事是必须将遗留代码保留在 Python 2 中,另一件事是学习或开始新项目 - 你应该只使用 Python 3。

【讨论】:

【参考方案2】:

我认为问题出在基类上。

当您使用 BaseFruit 并将其用作 Apple-Class 的基类时,python 会将 Apple-Class 和 BaseFruit-Class 中存在的任何值直接分配给 BaseFruit 类。由于两个“苹果”都基于相同的基类,它们共享来自该类的值。

当您将 saddestfuture 设置为要执行的函数时,您将其设置为基于 BaseFruit-Class 的所有对象的“全局”。

你需要一条线

self.userm = usermethod

apple__init__ 中。然后你将这个self.userm 作为kwarg 传递给BaseClass,而不是字符串"usermethod"

我不太清楚这个操作的语法,因为我很长时间没有使用 python 继承规则,我承认我忘记了语法。也许其他人可以在评论中提出代码,或者您自己发现:-)。

【讨论】:

以上是关于将用户选择的方法添加到 python 中的元基类的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python 中使用元类来增加或覆盖添加到类中的方法

将监听器添加到 Java 类中的方法

如何将对象添加到类中的静态向量

Flash as3 将一个类中的方法调用到另一个放在舞台上的类。

python中方法和属性之间的区别

react篇章-React State(状态)-将生命周期方法添加到类中