I am having a hard time understanding how this code works:
class add(int):
def __call__(self, int):
return add(self int)
result = add(2)
print(result(4))
The int 2 seems to get stored inside result(not sure if that is the case tbh), such that when we print(result), the output is 2. When print(result(4)) is ran, the output becomes 6. My question is...why is this? Is the number 2, in fact, being stored inside result? If we instantiate the add class without an argument, the result variable will output the number 0. What is happening here under the hood??
I want to understand why calling result(2)(3)(4) outputs 9.
CodePudding user response:
What this class does, it gives integers an ability to be called as a function, so that a(b)
actually means a b
. Imagine one day this feature is added to the python interpreter. Then you could write 2(3)
and get 5
. But that 5
is still callable, so we can do 5(4)
and get 9
, or, chained together, 2(3)(4) => 9
.
In actual python this is not possible (number literals are always int
), so we have to explicitly name our class (as in add(2)(3)(4)
), but the principle remains the same.
"Currying" is not what's happening here.
CodePudding user response:
add
is a class that subclasses int
. As such, it behaves exactly as we would expect int
instances to behave. So, for example, its "default"y value is 0. That is why print(add())
is 0 (since int()
is 0).
Now, we implemented the __add__
method. According to the data-model, the __add__
method allows instances of classes that implement it to be callable, ie be used to the left of ()
, with or without argument.
Since you allowed __call__
to accept an argument and you add this argument to the value that the instance currently represents (remember that add
instances behave just like a normal int
would), we can call instances of add
with an argument and expect the behavior you observed.
To recap, add(2)
gives us an object that represents the integer 2
(just like int(2)
would) with the added functionality of being able to call it with an argument that will be added to its own value (so add(2)(3)
is just 2 3
).
We can add some print
s to see what is going on, and it is better to not use the name int
in the definition of __call__
since we don't want to shadow the type int
(that the add
class subclasses). Ignore the call to super().__init__()
if you are not familiar with it already, it is there to allow us to print the message).
class add(int):
def __init__(self, own_value):
print(f"Created 'add' with own_value {own_value}")
super().__init__()
def __call__(self, number):
print(f"Inside __call__ with number {number}, will return new 'add' with own_value {self number}")
return add(self number)
result = add(2)
print(result(4))
outputs
Created 'add' with own_value 2
Inside __call__ with number 4, will return new 'add' with own_value 6
Created 'add' with own_value 6
6