使用元类来覆盖复杂内置的方法

时间:2012-05-27 01:27:18

标签: python metaprogramming metaclass complex-numbers

作为一个学习练习,我正在尝试实现一个类,它将模拟python complex内置的行为,但具有__str____repr__方法的不同行为:I希望他们以格式打印...

(1.0,2.0)

...而不是:

(1+2j)

我首先尝试简单地从complex进行子类化并重新定义__str____repr__,但这有一个问题,即当调用非重写方法时,标准complex是返回,并以标准格式打印:

>>> a = ComplexWrapper(1.0,1.0)
>>> a
(1.0,1.0)
>>> b = ComplexWrapper(2.0,3.0)
>>> b
(2.0,3.0)
>>> a + b
(3+4j)

当所需输出为(3.0,4.0)时。

我正在阅读有关元类的内容,并认为它们可以解决我的问题。从Python Class Decorator中的答案开始,我目前的实施如下:

def complex_str(z):
    return '(' + str(z.real) + ',' + str(z.imag) + ')'
def complex_repr(z):
    return '(' + repr(z.real) + ',' + repr(z.imag) + ')'

class CmplxMeta(type):
    def __new__(cls, name, bases, attrs):
        attrs['__str__'] = complex_str
        attrs['__repr__'] = complex_repr
        return super(CmplxMeta, cls).__new__(cls, name, bases, attrs)

class ComplexWrapper(complex):
    __metaclass__ = CmplxMeta

不幸的是,这似乎与前一个解决方案具有相同的行为(例如,当两个ComplexWrapper个实例互相添加时)。

我承认,我不完全理解元类。也许我的问题可以用不同的方式解决?

当然,我可以手动重新定义相关方法,例如__add____subtract__等。但这样会非常重复,所以我更喜欢更优雅的解决方案。

任何帮助表示感谢。


编辑:对agf答案的回应:

我对你的代码有很多不了解的事情:

  1. __new__元类的ReturnTypeWrapper方法从何处获取参数?如果它们是自动传递的,我希望在这种情况下name = "Complex", bases = (complex), dict = {}。那是对的吗?这种自动传递特定于元类的类数据的方法是什么?

  2. 你为什么用?     cls = type.__new__(mcs, name, bases, dct)代替     cls = type(mcs, name, bases, dct)? 是否只是为了避免与type()

  3. 的“其他含义”混淆
  4. 我复制了您的代码,并在__str__课程中添加了__repr__ComplexWrapper的特殊实现。但它不起作用;打印任何类型Complex的对象只是以标准Python格式打印。我不明白,因为这两个方法应该在元类的for循环中被选中,但是之后应该被我的定义覆盖。

  5. 我的代码的相关部分:

    class Complex(complex):
        __metaclass__ = ReturnTypeWrapper
        wrapped_base = complex
        def __str__(self):
            return '(' + str(self.real) + ',' + str(self.imag) + ')'
        def __repr__(self):
            return '(' + repr(self.real) + ',' + repr(self.imag) + ')'
    

    及其行为:

    >>> type(a)
    <class 'Cmplx2.Complex'>
    >>> a.__str__
    <bound method Complex.wrapper of (1+1j)>
    >>> a.__str__()
    '(1+1j)'
    >>> 
    

    再次感谢您的回答,如果您在答案中提及,请随时编辑/删除上述内容!

1 个答案:

答案 0 :(得分:8)

您目前的做法不起作用。如何定义类不是问题 - complex的方法是在调用它们时创建complex的新实例,而不是使用输入对象的type。您将始终返回complex而不是ComplexWrapper的实例,因此不会调用您的自定义方法:

>>> type(ComplexWrapper(1.0,1.0) + ComplexWrapper(2.0,3.0))
<type 'complex'>

相反,您需要转换complex方法返回的新complex对象,以返回派生类的对象。

此元类包装指定基类的所有方法,并将包装的方法附加到类。包装器检查要返回的值是否是基类的实例(但不包括子类的实例),如果是,则将其转换为派生类的实例。

class ReturnTypeWrapper(type):
    def __new__(mcs, name, bases, dct):
        cls = type.__new__(mcs, name, bases, dct)
        for attr, obj in cls.wrapped_base.__dict__.items():
            # skip 'member descriptor's and overridden methods
            if type(obj) == type(complex.real) or attr in dct:
                continue
            if getattr(obj, '__objclass__', None) is cls.wrapped_base:
                setattr(cls, attr, cls.return_wrapper(obj))
        return cls

    def return_wrapper(cls, obj):
        def convert(value):
            return cls(value) if type(value) is cls.wrapped_base else value
        def wrapper(*args, **kwargs):
            return convert(obj(*args, **kwargs))
        wrapper.__name__ = obj.__name__
        return wrapper

class Complex(complex):
    __metaclass__ = ReturnTypeWrapper
    wrapped_base = complex
    def __str__(self):
        return '({0}, {1})'.format(self.real, self.imag)
    def __repr__(self):
        return '{0}({1!r}, {2!r})'.format(self.__class__.__name__, 
                                          self.real, self.imag)


a = Complex(1+1j)
b = Complex(2+2j)

print type(a + b)

请注意,这不会包装__coerce__特殊方法,因为它会返回tuplecomplex个;如果需要,可以很容易地将包装器转换为查看内部序列。

未绑定方法的__objclass__属性似乎没有文档,但它指向定义方法的类,所以我用它来过滤掉除了我们要转换的类之外的类上定义的方法。我也在这里使用它来过滤掉不是未绑定方法的属性。