如何在Python中子类化str

发布于 2024-12-02 01:09:18 字数 749 浏览 2 评论 0原文

我正在尝试对 str 对象进行子类化,并向其添加几个方法。我的主要目的是学习如何去做。我陷入困境的是,我是否应该在元类中对 string 进行子类化,并使用该元创建我的类,或者直接对 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__() 方法中创建一个新实例,对吗?

I am trying to subclass str object, and add couple of methods to it. My main purpose is to learn how to do it. Where I am stuck is, am I supposed to subclass string in a metaclass, and create my class with that meta, or subclass str directly?

And also, I guess I need to implement __new__() somehow, because, my custom methods will modify my string object, and will return new mystr obj.

My class's methods, should be completely chainable with str methods, and should always return a new my class instance when custom methods modified it. I want to be able to do something like this:

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

I want to have it all the abilities that a str have. For example, a = mystr("something") then I want to use it like,
a.capitalize().mycustommethod().lower()

It is my understanding that, I need to implement __new__(). I think so because, strings methods would probably try to create new str instances. So , if I overwrite __new__(), They supposedly would return my custom str class. However, I don't know how to pass arguments to my custom class's __init__() method in that case. And I guess I would need to use type() in order to create a new instance in __new__() method right?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

羅雙樹 2024-12-09 01:09:18

如果您想在构造时修改字符串,则覆盖 __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

Overwriting __new__() works if you want to modify the string on construction:

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

But if you just want to add new methods, you don't even have to touch the constructor:

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

Note that the inherited methods, like for example upper() will still return a normal str, not text.

请恋爱 2024-12-09 01:09:18

我正在尝试对 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("某事")
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'>

请参阅 来源 了解更多信息。

I am trying to subclass str object, and add couple of methods to it. My main purpose is to learn how to do it.

UserString was created before it was possible to subclass str directly, so prefer to subclass str, instead of using UserString (as another answer suggests).

When subclassing immutable objects, it's usually necessary to modify the data before you instantiate the object - therefore you need to both implement __new__ and call the parent __new__ (preferably with super, instead of str.__new__ as another answer suggests).

In Python 3, it is more performant to call super like this:

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

__new__ looks like a class method, but it is actually implemented as a static method, so we need to pass cls redundantly as the first argument. We don't need the @staticmethod decorator, however.

If we use super like this to support Python 2, we'll note the redundant cls more clearly:

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

Usage:

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

The complete answer

None of the answers so far does what you've requested here:

My class's methods, should be completely chainable with str methods,
and should always return a new my class instance when custom methods
modified it. I want to be able to do something like this:

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

(I believe you mean isinstance(), not issubclass().)

You need a way to intercept the string methods. __getattribute__ does this.

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 + '!!')

and now:

>>> 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')]

And the case requested works:

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

Response to Comment

Why is the Python 3 only call, i.e. super().method(arg) more performant?

The function already has access to both __class__ and self without doing a global and local lookup:

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

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

See the source for more insight.

热鲨 2024-12-09 01:09:18

我对其他答案的复杂性感到有点震惊,Python 的标准库也是如此。您可以使用 collections.UserString 对字符串进行子类化,而不是弄乱了代理 str 的方法。

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

示例

I'm kinda horrified by the complexity of the other answers, and so is Python's standard library. You can use collections.UserString to subclass string and do not mess with proxying str's methods.

Just subclass it, and add your methods. self.data contains the actual string that is being represented by your object, so you can even implement str-"mutating" methods by reassigning self.data internally.

An example.

她如夕阳 2024-12-09 01:09:18

这是执行您想要的操作的快速技巧:您基本上拦截每个函数调用,并且,如果您看到它返回一个字符串,则将其转换回您自己的类类型。

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

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()

Here's a quick hack to do what you want: you basically intercept every function call, and, if you see that it's returning a string, you convert it back to your own class type.

While this works in this simple example, it has some limitations. Among other things, operators such as the subscript operator are apparently not handled.

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()
九八野马 2024-12-09 01:09:18

您可以尝试类似的方法:

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

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

You can try something like:

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

but you won't be sure that standard methods will return a 'mystr' instance too

吃颗糖壮壮胆 2024-12-09 01:09:18

这是我整理的一个替代方案,它不需要重写 __getattribute__ ,并且通过利用多重继承和 UserString 包装类更简单,但仍然作为 str 的实例传递

from collections import UserString

class Caps(UserString, str):
    def __init__(self, content):
        return super(type(self), self).__init__(content.upper())
    def __repr__(self):
        """A repr is useful for debugging"""
        return f'{type(self).__name__}({super().__repr__()})'
    def _new(self, data):
        """Convenience method"""
        new_obj = type(self).__new__(type(self))
        new_obj.__init__(data)
        return new_obj
    def __add__(self, other):
        other = other.upper()
        return self._new(f"{self.data}{other}")
    def mycustommethod(self): # shout
        return self + '!'
    def myothercustommethod(self): # shout harder
        return self + '!!'

:所以,测试:

In [2]: a = Caps("something")

In [3]: a.lower()
Out[3]: Caps('SOMETHING')

In [4]: a.casefold()
Out[4]: Caps('SOMETHING')

In [5]: a.swapcase()
Out[5]: Caps('SOMETHING')

In [6]: a.index('T')
Out[6]: 4

In [7]: a.strip().split('E')
Out[7]: ['SOM', 'THING']

In [8]: a.lower().mycustommethod().myothercustommethod().capitalize()
Out[8]: Caps('SOMETHING!!!')

In [9]: isinstance(a, Caps)
Out[9]: True

In [10]: isinstance(a, str)
Out[10]: True

在我的例子中,我需要一个具有特殊行为的字符串,但它仍然会作为 str 的实例传递,这样我无法控制的 JSON 编码器就不会抱怨我的对象不可序列化。

Here's an alternative I put together that does not need to override __getattribute__ and is simpler by harnessing multiple inheritance and the UserString wrapper class, but still passes as an instance of str:

from collections import UserString

class Caps(UserString, str):
    def __init__(self, content):
        return super(type(self), self).__init__(content.upper())
    def __repr__(self):
        """A repr is useful for debugging"""
        return f'{type(self).__name__}({super().__repr__()})'
    def _new(self, data):
        """Convenience method"""
        new_obj = type(self).__new__(type(self))
        new_obj.__init__(data)
        return new_obj
    def __add__(self, other):
        other = other.upper()
        return self._new(f"{self.data}{other}")
    def mycustommethod(self): # shout
        return self + '!'
    def myothercustommethod(self): # shout harder
        return self + '!!'

And so, the tests:

In [2]: a = Caps("something")

In [3]: a.lower()
Out[3]: Caps('SOMETHING')

In [4]: a.casefold()
Out[4]: Caps('SOMETHING')

In [5]: a.swapcase()
Out[5]: Caps('SOMETHING')

In [6]: a.index('T')
Out[6]: 4

In [7]: a.strip().split('E')
Out[7]: ['SOM', 'THING']

In [8]: a.lower().mycustommethod().myothercustommethod().capitalize()
Out[8]: Caps('SOMETHING!!!')

In [9]: isinstance(a, Caps)
Out[9]: True

In [10]: isinstance(a, str)
Out[10]: True

In my case, I needed a string with special behaviours but that would still pass as an instance of str so that a JSON encoder I can't control wouldn't carp that my object was not serializable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文