Ошибка TypeError при расширении класса дополнительными атрибутами - PullRequest
2 голосов
/ 02 июля 2019

Я хочу расширить стандартный класс python bytes некоторыми дополнительными атрибутами, чтобы я мог обрабатывать их как любой обычный bytes объект (например, помещая их в список и сортируя их).

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

class inheritest(bytes):
    def __init__(self, bs: bytes, x: int = 0):
        print("child class init")
        self.x = x
        super().__init__(bs)


print(inheritest(b'foobar', 3))

Этот подход не работает: я получаю ошибку типа из-за неверной подписи аргумента, передаваемой конструктору bytes, хотя я вызываю ее только в строке 5 с единственным аргументом типа bytes, что должно быть хорошо.
Более того, обратите внимание, что оператор print никогда не выполняется, поэтому конструктор класса inheritest никогда не выполняется, но проверка сигнатуры типа аргумента, вызывающая TypesError, кажется, происходит заранее.,

Traceback (most recent call last):
  File "inheritest.py", line 8, in <module>
    print(inheritest(b'foobar', 3))
TypeError: bytes() argument 2 must be str, not int

Так что же я делаю не так с наследованием и расширением атрибута?

Ответы [ 2 ]

2 голосов
/ 02 июля 2019

вам нужно переопределить __new__, чтобы наследовать от байтов:

class MyBytes(bytes):
    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls, *args, *kwargs)
        return self

Теперь, чтобы расширить, как вы хотели бы, я предлагаю это:

class MyBytes(bytes):
    def __new__(cls, source, x):
        self = super().__new__(cls, source)
        self.x = x
        return self

    def __str__(self):
        return f"<{repr(self)}, {self.x}>"


b = MyBytes(b"", 3)
print(b)  # <b'', 3>
b = MyBytes(b"345", 5)
print(b)  # <b'345', 5>

Примечание:

  • я проигнорировал другие аргументы bytes примет (encoding и errors)
  • __str__ работает только до тех пор, пока вы не переопределите __repr__.
  • экземпляры MyByte теперь могут изменяться; в зависимости от того, что вам нужно, вы можете либо избежать изменения x (с помощью property) и установить атрибут __slots__ для класса - либо предоставить метод __hasĥ__, который учитывает x.

это также будет работать:

class MyBytes(bytes):
    def __new__(cls, source, x):
        self = super().__new__(cls, source)
        return self

    def __init__(self, source, x):
        self.x = x
1 голос
/ 02 июля 2019

TLDR: вы неправильно перезаписываете __init__ вместо __new__.

class XBytes(bytes):
    __slots__ = 'x',  # avoid arbitrary attributes

    def __new__(cls, bs: bytes, x: int = 0):
        # call original __new__ with expected signature
        self = super().__new__(cls, bs)
        self.x = x
        return self

Типы Python канонически имеют два метода, которые используются при построении объекта:

  • __new__ создает объект ("конструктор")
  • __init__ создает состояние («инициализатор»)

Примечательно, что оба они вызываются при создании объекта. Вы можете думать о создании экземпляра Class (например, Class(1, 2, 3)) как эквивалент этого:

def new(cls, *args, **kwargs):
    """same as ``cls(*args, **kwargs)``"""
    self = cls.__new__(cls, *args, **kwargs)
    if isinstance(self, cls):
       cls.__init__(self, *args, **kwargs)

Обратите внимание, как __new__ создает объект, в то время как __init__ только изменяет его. Для неизменяемых типов необходимо переопределить __new__, поскольку они не могут быть изменены.


Если вы переопределите __init__, подпись __new__ не изменится! При вызове inheritest(b'foobar', 3) аргументы b'foobar', 3 передаются исходному bytes.__new__. Это происходит до того, как будет вызван ваш пользовательский __init__, таким образом, никогда не вызовет print.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...