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

Posted

技术标签:

【中文标题】Python如何扩展`str`并重载其构造函数? [复制]【英文标题】:Python how to extend `str` and overload its constructor? [duplicate] 【发布时间】:2015-07-14 17:20:48 【问题描述】:

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

我已尝试以 Google 为我解决的多种方式扩展 str 类。当我遇到这个时我放弃了;

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)

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

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

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

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

只有一个参数传递给cls(...) 调用,正如 str 类的错误所要求的,我明白了:

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

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


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

【问题讨论】:

Python 的原始字符串值不会放入 any 属性中。没有“原始字符串”;它的值只是 is 一个字符串,如果它在任何属性中,它将具有与 str 相同的类型。 同时,你需要阅读__new__。这是他们没有教给你的那些东西之一,因为大多数类型只需要一个初始化器,而不是一个构造器……但现在你试图继承一个不可变类型,所以你确实需要一个构造器。 最后,你明确地调用了超级的__init__,没有任何参数。充其量,这会给你一个空字符串。而且由于字符串是不可变的,它将永远为空。您可能不希望这样,但我不确定您 do 想要从您的其余代码中得到什么。 (你确定你甚至想要一个str 子类,而不仅仅是拥有一个str 和通过委派很多方法来实现类似str 的鸭子样式?) 【参考方案1】:

这正是__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__ 看不到您的额外参数,所以您仍然 em> 需要覆盖它,只是为了隐藏那些参数。


同时,你问:

str 的原始字符串值放入什么属性?

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

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

但是,作为str 的子类,如果你想知道你作为字符串的值,那就是self。毕竟,这就是成为子类的全部意义所在。


所以:

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 成为一种不可变的、仅在构造期间设置的值,则将其作为初始化器在概念上更有意义,就像任何普通类一样。


同时:

我想尽可能少地重载 str 类

这可能无法满足您的要求。例如,str.__add__str.__getitem__ 将返回 str,而不是您的子类的实例。如果这很好,那么你就完成了。如果没有,您将不得不重载所有这些方法并更改它们以使用适当的元数据包装返回值。 (您可以通过编程方式执行此操作,方法是在类定义时生成包装器,或者使用动态生成包装器的__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

【讨论】:

【参考方案2】:

代替__init__试试新的:

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

【讨论】:

以上是关于Python如何扩展`str`并重载其构造函数? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何从同一个类中的另一个构造函数调用抽象类的构造函数(方法重载)[重复]

Python:float的子类可以在其构造函数中获取额外的参数吗?

为什么类的拷贝构造参数加引用重载赋值函数的返回值和参数加引用

扩展函数和运算符重载

Python3面向i对象编程实例

运算符重载