【问题标题】:How to subclass str in Python如何在 Python 中对 str 进行子类化
【发布时间】:2011-08-31 10:28:28
【问题描述】:

我正在尝试对 str 对象进行子类化,并为其添加几个方法。我的主要目的是学习如何去做。我被卡住的地方是,我应该在元类中对字符串进行子类化,并使用该元类创建我的类,还是直接创建子类 str?

而且,我想我需要以某种方式实现__new__(),因为我的自定义方法将修改我的字符串对象,并返回新的 mystr obj。

我的类的方法,应该完全可以与 str 方法链接,并且当自定义方法修改它时,应该总是返回一个新的我的类实例。我希望能够做这样的事情:

a = mystr("something")
b = a.lower().mycustommethod().myothercustommethod().capitalize()
issubclass(b,mystr) # True

我想拥有str 拥有的所有能力。例如,a = mystr("something") 然后我想像这样使用它, a.capitalize().mycustommethod().lower()

据我了解,我需要实现__new__()。我认为是因为,字符串方法可能会尝试创建新的 str 实例。所以,如果我覆盖__new__(),他们应该会返回我的自定义 str 类。但是,在这种情况下,我不知道如何将参数传递给我的自定义类的 __init__() 方法。而且我想我需要使用type() 才能在__new__() 方法中创建一个新实例,对吧?

