对于这个相当长的问题,我提前道歉。
我正在实现可调用对象,并希望它们的行为有点像(数学)函数。我有一个基类,其 __call__
方法引发 NotImplementedError
,因此用户必须子类化以定义 __call__
。我的问题是:如何在基类中定义特殊方法 __neg__
以便子类立即具有预期的行为,而无需在每个子类中实现 __neg__
?我对预期行为的看法是,如果 f
是具有正确定义的 __call__
的基类(的子类)的实例,那么 -f
应该是与 f
相同的类的实例,拥有与 f
相同的所有属性,除了对于 __call__
,它应该返回 f
的 __call__
的负数。
这是我的意思的一个例子:
class Base(object):
def __call__(self, *args, **kwargs):
raise NotImplementedError, 'Please subclass'
def __neg__(self):
def call(*args, **kwargs):
return -self(*args, **kwargs)
mBase = type('mBase', (Base,), {'__call__': call})
return mBase()
class One(Base):
def __init__(self data):
self.data = data
def __call__(self, *args, **kwargs):
return 1
这具有预期的行为:
one = One()
print one() # Prints 1
minus_one = -one
print minus_one() # Prints -1
虽然这不是我想要的,因为
minus_one
不是与 one
相同的类的实例(但我可以接受)。现在我希望新实例
minus_one
继承 one
的所有属性和方法;只有 __call__
方法应该改变。所以我可以将 __neg__
更改为 def __neg__(self):
def call(*args, **kwargs):
return -self(*args, **kwargs)
mBase = type('mBase', (Base,), {'__call__': call})
new = mBase()
for n, v in inspect.getmembers(self):
if n != '__call__':
setattr(new, n, v)
return new
这似乎有效。我的问题是:这种策略有什么缺点吗?实现通用的
__neg__
必须是标准练习,但我在网上找不到任何内容。有推荐的替代品吗?提前感谢您的任何意见。
最佳答案
您遇到的基本问题是 __xxx__
方法仅在类上查找,这意味着同一类的所有实例都将使用相同的 __xxx__
方法。这建议使用类似于 Cat Plus Plus 建议的方法;但是,您也不希望您的用户担心更特殊的名称(例如 _call_impl
和 _negate
)。
如果您不介意元类可能具有的令人心碎的力量,那么这就是您要走的路。元类可以自动添加 _negate
属性(并将其命名为 mangle 以避免冲突),以及获取用户编写的 __call__
并将其重命名为 _call
,然后创建一个新的 __call__
调用旧的 __call__
(现在称为 _call
; ),然后在返回之前对结果进行否定(如有必要)。
这是代码:
import copy
import inspect
class MetaFunction(type):
def __new__(metacls, cls_name, cls_bases, cls_dict):
result_class = type.__new__(metacls, cls_name, cls_bases, cls_dict)
if '__call__' in cls_dict:
original_call = cls_dict['__call__']
args, varargs, kwargs, defaults = inspect.getargspec(original_call)
args = args[1:]
if defaults is None:
defaults = [''] * len(args)
else:
defaults = [''] * (len(args) - len(defaults)) + list(defaults)
signature = []
for arg, default in zip(args, defaults):
if default:
signature.append('%s=%s' % (arg, default))
else:
signature.append(arg)
if varargs is not None:
signature.append(varargs)
if kwargs is not None:
signature.append(kwargs)
signature = ', '.join(signature)
passed_args = ', '.join(args)
new_call = (
"""def __call__(self, %(signature)s):
result = self._call(%(passed_args)s)
if self._%(cls_name)s__negate:
result = -result
return result"""
% {
'cls_name':cls_name,
'signature':signature,
'passed_args':passed_args,
})
eval_dict = {}
exec new_call in eval_dict
new_call = eval_dict['__call__']
new_call.__doc__ = original_call.__doc__
new_call.__module__ = original_call.__module__
new_call.__dict__ = original_call.__dict__
setattr(result_class, '__call__', new_call)
setattr(result_class, '_call', original_call)
setattr(result_class, '_%s__negate' % cls_name, False)
negate = """def __neg__(self):
"returns an instance of the same class that returns the negation of __call__"
negated = copy.copy(self)
negated._%(cls_name)s__negate = not self._%(cls_name)s__negate
return negated""" % {'cls_name':cls_name}
eval_dict = {'copy':copy}
exec negate in eval_dict
negate = eval_dict['__neg__']
negate.__module__ = new_call.__module__
setattr(result_class, '__neg__', eval_dict['__neg__'])
return result_class
class Base(object):
__metaclass__ = MetaFunction
class Power(Base):
def __init__(self, power):
"power = the power to raise to"
self.power = power
def __call__(self, number):
"raises number to power"
return number ** self.power
和一个例子:
--> square = Power(2)
--> neg_square = -square
--> square(9)
81
--> neg_square(9)
-81
虽然元类代码本身可能很复杂,但生成的对象却非常易于使用。公平地说,
MetaFunction
中的大部分代码和复杂性是由于重写 __call__
以保留调用签名并使内省(introspection)变得有用......所以不是在帮助中看到 __call__(*args, *kwargs)
,而是:Help on Power in module test object:
class Power(Base)
| Method resolution order:
| Power
| Base
| __builtin__.object
|
| Methods defined here:
|
| __call__(self, number)
| raises number to power
|
| __init__(self, power)
| power = the power to raise to
|
| __neg__(self)
| returns an instance of the same class that returns the negation of __call__
关于python - 以通用方式为 Python 中的所有子类实现 __neg__,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7719018/