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
.