Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/359.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中以通用方式为所有子类实现_neg__Python_Metaprogramming - Fatal编程技术网

在Python中以通用方式为所有子类实现_neg_

在Python中以通用方式为所有子类实现_neg_,python,metaprogramming,Python,Metaprogramming,我为这个相当长的问题提前道歉 我正在实现可调用对象,希望它们的行为有点像(数学)函数。我有一个基类,它的\uuu call\uu方法会引发未实现的错误,因此用户必须子类来定义\uu call\uu。我的问题是:如何在基类中定义特殊的方法\uuuuuneg\uuuuu,使子类立即具有预期的行为,而不必在每个子类中都实现\uuuuuuneg\uuuu?我对预期行为的感觉是,如果f是基类(子类)的一个实例,并且有一个正确定义的\uuu调用

我为这个相当长的问题提前道歉

我正在实现可调用对象,希望它们的行为有点像(数学)函数。我有一个基类,它的
\uuu call\uu
方法会引发
未实现的错误
,因此用户必须子类来定义
\uu call\uu
。我的问题是:如何在基类中定义特殊的方法
\uuuuuneg\uuuuu
,使子类立即具有预期的行为,而不必在每个子类中都实现
\uuuuuuneg\uuuu
?我对预期行为的感觉是,如果
f
是基类(子类)的一个实例,并且有一个正确定义的
\uuu调用
,那么
-f
应该是与
f
相同的类的一个实例,拥有与
f
相同的所有属性,除了
\uu调用
,它应该返回
f
\uuuuu call\uuuu
的负数

这里有一个例子来说明我的意思:

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
虽然这不完全是我想要的,因为
减1
减1
不是同一类的实例(但我可以接受)

现在,我希望新实例
减去_one
继承
one
的所有属性和方法;只有
\u调用\u
方法应该更改。所以我可以把
\uuuu neg\uuuu
改为

    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
这似乎奏效了。我的问题是:这一策略是否有缺点?实现一个通用的
\uuuu neg\uuuu
必须是一个标准的练习,但我在网上找不到任何东西。是否有推荐的替代方案


提前感谢您的评论。

您可以在实例上保留一个标志,说明调用结果是否应该被否定,而不是创建新类型。然后,您可以将实际的可重写调用行为卸载到一个单独的(非特殊)方法中,作为您自己协议的一部分

class Base(object):
    def __init__(self):
        self._negate_call = False

    def call_impl(self, *args, **kwargs):
        raise NotImplementedError

    def __call__(self, *args, **kwargs):
        result = self.call_impl(*args, **kwargs)
        return -result if self._negate_call else result

    def __neg__(self):
        other = copy.copy(self)
        other._negate_call = not other._negate_call
        return other

你的方法有几个缺点。一个例子是,您将原始实例的所有成员复制到新实例中——如果您的类重写了除
\uuuuu call\uuuuu
以外的任何特殊方法,这将不起作用,因为特殊方法仅在隐式调用时在对象类型的字典中查找。此外,它复制了许多实际上从
对象继承的内容,而不需要进入实例的
\uuuuuu dict\uuuu

满足您确切需求的一种更简单的方法是将新类型作为实例原始类型的子类。这可以通过在
\uuu neg\uuu()方法中定义一个本地类来实现:

def __neg__(self):
    class Neg(self.__class__):
        def __call__(self_, *args, **kwargs):
            return -self(*args, **kwargs)
    neg = Base.__new__(Neg)
    neg.__dict__ = self.__dict__.copy()
    return neg
这定义了一个从原始函数类型派生的新类
Neg
,并覆盖了它的
\uuuuu调用方法()。它使用
Base
的构造函数创建该类的实例——这是为了说明
self
的类将采用构造函数参数的情况。最后,我们将直接存储在实例
self
中的所有内容复制到新实例中

如果我要设计这个系统,我会采取完全不同的方法。我会为一个函数修复接口,并且只会为每个函数依赖这个固定接口。我不会费心将实例的所有属性复制到否定函数,而是这样做:

class Function(object):
    def __neg__(self):
        return NegatedFunction(self)
    def __add__(self, other):
        return SumFunction(self, other)

class NegatedFunction(Function):
    def __init__(self, f):
        self.f = f
    def __call__(self, *args, **kwargs):
        return -self.f(*args, **kwargs)

class SumFunction(Function):
    def __init__(self, *funcs):
        self.funcs = funcs
    def __call__(self, *args, **kwargs):
        return sum(f(*args, **kwargs) for f in self.funcs)

这种方法不能满足您的要求,即
\uuu neg\uuu()
返回的函数具有原始函数的所有属性和方法,但我认为就设计而言,这一要求相当可疑。我认为放弃这个要求会给你一个更干净、更通用的方法(如上面的例子中包含一个
\uuu add\uuu()
操作符所示)。

你遇到的基本问题是
\uuuuxxx\uucode>方法只在类上查找,这意味着同一类的所有实例将使用相同的
\uuuuuxxx\uuuuu
方法。这建议使用类似Cat Plus建议的方法;但是,您也不希望用户不得不担心更多的特殊名称(例如
\u call\u impl
\u negate

如果你不介意元类可能具有的融心能力,那么这就是要走的路。元类可以自动添加
\u negate
属性(并命名它以避免冲突),还可以使用用户编写的
\u call\u
,并将其重命名为
\u call
,然后创建一个新的
\u call\u
,调用旧的
\u call\u
(现在称为
\u 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
中的大部分代码和复杂性是由于重新编写了
\uuuuu call\uuuuuu
以保留调用签名并使内省有用。。。因此,不要在帮助中看到
\uuuuuuu调用(*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__

是的,这更简单。我想知道它是否可以在不引入额外属性的情况下通用地完成。同样在你的
\uuuu neg\uuuuu
中,我想
其他。\u negate\u call=True
应该是
其他。\u negate\u call=not self。\u negate\u call
。否则,
-(-one)
one
不一样@Dominique:是的,我在那里有,但我的大脑在这个时候几乎不能工作。谢谢!这是优雅的。关于更一般的设计,你是对的。我发现
f
-f
是不同类型的,这有点令人不满意。这使得通过
-f
(例如,其域)访问
f
的属性非常困难,但是可以解决这个问题。
NegatedFunction
充当原始函数的代理。所有接口方法都需要转发