Возможно ли быть виртуальным подклассом встроенного типа?

Можно ли сделать определяемый пользователем тип виртуальным подклассом встроенного типа в python? Я бы хотел, чтобы мой класс считался подклассом int, однако я не хочу наследовать его напрямую следующим образом:

class MyInt(int):
    '''Do some stuff kind of like an int, but not exactly'''
    pass

С тех пор мой класс становится фактически неизменным, хочу я этого или нет. Например, становится невозможным использование таких методов, как __iadd__ и __isub__, так как int не может модифицировать себя. Я мог бы наследовать от numbers.Integral, но тогда, когда кто-то вызывает isinstance(myIntObj, int) или issubclass(MyInt, int), ответ будет False. Я понимаю, что классы с метаклассом ABCMeta могут использовать метод register для регистрации классов в качестве виртуальных базовых классов, которые на самом деле не наследуются от них. Есть ли способ сделать это со встроенными типами? Что-то типа:

registerAsParent(int, MyInt)

Я просмотрел (как в документации по Python, так и в Интернете) и еще не нашел ничего близкого к тому, что я ищу. То, о чем я прошу, просто невозможно?


person eestrada    schedule 05.08.2014    source источник


Ответы (1)


Не уверен, что именно вы пытаетесь сделать, поскольку то, что вы просите, невозможно, поскольку примитивные типы практически неизменяемы. Однако вы можете переопределить __iadd__ и тому подобное, чтобы вернуть результат нужного вам типа. Обратите внимание, что я поменял местами знаки (использовал - вместо +) для драмы.

>>> class MyInt(int):
...     def __iadd__(self, other):
...         return MyInt(self - other)
...     def __add__(self, other):
...         return MyInt(self - other)
... 
>>> i = MyInt(4)
>>> i += 1
>>> type(i)
<class '__main__.MyInt'>
>>> i
3
>>> i + 5
-2
>>> type(i + 5)
<class '__main__.MyInt'>

Промойте и повторите для остальных магических методов, которые вам все равно нужно было бы сделать, чтобы иметь «правильный» подкласс int (даже если «виртуальные» пользователи могут ожидать, что они будут работать определенным образом).

О, да, для расширения (как будто это уже не безумие) используйте self.__class__ вместо результатов

class MyInt(int):
    def __iadd__(self, other):
        return self.__class__(self - other)

Итак, если у нас есть еще один подкласс этого.

>>> class MyOtherInt(MyInt):
...     def __iadd__(self, other):
...         return self.__class__(self + other)
... 
>>> i = MyOtherInt(4)
>>> i += 4
>>> i
8
>>> type(i)
<class '__main__.MyOtherInt'>
person metatoaster    schedule 05.08.2014
comment
Хорошо, да. Я думаю, что это имеет смысл для меня. В основном __iadd__ делает то же, что и обычный int; выполните операцию, а затем замените объект MyInt, на который указывал i, новым объектом MyInt. По какой-то причине я вбил себе в голову, что очень важно модифицировать объект. Но возврат новой копии эффективно делает то же самое. С практической точки зрения это должно работать просто отлично. Однако с чисто теоретической точки зрения мне все еще интересно узнать, знает ли кто-нибудь способ сделать пользовательские типы виртуальными подклассами встроенных типов, неизменяемыми или нет. - person eestrada; 06.08.2014
comment
iadd ничего не заменяет — он просто вычисляет и возвращает новый объект. Это остальная часть кода, которая выполняет замену. Имейте в виду, что если мы делаем += b в python, a и b — это просто ссылки на целочисленные объекты. - person Tony Suffolk 66; 06.08.2014
comment
Именно это я имел в виду под заменой. Вместо того, чтобы изменять self, а затем возвращать self (что я надеялся сделать изначально и чего можно было ожидать от вызова метода __iadd__), с ответом метатостера возвращается новый объект, таким образом, объект i, на который указывал, заменяется на новый объект (хотя и того же типа). Извините, если я был неясен. - person eestrada; 07.08.2014
comment
Основная проблема заключается в том, что ints являются неизменяемыми в python, так что вы спрашиваете не имеет смысла/невозможно в Python. - person metatoaster; 07.08.2014
comment
@eestrada На самом деле есть один способ, но абсолютно глупый и, вероятно, сломается - использовать внутренний атрибут для MyInt, обновить __init__, чтобы установить его (а также вызвать int.__init__ для совместимости) и реализовать все операции (и __str__ и __repr__), чтобы вы могли эмулировать мутация на месте, но это хорошо на территории ненужной работы. Спросите себя, почему вы это делаете и насколько это практично для того, чего вы пытаетесь достичь. - person metatoaster; 07.08.2014
comment
@metatoaster, так что в основном использовать композицию и наследование? Я понимаю, что вы имеете в виду под ненужной работой. Я предполагаю, что большую часть времени люди просто используют утиную печать или самоанализ вместо проверки наследования, поэтому вложение такого большого количества работы становится излишним. Скорее всего, если мне действительно нужна иерархия классов, я просто унаследую от numbers.Integral и пойду оттуда. - person eestrada; 14.08.2014
comment
@estrada в каком-то смысле, да. Проверка наследования обычно зарезервирована для очень строгой проверки и обычно является излишней. Обычно шаблон, который я использую в наши дни, представляет собой композицию, а не наследование, особенно когда модель наследования (то есть создание подклассов) вводит ненужную связь, которая затрудняет тестирование моего кода. Рад, что это сработало для вас в конце концов, и вы поняли эту часть о Python. - person metatoaster; 14.08.2014
comment
ИМО, это не отвечает на вопрос ОП, как указано в заголовке вопроса. - person martineau; 18.09.2019