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



我有无法实例化的基类(在我的简化示例中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 !

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

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

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

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

因此,当您创建第二个Apple实例时,调用badapple = Apple(aspect='rotten', usermethod=saddestfuture)只是saddestfuturesaddestfutureApple类的printfuture,不仅仅是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

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

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

我认为问题来自基类。

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

当您将saddestfuture设置为要执行的函数时,您可以根据 BaseFruit-Class 为所有对象设置"全局"它。

你需要一条线

self.userm = usermethod

apple__init__.然后,将此self.userm作为kwarg传递给 BaseClass,而不是字符串"usermethod"

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

相关内容

  • 没有找到相关文章

最新更新