【问题标题】:Using metaclasses to override methods of complex builtin使用元类覆盖复杂的内置方法
【发布时间】:2023-03-14 23:57:01
【问题描述】:

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

(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. ReturnTypeWrapper 元类的__new__ 方法从哪里获取参数?如果它们是自动通过的,我希望在这种情况下name = "Complex", bases = (complex), dict = {}。那是对的吗?这种自动传递类数据的方法是否特定于元类?

  2. 你为什么使用 cls = type.__new__(mcs, name, bases, dct) 而不是 cls = type(mcs, name, bases, dct)? 是否只是为了避免与type()的“其他含义”混淆?

  3. 我复制了您的代码,并在您的 ComplexWrapper 类中添加了我对 __str____repr__ 的特殊实现。但它不起作用;打印Complex 类型的任何对象仅以标准 Python 格式打印。我不明白,因为这两个方法应该在元类的 for 循环中被拾取,但之后应该被我的定义覆盖。

我的代码的相关部分:

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. 你几乎是对的; dct 类似于{'__module__': '__main__', '__metaclass__': &lt;class '__main__.ReturnTypeWrapper'&gt;, 'wrapped_base': &lt;type 'complex'&gt;},一旦创建,dict 将成为类的__dict__ 属性。
  • 2.试试吧。您必须直接调用 __new__ 才能使用自定义元类,否则 Python 会将 type 作为第一个参数放入其中是一种特殊的魔法——这里您希望 ReturnTypeWrapper 作为第一个参数。
  • 3.那是我的错。首先,我什至包装了已被覆盖的方法。那是固定的。其次,complex.realcomplex.imag 是属性/描述符,因此它们被包装了。我对它进行了特殊处理,所以它们不会,但现在,就像__coerce__.imag 返回一个complex,而不是你的子类。如果需要,这也可以解决。
  • 我还添加了__str____repr__ 方法来向您展示如何获得所需的输出,其中__repr__ 修改为eval-能够取回对象(如@ 987654365@ 输出应该是)。
  • 再次感谢,这是一个很大的帮助。 1.和2.我现在很清楚。对于 3.,我认为您的修复比您想象的要好,因为 .imag 实际上返回一个浮点数,所以那里没有问题。稍后我可能会尝试实施强制

标签: python metaprogramming metaclass complex-numbers


【解决方案1】:

您当前的方法行不通。你如何定义你的类不是问题——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__ 特殊方法,因为它返回complexs 的tuple;如果需要,包装器可以很容易地转换为查看内部序列。

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

【讨论】:

  • 非常感谢您的回答。但是,我不确定我是否理解发生了什么。由于 cmets 的长度显然有限,我将编辑问题以澄清。
  • 所以还有两个 cmets 和一个问题: 1. 您在 repr 的定义中有一个缩进错误,StackOverflow 不会让我更正,因为它只是空格。 2.除此之外,它可以正常工作,但是我不明白您为什么要使用 strrepr 中虚部的 abs()。 3. 我想知道你添加的 line wrapper.__name__ = obj.__name__ 行......它只是为了美观吗?
  • @mszep 1. 已修复。 2. 我的错,我以为它返回了complex。 3. 语义和装饰——如果你print Complex.__add__, Complex.__sub__,结果应该是有意义的;没有那条线,他们都是Complex.wrapper
猜你喜欢
  • 2014-11-30
  • 1970-01-01
  • 2012-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-26
  • 2021-01-14
  • 2013-11-09
相关资源
最近更新 更多