对于这个相当长的问题,我提前道歉。

我正在实现可调用对象,并希望它们的行为有点像(数学)函数。我有一个基类,其 __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/

10-11 06:47
查看更多