简体   繁体   English

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

[英]Add a user selected method to a meta base class in python

I have base class that cannot be instanciated ( BaseFruit in my simplified example) and a few derived classes (for instance Apple in my example) that should all share a same method ( printfuture ). 我有一些无法实例化的基类(在简化的示例中为BaseFruit )和一些派生类(例如,在我的示例中为Apple ),它们都应共享同一方法( printfuture )。 However, they are many possible variants, and I would like to let the user choose what variant should be used (for instance sadfuture and saddestfuture ). 但是,它们是许多可能的变体,我想让用户选择应使用哪种变体(例如sadfuturesaddestfuture )。

As my printfuture method is common to all my derived class, I thought that it would be appropriated to catch the user argument for the variant with the __new__ method of my base class and assign the method to the base class itself. 由于我的所有继承类都使用我的printfuture方法,因此我认为使用基类的__new__方法捕获该变体的用户参数,并将该方法分配给基类本身是合适的。 As written in the example below: 如以下示例所示:

# 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

Which prints: 哪些打印:

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

instead of the expected behavior: 而不是预期的行为:

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

I do understand that I have overwritten my base class and my first object has changed its behavior. 我确实知道我已经覆盖了我的基类,并且我的第一个对象已更改其行为。 So, my main question is: how can I achieve the expected behavior while keeping my custom methods out of the base class? 因此,我的主要问题是: 如何在将自定义方法保留在基类之外的同时实现预期的行为?

Comments on best practices and on proper designs for such problems are also welcome. 也欢迎就此类问题的最佳实践和适当设计发表评论。

The "expected" behavior is truly what is actually printed. “预期”行为实际上是实际打印的内容。 So, the behavior is not what "you were expecting", which is a different thing. 因此,行为不是“您所期望的”,而是另一回事。 Let's se why: 让我们来说明为什么:

What you are doing is creating a new method on the instantiated class (in this case, Apple ) each time you mak ea new instance of Apple. 每次创建新的Apple实例时,您正在做的事就是在实例化类(在本例中为Apple )上创建一个新方法。 The line setattr(cls, 'printfuture', kwargs['usermethod']) does exactly that, each time you create a new instance of BaseFruit or any subclass of it. 每次创建新的BaseFruit实例或其任何子类时, setattr(cls, 'printfuture', kwargs['usermethod']) BaseFruit这一点。 (By the way, this line could be simply cls.printfuture = kwargs['usermethod'] , there is no need for setattr if the attribute name is hardcoded). (顺便说一句,这行可能只是cls.printfuture = kwargs['usermethod'] ,如果属性名称是硬编码的,则不需要setattr )。

So, when you create your second instance of Apple , the call badapple = Apple(aspect='rotten', usermethod=saddestfuture) just make saddestfuture the printfuture for the Apple class to be saddestfuture , not just the method for badapple , but for any instance of Apple. 因此,当您创建Apple的第二个实例时,调用badapple = Apple(aspect='rotten', usermethod=saddestfuture)只是使saddestfuture成为Apple类的printfuture成为saddestfuture ,而不仅是badapple的方法,还包括其他任何方法。苹果的实例。

Fixing that has no need for a metaclass - you can use the code in __new__ itself to create a "pseudomethod", attached to the instance instead - as you intend. 不需要元类的修复-您可以根据需要使用__new__本身中的代码创建附加到实例的“伪方法”。 But you have to do that on the instance, after it is created, when you have a reference to the instance, not before instantiation, whenyou just have a reference to the class itself. 但是,您必须在实例创建完实例之后,对实例进行引用时(而不是在实例化之前)对类本身进行引用时,才对实例执行此操作。

Since there is no actual code you need to run on before instatianting the class, you may as well bind the method-like function in __init__ , and leave customizing __new__ just for when it is really needed. 由于没有必要在实例化类之前运行任何实际代码,因此您还可以在__init__绑定类似方法的函数,并保留__new__自定义, __new__仅在实际需要时使用。 And while at that, it won't hurt to use super instead of hardcoding the superclass's call: 同时,使用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

And as for metaclasses, this has no need for them - to the contrary, you have to customize each instance as it is created. 对于元类,不需要它们-相反,您必须在创建每个实例时自定义它们。 We usually make use of metaclasses when we have to customize the class itself. 当我们必须自定义类本身时,通常会使用元类。 Your original code is doing that (customizing the class itself), but the code for that is run when each instance is created, which made for the behavior you were not expecting. 您的原始代码正在执行此操作(自定义类本身),但是在创建每个实例时将运行该代码,从而实现了您所不希望的行为。 If the code to create the printfuture method where on the metaclass __new__ method instead, what is not the same as being in a superclass, that would happen just once, when each subclass is declared (and all instances of that subclass would share the same printifuture method). 如果创建printfuture方法的代码位于元类__new__方法上,而不是超类中的代码,那么在声明每个子类时,这种情况只会发生一次(并且该子类的所有实例将共享相同的printifuture方法)。

Now, once you grasp how this works, please, just move to Python 3 to continue learning this stuff. 现在,一旦您了解了它的工作原理,请转到Python 3继续学习这些内容。 Python 2 will be at complete end of line in 2 years from now, and will be useless in any prokect. Python 2将在2年后完全淘汰,并且在任何情况下都将毫无用处。 One thing is having to keep legacy code in Python 2, another is learning or starting new projects - you should only use Python 3 for that. 一件事是必须将旧代码保留在Python 2中,另一件事是学习或启动新项目-您仅应使用Python 3。

I think that the problem is coming from the base class. 我认为问题出在基层。

When you used the BaseFruit and used it as base class for the Apple-Class, python will assign any value that exist in the Apple-Class and the BaseFruit-Class directly to the BaseFruit Class. 当您使用BaseFruit并将其用作Apple-Class的基类时,python会将Apple-Class和BaseFruit-Class中存在的任何值直接分配给BaseFruit类。 Since both 'apples' are based on the same Base Class, they share the values that come from this class. 由于两个“苹果”都基于相同的基类,因此它们共享来自该类的值。

When you set the saddestfuture as function to be executed you set it kind of 'globally' for all objects based on the BaseFruit-Class. 当您将saddestfuture设置为要执行的功能时,您会为所有基于BaseFruit-Class的对象设置“全局”种类。

You need a line 你需要一条线

self.userm = usermethod

in the __init__ of the apple . apple__init__中。 Then you pass this self.userm to the BaseClass as an kwarg and not the string "usermethod" . 然后,您将此self.userm作为kwarg而不是字符串"usermethod"传递给BaseClass。

I don't know excatly the syntax for this operation as I have not worked with python inheritance rules for a long time and I admit I have forgotten the syntax. 我不完全了解此操作的语法,因为我已经很长时间没有使用python继承规则了,并且我承认我忘记了该语法。 Maybe somebody else can propose code in a comment or you find that out yourself :-) . 也许其他人可以在注释中提出代码,或者您发现自己:-)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM