将用户选择的方法添加到 python 中的元数据库 class
Add a user selected method to a meta base class in python
我有无法实例化的基础 class(在我的简化示例中为 BaseFruit
)和一些派生的 classes(例如在我的示例中为 Apple
)应该都共享相同的方法 (printfuture
)。但是,它们有很多可能的变体,我想让用户选择应该使用的变体(例如 sadfuture
和 saddestfuture
)。
由于我的 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。
我有无法实例化的基础 class(在我的简化示例中为 BaseFruit
)和一些派生的 classes(例如在我的示例中为 Apple
)应该都共享相同的方法 (printfuture
)。但是,它们有很多可能的变体,我想让用户选择应该使用的变体(例如 sadfuture
和 saddestfuture
)。
由于我的 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.
你需要一条线
self.userm = usermethod
在__init__
的apple
。然后将此 self.userm
作为 kwarg
而不是字符串 "usermethod"
.
我不太清楚这个操作的语法,因为我已经很长时间没有使用 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。