Python 如何扩展 `str` 并重载其构造函数?

Python how to extend `str` and overload its constructor?

我有一个字符序列,如果您愿意,可以是一个字符串,但我想存储有关字符串来源的元数据。另外我想提供一个简化的构造函数。

我已经尝试过 str class 的扩展方式 Google 可以为我解决的问题。说到这里我放弃了;

class WcStr(str):
    """wc value and string flags"""

    FLAG_NIBBLES = 8 # Four Bytes

    def __init__(self, value, flags):
        super(WcStr, self).__init__()
        self.value = value
        self.flags = flags

    @classmethod
    def new_nibbles(cls, nibbles, flag_nibbles=None):
        if flag_nibbles is None:
            flag_nibbles = cls.FLAG_NIBBLES

        return cls(
            nibbles[flag_nibbles+1:],
            nibbles[:flag_nibbles]
        )

当我注释掉 @classmethod 的 cls() 调用的两个参数时,它给我这个错误:

TypeError: __init__() takes exactly 3 arguments (1 given)

非常典型,参数数量错误,

还有两个参数(如示例代码所示):

TypeError: str() takes at most 1 argument (2 given)

我试过更改 __init__ 的参数,super().__init__ 的参数,似乎都没有改变。

只有一个参数传递给 cls(...) 调用,正如 str class 的错误提示,我得到这个:

TypeError: __init__() takes exactly 3 arguments (2 given)

所以我在这里赢不了,出了什么问题?


Ps 这应该是第二个 post 但是 属性 将 str 的原始字符串值放入什么?我想尽可能少地重载 str class 以将此元数据添加到构造函数中。

而不是 __init__ 试试新的:

def __new__(cls, value, flags):    
    obj = str.__new__(cls, value)
    obj.flags = flags
    return obj    

这正是 __new__ 方法的用途。

在Python中,创建对象实际上有两个步骤。在伪代码中:

value = the_class.__new__(the_class, *args, **kwargs)
if isinstance(value, the_class):
    value.__init__(*args, **kwargs)

这两个步骤称为构建和初始化。大多数类型在构造中不需要任何花哨的东西,所以它们可以只使用默认的 __new__ 并定义一个 __init__ 方法——这就是为什么教程等只提到 __init__.

但是 str 对象是不可变的,所以初始化器不能做设置属性等通常的事情,因为你不能在不可变对象上设置属性。

所以,如果你想改变 str 实际包含的内容,你必须覆盖它的 __new__ 方法,并用你修改过的参数调用超级 __new__

在这种情况下,您实际上并不想这样做……但是您确实想确保str.__new__看不到您的额外参数,所以你仍然需要覆盖它,只是为了隐藏那些参数。


与此同时,您问:

what property does str's raw string value get put into?

没有。重点是什么?它的值是一个字符串,所以你有一个 str 有一个属性是相同的 str 有一个属性等无限。

在幕后,当然,它必须存储一些东西。但那是在幕后。特别是,在 CPython 中,str class 是在 C 中实现的,除其他外,它包含一个 C char * 实际字节数组,用于表示字符串。您不能直接访问它。

但是,作为 str 的子class,如果您想知道字符串形式的值,那就是 self。毕竟,这就是成为子class的全部意义。


所以:

class WcStr(str):
    """wc value and string flags"""

    FLAG_NIBBLES = 8 # Four Bytes

    def __new__(cls, value, *args, **kwargs):
        # explicitly only pass value to the str constructor
        return super(WcStr, cls).__new__(cls, value)

    def __init__(self, value, flags):
        # ... and don't even call the str initializer 
        self.flags = flags

当然你并不真的需要 __init__这里;您可以在 __new__ 中进行初始化和构建。但是,如果您不打算让 flags 成为一个不可变的、仅在构造期间设置的值,那么将其作为初始化程序在概念上更有意义,就像任何普通的 class 一样。


同时:

I'd like to overload as little of the str class as I can

这可能不是你想要的。例如,str.__add__str.__getitem__ 将成为 return 一个 str,而不是您的 subclass 的一个实例。如果那很好,那么你就完成了。否则,您将不得不重载所有这些方法并更改它们以使用适当的元数据包装 return 值。 (您可以通过在 class 定义时生成包装器,或使用 __getattr__ 方法动态生成包装器,以编程方式执行此操作。)


最后一件事要考虑:str 构造函数不接受一个参数。

可以取0:

str() == ''

而且,虽然这与 Python 2 无关,但在 Python 3 中可能需要 2:

str(b'abc', 'utf-8') == 'abc'

此外,即使它接受 1 个参数,它显然也不一定是字符串:

str(123) == '123'

那么……您确定这是您想要的界面吗?也许您最好创建一个 拥有 字符串(在 self.value 中)的对象,然后明确地使用它。或者甚至通过将大部分或所有 str 方法委托给 self.value?

来隐含地使用它,将鸭子打字作为 str