将用户选择的方法添加到 python 中的元数据库 class

Add a user selected method to a meta base class in python

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

由于我的 printfuture 方法对我所有派生的 class 都是通用的,我认为用我的 __new__ 方法捕获变体的用户参数是合适的base class 并将方法分配给 base class 本身。如下例所写:

# 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 !

我知道我已经覆盖了我的基础 class 并且我的第一个对象改变了它的行为。所以,我的主要问题是:我怎样才能实现预期的行为,同时让我的自定义方法远离基础 class?

也欢迎就此类问题的最佳实践和适当设计提出意见。

我认为问题出在基地 class。

当您使用 BaseFruit 并将其用作 Apple-Class 的基础 class 时,python 将分配 Apple-Class 中存在的任何值和 BaseFruit-Class 直接到 BaseFruit Class。由于 'apples' 都基于相同的 Base Class,因此它们共享来自此 class 的值。

当您将 saddestfuture 设置为要执行的函数时,您将基于 BaseFruit-Class.

的所有对象设置为 'globally'

你需要一条线

self.userm = usermethod

__init__apple。然后将此 self.userm 作为 kwarg 而不是字符串 "usermethod".

传递给 BaseClass

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

"expected" 行为确实是实际打印的内容。所以,行为不是什么"you were expecting",这是另一回事。让我们看看原因:

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

因此,当您创建 Apple 的第二个实例时,调用 badapple = Apple(aspect='rotten', usermethod=saddestfuture) 只是使 saddestfuture 成为 Apple [=51] 的 printfuture =] 是 saddestfuture,不仅仅是 badapple 的方法,而是 Apple 的任何实例。

不需要元的修复class - 您可以使用 __new__ 本身中的代码创建一个 "pseudomethod",而不是附加到实例 - 如您所愿。但是您必须在实例上执行此操作,在实例创建之后,当您引用实例时,而不是在实例化之前,当您仅引用 class 本身时。

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

...
# 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

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

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