Как подклассы str в Python
Я пытаюсь подклассифицировать объект 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(). ниже()
Насколько я понимаю, мне нужно реализовать __new__()
. Я так думаю, потому что методы строк, вероятно, попытаются создать новые экземпляры str. Итак, если я перезаписал __new__()
, они якобы вернут мой собственный класс str. Тем не менее, я не знаю, как передать аргументы моему методу __init__()
класса в этом случае. И я предполагаю, что мне нужно будет использовать type()
, чтобы создать новый экземпляр в методе __new__()
правильно?
Ответы
Ответ 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
.
Ответ 2
Здесь вы можете быстро взломать все, что хотите: вы перехватываете каждый вызов функции и, если видите, что он возвращает строку, вы конвертируете ее обратно в свой собственный тип класса.
Хотя это работает в этом простом примере, у него есть некоторые ограничения. Среди прочего, операторы, такие как оператор индекса, по-видимому, не обрабатываются.
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()
Ответ 3
Я пытаюсь подклассифицировать объект str и добавлять к нему несколько методов. Моя главная цель - научиться делать это.
Не перегружайте метод родительским классом (например, верхний ответ). Вместо этого используйте super
, как это, для поддержки Python 2:
class Caps(str):
def __new__(cls, content):
return super(Caps, cls).__new__(cls, content.upper())
В Python 3 более эффективно вызывать super
как это, но он не совместим с Python 2:
class Caps(str):
def __new__(cls, content):
return super().__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(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 + '!!')
и теперь:
>>> 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!!!')
Ответ 4
Я немного ужасен от сложности других ответов, а также стандартной библиотеки python. Вы можете использовать collection.UserString для подкласса строки и не использовать методы проксирования str
.
Просто подклассируйте его и добавьте свои методы. self.data
содержит фактическую строку, представленную вашим объектом, поэтому вы можете даже реализовать str- "мутирующие" методы, переназначив self.data
внутренне.
Пример.
Ответ 5
Вы можете попробовать что-то вроде:
class mystr(str):
def new_method(self):
pass
но вы не будете уверены, что стандартные методы также вернут экземпляр "mystr"