【发布时间】: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 的回答:
所以我对你的代码有很多不明白的地方:
ReturnTypeWrapper元类的__new__方法从哪里获取参数?如果它们是自动通过的,我希望在这种情况下name = "Complex", bases = (complex), dict = {}。那是对的吗?这种自动传递类数据的方法是否特定于元类?你为什么使用
cls = type.__new__(mcs, name, bases, dct)而不是cls = type(mcs, name, bases, dct)? 是否只是为了避免与type()的“其他含义”混淆?我复制了您的代码,并在您的
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__': <class '__main__.ReturnTypeWrapper'>, 'wrapped_base': <type 'complex'>},一旦创建,dict将成为类的__dict__属性。 -
2.试试吧。您必须直接调用
__new__才能使用自定义元类,否则 Python 会将type作为第一个参数放入其中是一种特殊的魔法——这里您希望ReturnTypeWrapper作为第一个参数。 -
3.那是我的错。首先,我什至包装了已被覆盖的方法。那是固定的。其次,
complex.real和complex.imag是属性/描述符,因此它们被包装了。我对它进行了特殊处理,所以它们不会,但现在,就像__coerce__、.imag返回一个complex,而不是你的子类。如果需要,这也可以解决。 -
我还添加了
__str__和__repr__方法来向您展示如何获得所需的输出,其中__repr__修改为eval-能够取回对象(如@ 987654365@ 输出应该是)。 -
再次感谢,这是一个很大的帮助。 1.和2.我现在很清楚。对于 3.,我认为您的修复比您想象的要好,因为 .imag 实际上返回一个浮点数,所以那里没有问题。稍后我可能会尝试实施强制。
标签: python metaprogramming metaclass complex-numbers