10

学習課題として、Python のビルトインの動作をエミュレートするクラスを実装しようとしていますが、メソッドとメソッドcomplexの動作が異なります。__str____repr__

(1.0,2.0)

...それ以外の:

(1+2j)

最初に単純に and をサブクラス化し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

残念ながら、これは以前のソリューションと同じ動作をしているようです (たとえば、2 つの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. 私はあなたのコードをコピーし、あなたのクラスに__str__との特別な実装を追加しました。しかし、うまくいきません。タイプの任意のオブジェクトを印刷すると、標準の Python 形式で印刷されます。メタクラスの for ループで 2 つのメソッドを取得する必要がありましたが、後で定義によってオーバーライドする必要があったため、私には理解できません。__repr__ComplexWrapperComplex

私のコードの関連セクション:

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)'
>>> 

回答に感謝します。回答でそれらに対処する場合は、上記を自由に編集/削除してください!

4

1 に答える 1

9

Your current approach won't work. How you define your class isn't the issue -- the methods of complex are creating new instances of complex when you call them, rather than using the type of the input objects. You'll always get back instances of complex rather than ComplexWrapper, so your customized methods won't be called:

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

Instead, you need to convert the new complex objects returned by the methods of complex to return objects of the derived class.

This metaclass wraps all the methods of the specified base class and attaches the wrapped methods to the class. The wrapper checks if the value to be returned is an instance of the base class (but excluding instances of subclasses), and if it is, converts it to an instance of the derived class.

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)

Note that this won't wrap the __coerce__ special method, since it returns a tuple of complexs; the wrapper can easily be converted to look inside sequences if necessary.

The __objclass__ attribute of unbound methods seems to be undocumented, but it points to the class the method is defined on, so I used it to filter out methods defined on classes other than the one we're converting from. I also use it here to filter out attributes that aren't unbound methods.

于 2012-05-27T02:47:03.013 に答える