【问题标题】:TypeError when extending a class with additional attributes扩展具有附加属性的类时出现 TypeError
【发布时间】:2019-11-12 23:57:39
【问题描述】:

我想用一些额外的属性扩展标准 python bytes 类,这样我就可以像处理任何普通的 bytes objext 一样处理它们(例如,将它们放入列表并对其进行排序)。

因此,我创建了自己的类,该类继承自 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

那么我在继承和属性扩展方面做错了什么?

【问题讨论】:

    标签: python python-3.x inheritance


    【解决方案1】:

    您需要覆盖 __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 会接受的其他参数(encodingerrors
    • __str__ 仅在您不覆盖 __repr__ 时有效。
    • MyByte 的实例现在是可变的;根据您的需要,您可以避免x 可以更改(使用property)并为该类设置__slots__ 属性 - 或提供考虑x__hasĥ__ 方法。

    这也可以:

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

    【讨论】:

    • @ReblochonMasque 我想说的是:bytes 是不可变的;因此在调用__init__ 时初始化实例为时已晚。
    • 哦,这个答案可能更中肯:stackoverflow.com/a/2673863/4954037
    【解决方案2】:

    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__ 创建状态(“initializer”)

    值得注意的是,两者都在创建对象时被调用。你可以考虑构造一个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

    【讨论】:

    • 谢谢。难道__new__在所有教python的文献中通常都被遗漏了吗?以前从未听说过它在对象创建中的作用。
    • 教材一般只关注子类object,因为它是最常见的。 Python 对象模型中更复杂的部分通常会被忽略。您可以在文档 (docs.python.org/3/reference/datamodel.html#object.__new__) 中找到更多信息,如果您专门寻找 __new__,还有一些教程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-13
    • 2021-12-04
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多