-4

I am trying to understand the mechanics of chained functions such as

>>> 'hello'.upper()
'HELLO'

(the chain can be longer, I do not have a good example in my head right now - something like 'hello'.upper().reverse().take_every_second_character().rot13())

How is this functionality achieved? What must be returned by each of the functions (and intercepted (= used as a parameter?) by the next one), taken into account that one can break the chain somewhere and still get an output? To take the fictional example above:

>>> 'hello'
'hello'

>>> 'hello'.upper()
'HELLO'

>>> 'hello'.upper().reverse()
'OLLEH'

etc.

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • Do you really need this to work with an initial Python `str` type? You'd have to resort to some kind of non-elegance to make this work with custom functions/methods. It would be better if you had some custom object of your own type from the get-go. – blubberdiblub Nov 01 '19 at 15:40
  • 2
    There's nothing mysterious about it. The thing returned is the thing the next method is called on. `'hello'.upper()` returns the string `'HELLO'`, and you are trying to call `reverse()` on that, but there's no such method defined on a string. – khelwood Nov 01 '19 at 15:41
  • This provides some background on [basic method chaining](https://stackoverflow.com/questions/41817578/basic-method-chaining) – DarrylG Nov 01 '19 at 15:43
  • Just change the notation: `a = 'hello'.upper(); b = a.reverse(); c = b.foo(); ...`. It's the exact same thing, just deconstructed into individual steps. – deceze Nov 01 '19 at 15:45
  • `'hello'.upper()[::-1]` would work, as that is a way to reverse a string in an expression. – RemcoGerlich Nov 01 '19 at 15:49
  • @DarrylG: this is it, thank you. I was not aware that the functions must actually be methods and that they return `self`. i will flag as duplicate. Thanks. – WoJ Nov 01 '19 at 15:49
  • @WoJ: they don't result `self` in the general case -- strings cannot be mutated, so functions like `.upper()` return a new string, not self. Returning self is to make it possible to chain methods that act on the same object. – RemcoGerlich Nov 01 '19 at 15:51
  • try running this code snippet https://pastebin.com/6xN9YB65 -- does it help? – Chris_Rands Nov 01 '19 at 15:54
  • @RemcoGerlich: in that case the next method is expecting a parameter, right? The accepted answer to the duplicate I flagged only mentions an implicit reuse of `self` (and a property) – WoJ Nov 01 '19 at 15:54
  • @WoJ--for the question about strings not returning self, this post provides [an alternative mechanism](https://stackoverflow.com/questions/39038358/function-chaining-in-python). Namely, in this case the method returns an instance of the updated value of itself. – DarrylG Nov 01 '19 at 16:51

2 Answers2

1

'hello' is a str object.

str class has methods (like upper(), reverse(), etc.) that return the string transformed.

You could extend the str class to create your own methods.

>>> import inspect
>>> from pprint import pprint
>>> 
>>> str
<class 'str'>
>>> pprint(inspect.getmembers(str))
[('__add__', <slot wrapper '__add__' of 'str' objects>),
 ('__class__', <class 'type'>),
 ('__contains__', <slot wrapper '__contains__' of 'str' objects>),
 ('__delattr__', <slot wrapper '__delattr__' of 'object' objects>),
 ('__dir__', <method '__dir__' of 'object' objects>),
 ('__doc__',
  "str(object='') -> str\n"
  'str(bytes_or_buffer[, encoding[, errors]]) -> str\n'
  '\n'
  'Create a new string object from the given object. If encoding or\n'
  'errors is specified, then the object must expose a data buffer\n'
  'that will be decoded using the given encoding and error handler.\n'
  'Otherwise, returns the result of object.__str__() (if defined)\n'
  'or repr(object).\n'
  'encoding defaults to sys.getdefaultencoding().\n'
  "errors defaults to 'strict'."),
 ('__eq__', <slot wrapper '__eq__' of 'str' objects>),
 ('__format__', <method '__format__' of 'str' objects>),
 ('__ge__', <slot wrapper '__ge__' of 'str' objects>),
 ('__getattribute__', <slot wrapper '__getattribute__' of 'str' objects>),
 ('__getitem__', <slot wrapper '__getitem__' of 'str' objects>),
 ('__getnewargs__', <method '__getnewargs__' of 'str' objects>),
 ('__gt__', <slot wrapper '__gt__' of 'str' objects>),
 ('__hash__', <slot wrapper '__hash__' of 'str' objects>),
 ('__init__', <slot wrapper '__init__' of 'object' objects>),
 ('__init_subclass__',
  <built-in method __init_subclass__ of type object at 0x7f9d596539c0>),
 ('__iter__', <slot wrapper '__iter__' of 'str' objects>),
 ('__le__', <slot wrapper '__le__' of 'str' objects>),
 ('__len__', <slot wrapper '__len__' of 'str' objects>),
 ('__lt__', <slot wrapper '__lt__' of 'str' objects>),
 ('__mod__', <slot wrapper '__mod__' of 'str' objects>),
 ('__mul__', <slot wrapper '__mul__' of 'str' objects>),
 ('__ne__', <slot wrapper '__ne__' of 'str' objects>),
 ('__new__', <built-in method __new__ of type object at 0x7f9d596539c0>),
 ('__reduce__', <method '__reduce__' of 'object' objects>),
 ('__reduce_ex__', <method '__reduce_ex__' of 'object' objects>),
 ('__repr__', <slot wrapper '__repr__' of 'str' objects>),
 ('__rmod__', <slot wrapper '__rmod__' of 'str' objects>),
 ('__rmul__', <slot wrapper '__rmul__' of 'str' objects>),
 ('__setattr__', <slot wrapper '__setattr__' of 'object' objects>),
 ('__sizeof__', <method '__sizeof__' of 'str' objects>),
 ('__str__', <slot wrapper '__str__' of 'str' objects>),
 ('__subclasshook__',
  <built-in method __subclasshook__ of type object at 0x7f9d596539c0>),
 ('capitalize', <method 'capitalize' of 'str' objects>),
 ('casefold', <method 'casefold' of 'str' objects>),
 ('center', <method 'center' of 'str' objects>),
 ('count', <method 'count' of 'str' objects>),
 ('encode', <method 'encode' of 'str' objects>),
 ('endswith', <method 'endswith' of 'str' objects>),
 ('expandtabs', <method 'expandtabs' of 'str' objects>),
 ('find', <method 'find' of 'str' objects>),
 ('format', <method 'format' of 'str' objects>),
 ('format_map', <method 'format_map' of 'str' objects>),
 ('index', <method 'index' of 'str' objects>),
 ('isalnum', <method 'isalnum' of 'str' objects>),
 ('isalpha', <method 'isalpha' of 'str' objects>),
 ('isascii', <method 'isascii' of 'str' objects>),
 ('isdecimal', <method 'isdecimal' of 'str' objects>),
 ('isdigit', <method 'isdigit' of 'str' objects>),
 ('isidentifier', <method 'isidentifier' of 'str' objects>),
 ('islower', <method 'islower' of 'str' objects>),
 ('isnumeric', <method 'isnumeric' of 'str' objects>),
 ('isprintable', <method 'isprintable' of 'str' objects>),
 ('isspace', <method 'isspace' of 'str' objects>),
 ('istitle', <method 'istitle' of 'str' objects>),
 ('isupper', <method 'isupper' of 'str' objects>),
 ('join', <method 'join' of 'str' objects>),
 ('ljust', <method 'ljust' of 'str' objects>),
 ('lower', <method 'lower' of 'str' objects>),
 ('lstrip', <method 'lstrip' of 'str' objects>),
 ('maketrans', <built-in method maketrans of type object at 0x7f9d596539c0>),
 ('partition', <method 'partition' of 'str' objects>),
 ('replace', <method 'replace' of 'str' objects>),
 ('rfind', <method 'rfind' of 'str' objects>),
 ('rindex', <method 'rindex' of 'str' objects>),
 ('rjust', <method 'rjust' of 'str' objects>),
 ('rpartition', <method 'rpartition' of 'str' objects>),
 ('rsplit', <method 'rsplit' of 'str' objects>),
 ('rstrip', <method 'rstrip' of 'str' objects>),
 ('split', <method 'split' of 'str' objects>),
 ('splitlines', <method 'splitlines' of 'str' objects>),
 ('startswith', <method 'startswith' of 'str' objects>),
 ('strip', <method 'strip' of 'str' objects>),
 ('swapcase', <method 'swapcase' of 'str' objects>),
 ('title', <method 'title' of 'str' objects>),
 ('translate', <method 'translate' of 'str' objects>),
 ('upper', <method 'upper' of 'str' objects>),
 ('zfill', <method 'zfill' of 'str' objects>)]
Loïc
  • 11,804
  • 1
  • 31
  • 49
0

Assuming these functions all exist and return the appropriate types.

res = 'hello'
res.upper().reverse().take_every_second_character().rot13()

is equivalent to saying:

res = 'hello'
((((res.upper()).reverse()).take_every_second_character()).rot13())

OR

res = 'hello'.upper()
res = res.reverse()
res = res.take_every_second_character()
res.rot13()

You cannot "break the chain" (if one fails, meaning an error, then an error will be thrown unless you catch it).

Akaisteph7
  • 5,034
  • 2
  • 20
  • 43