Использование метаклассов для переопределения методов комплексного построения

В качестве учебного упражнения я пытаюсь реализовать класс, который будет эмулировать поведение complex встроенного complex python, но с различным поведением методов __str__ и __repr__ : я хочу, чтобы они печатались в формате …

 (1.0,2.0) 

…вместо:

 (1+2j) 

Сначала я попробовал просто подклассифицировать из complex и переопределить __str__ и __repr__ , но у этого есть проблема, что при __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__ и т. Д. Но это было бы очень повторяющимся, поэтому я предпочел бы более элегантное решение.

Любая помощь оценивается.


EDIT: Ответ на ответ agf:

Поэтому ряд вещей, которые я не понимаю о вашем коде:

  1. Где метод ReturnTypeWrapper метакласса ReturnTypeWrapper получает свои аргументы? Если они будут переданы автоматически, я бы ожидал в этом случае, что name = "Complex", bases = (complex), dict = {} . Это верно? Является ли этот метод автоматической передачи данных класса конкретным для метаклассов?

  2. Почему вы используете cls = type.__new__(mcs, name, bases, dct) вместо cls = type(mcs, name, bases, dct) ? Следует ли просто избегать путаницы с «другим значением» type() ?

  3. Я скопировал ваш код и добавил свои специальные реализации __str__ и __repr__ в свой класс ComplexWrapper . Но это не работает; печать любого объекта типа 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)' >>> 

Еще раз спасибо за ваш ответ и не стесняйтесь редактировать / удалять выше, если вы обратитесь к ним в своем ответе!

    Ваш текущий подход не будет работать. Как вы определяете свой класс, это не проблема. Методы 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__ , так как он возвращает tuple complex s; обертка может быть легко преобразована для просмотра последовательностей, если это необходимо.

    __objclass__ несвязанных методов кажется недокументированным, но он указывает на класс, на котором определен метод, поэтому я использовал его для фильтрации методов, определенных на классах, отличных от того, с которого мы конвертируем. Я также использую его здесь для фильтрации атрибутов, которые не являются несвязанными методами.