Estendendo builtin do Python Str
-
08-07-2019 - |
Pergunta
Eu estou tentando str
subclasse, mas tendo algumas dificuldades devido à sua imutabilidade.
class DerivedClass(str):
def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob
def upper(self):
#overridden, new functionality. Return ob of type DerivedClass. Great.
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')
derived = DerivedClass('a')
print derived.upper() #'A123'
print type(derived.upper()) #<class '__main__.DerivedClass'>
print derived.lower() #'a'
print type(derived.lower()) #<type 'str'>
Para métodos herdados que não necessitam de qualquer nova funcionalidade, como derived.lower()
, existe uma maneira simples, maneira Python para retornar um objeto do tipo DerivedClass
(em vez de str
)? Ou eu estou preso substituir manualmente cada str.method (), como eu fiz com derived.upper()
?
Editar:
#Any massive flaws in the following?
class DerivedClass(str):
def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob
def upper(self):
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')
def __getattribute__(self, name):
att = super(DerivedClass, self).__getattribute__(name)
if not callable(att):
return att
def call_me_later(*args, **kwargs):
result = att(*args, **kwargs)
if isinstance(result, basestring):
return DerivedClass(result)
return result
return call_me_later
Solução
Você pode fazer isso, substituindo __getattribute__
como Zr40 sugere, mas você precisa ter getAttribute retornar uma função que pode ser chamado. O exemplo abaixo deve dar-lhe o que você quer; ele usa o wrapper functools.partial
para tornar a vida mais fácil, embora você poderia implementá-lo sem parcial se você gosta:
from functools import partial
class DerivedClass(str):
def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob
def upper(self):
#overridden, new functionality. Return ob of type DerivedClass. Great.
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')
def __getattribute__(self, name):
func = str.__getattribute__(self, name)
if name == 'upper':
return func
if not callable(func):
return func
def call_me_later(*args, **kwargs):
result = func(*args, **kwargs)
# Some str functions return lists, ints, etc
if isinstance(result, basestring:
return DerivedClass(result)
return result
return partial(call_me_later)
Outras dicas
Bom uso para um decorador de classe - mais ou menos (código não testado):
@do_overrides
class Myst(str):
def upper(self):
...&c...
e
def do_overrides(cls):
done = set(dir(cls))
base = cls.__bases__[0]
def wrap(f):
def wrapper(*a, **k):
r = f(*a, **k)
if isinstance(r, base):
r = cls(r)
return r
for m in dir(base):
if m in done or not callable(m):
continue
setattr(cls, m, wrap(getattr(base, m)))
Você é ambos perto, mas a verificação de cada não se estende bem para substituir muitos métodos.
from functools import partial
class DerivedClass(str):
def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob
def upper(self):
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')
def __getattribute__(self, name):
if name in ['__dict__', '__members__', '__methods__', '__class__']:
return object.__getattribute__(self, name)
func = str.__getattribute__(self, name)
if name in self.__dict__.keys() or not callable(func):
return func
def call_me_later(*args, **kwargs):
result = func(*args, **kwargs)
# Some str functions return lists, ints, etc
if isinstance(result, basestring):
return DerivedClass(result)
return result
return partial(call_me_later)
(melhorias sugeridas por Jarret Hardie nos comentários.)
Você pode ser capaz de fazer isso, substituindo __getattribute__
.
def __getattribute__(self, name):
# Simple hardcoded check for upper.
# I'm sure there are better ways to get the list of defined methods in
# your class and see if name is contained in it.
if name == 'upper':
return object.__getattribute__(self, name)
return DerivedClass(object.__getattribute__(self, name))