package__ org language index docs python subclassing fluent-interface

org - Cómo subclase str en Python



python eq (5)

Estoy intentando subclasificar el objeto str, y añadirle un par de métodos. Mi principal objetivo es aprender a hacerlo. Donde estoy atascado es, ¿se supone que debo subclasificar una cadena en una metaclase y crear mi clase con esa meta, o subclase str directamente?

Y también, creo que necesito implementar __new__() alguna manera, porque mis métodos personalizados modificarán mi objeto de cadena y devolverán el nuevo mystr obj.

Los métodos de mi clase, deben ser completamente desechables con los métodos str, y siempre deben devolver una nueva instancia de mi clase cuando los métodos personalizados la modifiquen. Quiero poder hacer algo como esto:

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

Quiero tener todas las habilidades que tiene un str . Por ejemplo, a = mystr("something") entonces quiero usarlo como, a.capitalize (). Mycustommethod (). Lower ()

Tengo entendido que debo implementar __new__() . Creo que sí, porque los métodos de cadenas probablemente intentarán crear nuevas instancias de str. Por lo tanto, si sobrescribo __new__() , supuestamente devolverían mi clase str personalizada. Sin embargo, no sé cómo pasar argumentos al método __init__() mi clase personalizada en ese caso. Y creo que necesitaría usar type() para crear una nueva instancia en el __new__() , ¿no?


Estoy intentando subclasificar el objeto str, y añadirle un par de métodos. Mi principal objetivo es aprender a hacerlo.

No codifique el método a la clase principal (como lo hace la respuesta superior). En su lugar, use super como este para soportar Python 2:

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

En Python 3, es más eficaz llamar a super como este, pero no es compatible con Python 2:

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

Uso:

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

La respuesta completa

Ninguna de las respuestas hasta ahora hace lo que has solicitado aquí:

Los métodos de mi clase, deben ser completamente desechables con los métodos str, y siempre deben devolver una nueva instancia de mi clase cuando los métodos personalizados la modifiquen. Quiero poder hacer algo como esto:

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

(Creo que te refieres a isinstance() , no issubclass() .)

Necesita una forma de interceptar los métodos de cadena. __getattribute__ hace esto.

class Caps(str): def __new__(cls, content): return super(Caps, cls).__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 + ''!!'')

y ahora:

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

Y el caso solicitado funciona:

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


Aquí hay un truco rápido para hacer lo que quiera: básicamente intercepta cada llamada de función y, si ve que está devolviendo una cadena, la convierte de nuevo a su propio tipo de clase.

Si bien esto funciona en este simple ejemplo, tiene algunas limitaciones. Entre otras cosas, los operadores como el operador de subíndices aparentemente no se manejan.

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


Estoy un poco horrorizado por la complejidad de las otras respuestas, y también lo está la biblioteca estándar de python. Puede utilizar collections.UserString para subclase de cadena y no ensucie con los métodos de la secuencia de proxy.

Solo haz una subclase y agrega tus métodos. self.data contiene la cadena real que está representando su objeto, por lo que incluso puede implementar métodos de "mutación" de self.data reasignando self.data internamente.

Un ejemplo


Puedes probar algo como:

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

pero no estará seguro de que los métodos estándar también devolverán una instancia ''mystr''


Sobrescribir __new__() funciona si desea modificar la cadena en construcción:

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

Pero si solo desea agregar nuevos métodos, ni siquiera tiene que tocar el constructor:

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

Tenga en cuenta que los métodos heredados, como por ejemplo upper() todavía devolverán una str normal, no text .