【问题讨论】:

  • @yasar11732:正如下面指出的那样,a.capitalize() 将返回一个标准的、未修改的str,而不是您的自定义类,因此a.capitalize().mycustommethod() 将失败。编写几个函数并执行mycustommethod(a.capitalize()).lower()远远更好的编码实践,因为这不会混淆阅读您代码的其他所有人(顺便说一句,“其他所有人”包括“你,两年后")。

标签: python subclassing fluent-interface


【解决方案1】:

如果您想在构造时修改字符串,则覆盖 __new__() 有效:

class caps(str):
   def __new__(cls, content):
      return str.__new__(cls, content.upper())

但如果你只是想添加新方法,你甚至不必接触构造函数:

class text(str):
   def duplicate(self):
      return text(self + self)

请注意,继承的方法(例如 upper())仍将返回正常的 str,而不是 text

【讨论】:

  • 基于 __init__(self, *args, **kwargs) + super() 的更通用的方法正在报告 DeprecationWarning: object.__init__() 没有参数。因此,我认为 __new__(cls, *args, **kwargs) 是一种更好的方法。或者?也许这解释得更好:jfine-python-classes.readthedocs.org/en/latest/…
【解决方案2】:

其他答案的复杂性让我有点害怕,Python 的标准库也是如此。您可以使用collections.UserString 对字符串进行子类化,并且不要混淆代理str 的方法。

只需将其子类化,然后添加您的方法。 self.data 包含由您的对象表示的实际字符串,因此您甚至可以通过在内部重新分配 self.data 来实现 str-"mutating" 方法。

An example.

【讨论】:

【解决方案3】:

我正在尝试对 str 对象进行子类化,并为其添加几个方法。我的主要目的是学习如何去做。

UserString 是在可以直接继承 str 之前创建的,因此更喜欢继承 str,而不是使用 UserString(正如另一个答案所暗示的那样)。

当子类化不可变对象时,通常需要在实例化对象之前修改数据 - 因此您需要同时实现__new__ 并调用父对象__new__(最好使用super , 而不是 str.__new__ 另一个答案建议)。

在 Python 3 中,像这样调用 super 会更高效:

class Caps(str):
    def __new__(cls, content):
        return super().__new__(cls, content.upper())

__new__ 看起来像一个类方法,但它实际上是作为静态方法实现的,所以我们需要将cls 作为第一个参数冗余传递。但是,我们不需要 @staticmethod 装饰器。

如果我们像这样使用super 来支持Python 2,我们会更清楚地注意到多余的cls

class Caps(str):
    def __new__(cls, content):
        return super(Caps, cls).__new__(cls, content.upper())

用法:

>>> Caps('foo')
'FOO'
>>> isinstance(Caps('foo'), Caps)
True
>>> isinstance(Caps('foo'), str)
True

完整答案

到目前为止,没有一个答案能满足您在这里的要求:

我的类的方法,应该完全可以与 str 方法链接, 并且在自定义方法时应该始终返回一个新的我的类实例 修改它。我希望能够做这样的事情:

a = mystr("something")
b = a.lower().mycustommethod().myothercustommethod().capitalize()
issubclass(b,mystr) # True

(我相信你的意思是isinstance(),而不是issubclass()。)

你需要一种拦截字符串方法的方法。 __getattribute__ 这样做。

class Caps(str):
    def __new__(cls, content):
        return super().__new__(cls, content.upper())
    def __repr__(self):
        """A repr is useful for debugging"""
        return f'{type(self).__name__}({super().__repr__()})'
    def __getattribute__(self, name):
        if name in dir(str): # only handle str methods here
            def method(self, *args, **kwargs):
                value = getattr(super(), name)(*args, **kwargs)
                # not every string method returns a str:
                if isinstance(value, str):
                    return type(self)(value)  
                elif isinstance(value, list):
                    return [type(self)(i) for i in value]
                elif isinstance(value, tuple):
                    return tuple(type(self)(i) for i in value)
                else: # dict, bool, or int
                    return value
            return method.__get__(self) # bound method 
        else: # delegate to parent
            return super().__getattribute__(name)
    def mycustommethod(self): # shout
        return type(self)(self + '!')
    def myothercustommethod(self): # shout harder
        return type(self)(self + '!!')

现在:

>>> a = Caps("something")
>>> a.lower()
Caps('SOMETHING')
>>> a.casefold()
Caps('SOMETHING')
>>> a.swapcase()
Caps('SOMETHING')
>>> a.index('T')
4
>>> a.strip().split('E')
[Caps('SOM'), Caps('THING')]

请求的案例有效:

>>> a.lower().mycustommethod().myothercustommethod().capitalize()
Caps('SOMETHING!!!')

回复评论

为什么只调用 Python 3,即 super().method(arg) 更高效?

该函数已经可以访问__class__self,而无需进行全局和本地查找:

class Demo:
    def foo(self):
        print(locals())
        print(__class__)

>>> Demo().foo()
{'self': <__main__.Demo object at 0x7fbcb0485d90>, '__class__': <class '__main__.Demo'>}
<class '__main__.Demo'>

请参阅source 了解更多信息。

【讨论】:

  • 另外,覆盖__str__() 值得一提,因为将self 作为值访问会很快导致无限循环。例如,要始终包含字符串,请使用f'"{super().__str__()}"' 而不是str(super()),这将返回超类描述而不是值
  • 嗨,快速提问,但从测试中我注意到 __getattribute__ 不处理像 __add__ 这样的方法,例如。 Caps('a') + 'b'。想知道是否有人知道如何自动处理这样的算术方法,使其返回 Caps 实例。
  • @rv.kvetch - 看起来你有几个选择 - 你的问题在这里得到了回答,请注意我不保证答案虽然stackoverflow.com/questions/9057669/…
【解决方案4】:

这里有一个快速的技巧来做你想做的事:你基本上拦截每个函数调用,如果你看到它返回一个字符串,你将它转换回你自己的类类型。

虽然这在这个简单的示例中有效,但它有一些限制。除其他外,显然没有处理诸如下标运算符之类的运算符。

class FunWrapper(object):
    def __init__(self, attr):
        self.attr = attr

    def __call__(self, *params, **args):
        ret = self.attr(*params, **args)
        if type(ret) is str:
            return Foo(ret)
        return ret

class Foo(object):
    def __init__(self, string):
        self.string = string

    def __getattr__(self, attr):
        return FunWrapper(getattr(self.string, attr))

    def newMethod(self):
        return "*%s*" % self.string.upper()


f = Foo('hello')
print f.upper().newMethod().lower()

【讨论】:

  • 可以使用__getitem__处理下标,其他运算符也有神奇的方法。
【解决方案5】:

你可以试试这样的:

class mystr(str):
    def new_method(self):
        pass

但您不确定标准方法是否也会返回“mystr”实例

【讨论】:

    猜你喜欢
    • 2016-02-20
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 2020-12-18
    • 2014-02-28
    • 1970-01-01
    • 2020-12-16
    • 1970-01-01
    相关资源
    最近更新 更多