## Callables and Callable Instances

### The **call** method

There will be hardly any Python user who hasn't stumbled upon exceptions like `'dict' object is not callable`

or `'int' object is not callable`

.
After a while they find out the reason. They used parentheses (round bracket) in situation where they shouldn't have done it. Expressions like `f(x)`

, `gcd(x, y)`

or `sin(3.4)`

are usually used for function calls. The question is, why do we get the message `'dict' object is not callable`

if we write `d('a')`

, if `d`

is a dictionary? Why doesn't it say `'dict' object is not a function`

? First of all, when we invoke a function in Python, we also say we 'call the function'.
Secondly, there are objects in Python, which are 'called' like functions but are not functions strictly speaking. There are 'lambda functions', which are defined in a different way. It is also possible to define classes, where the instances are callable like 'regular' functions.
This will be achieved by adding another magic method the `__call__`

method.

Before we will come to the `__call__`

method, we have to know what a callable is. In general, a "callable" is an object that can be called like a function and behaves like one. All functions are also callables. Python provides a function with the name `callable`

. With the help of this funciton we can determine whether an object is callable or not.
The function `callable`

returns a Boolean truth value which indicates whether the object passed as an argument can be called like a function or not. In addition to functions, we have already seen another form of `callables`

: classes

```
def the_answer(question):
return 42
print("the_answer: ", callable(the_answer))
```

The `__call__`

method can be used to turn the instances of the class into callables. Functions are callable objects. A callable object is an object which can be used and behaves like a function but might not be a function. By using the `__call__`

method it is possible to define classes in a way that the instances will be callable objects. The `__call__`

method is called, if the instance is called "like a function", i.e. using brackets. The following class definition is the simplest possible way to define a class with a `__call__`

method.

```
class FoodSupply:
def __call__(self):
return "spam"
foo = FoodSupply()
bar = FoodSupply()
print(foo(), bar())
```

The previous class example is extremely simple, but useless in practical terms. Whenever we create an instance of the class, we get a callable. These callables are always defining the same constant function. A function without any input and a constant output "spam". We'll now define a class which is slightly more useful. Let us slightly improve this example:

```
class FoodSupply:
def __init__(self, *incredients):
self.incredients = incredients
def __call__(self):
result = " ".join(self.incredients) + " plus delicious spam!"
return result
f = FoodSupply("fish", "rice")
f()
```

Let's create another function:

```
g = FoodSupply("vegetables")
g()
```

Now, we define a class with the name TriangleArea. This class has only one method, which is the `__call__`

method. The `__call__`

method calculates the area of an arbitrary triangle, if the length of the three sides are given.

```
class TriangleArea:
def __call__(self, a, b, c):
p = (a + b + c) / 2
result = (p * (p - a) * (p - b) * (p - c)) ** 0.5
return result
area = TriangleArea()
print(area(3, 4, 5))
```

This program returns 6.0. This class is not very exciting, even though we can create an arbitrary number of instances where each instance just executes an unaltered `__call__`

function of the TrianlgeClass. We cannot pass parameters to the instanciation and the `__call__`

of each instance returns the value of the area of the triangle. So each instance behaves like the area function.

After the two very didactic and not very practical examples, we want to demonstrate a more practical example with the following. We define a class that can be used to define linear equations:

```
class StraightLines():
def __init__(self, m, c):
self.slope = m
self.y_intercept = c
def __call__(self, x):
return self.slope * x + self.y_intercept
line = StraightLines(0.4, 3)
for x in range(-5, 6):
print(x, line(x))
```

We will use this class now to create some straight lines and visualize them with matplotlib:

```
lines = []
lines.append(StraightLines(1, 0))
lines.append(StraightLines(0.5, 3))
lines.append(StraightLines(-1.4, 1.6))
import matplotlib.pyplot as plt
import numpy as np
X = np.linspace(-5,5,100)
for index, line in enumerate(lines):
line = np.vectorize(line)
plt.plot(X, line(X), label='line' + str(index))
plt.title('Some straight lines')
plt.xlabel('x', color='#1C2833')
plt.ylabel('y', color='#1C2833')
plt.legend(loc='upper left')
plt.grid()
plt.show()
```

Our next example is also exciting. The class FuzzyTriangleArea defines a `__call__`

method which implements a fuzzy behaviour in the calculations of the area. The result should be correct with a likelihood of p, e.g. 0.8. If the result is not correct the result will be in a range of ± v %. e.g. 0.1.

```
import random
class FuzzyTriangleArea:
def __init__(self, p=0.8, v=0.1):
self.p, self.v = p, v
def __call__(self, a, b, c):
p = (a + b + c) / 2
result = (p * (p - a) * (p - b) * (p - c)) ** 0.5
if random.random() <= self.p:
return result
else:
return random.uniform(result-self.v,
result+self.v)
area1 = FuzzyTriangleArea()
area2 = FuzzyTriangleArea(0.5, 0.2)
for i in range(12):
print(f"{area1(3, 4, 5):4.3f}, {area2(3, 4, 5):4.2f}")
```

Beware that this output differs with every call! We can see the in most cases we get the right value for the area but sometimes not.

We can create many different instances of the previous class. Each of these behaves like an area function, which returns a value for the area, which may or may not be correct, depending on the instantiation parameters p and v. We can see those instances as experts (expert functions) which return in most cases the correct answer, if we use p values close to 1. If the value v is close to zero, the error will be small, if at all. The next task would be merging such experts, let's call them exp1, exp2, ..., expn to get an improved result. We can perform a vote on the results, i.e. we will return the value which is most often occuring, the correct value. Alternatively, we can calculate the arithmetic mean. We will implement both possibilities in our class FuzzyTriangleArea:

```
from random import uniform, random
from collections import Counter
class FuzzyTriangleArea:
def __init__(self, p=0.8, v=0.1):
self.p, self.v = p, v
def __call__(self, a, b, c):
p = (a + b + c) / 2
result = (p * (p - a) * (p - b) * (p - c)) ** 0.5
if random() <= self.p:
return result
else:
return uniform(result-self.v,
result+self.v)
class MergeExperts:
def __init__(self, mode, *experts):
self.mode, self.experts = mode, experts
def __call__(self, a, b, c):
results= [exp(a, b, c) for exp in self.experts]
if self.mode == "vote":
c = Counter(results)
return c.most_common(1)[0][0]
elif self.mode == "mean":
return sum(results) / len(results)
rvalues = [(uniform(0.7, 0.9), uniform(0.05, 0.2)) for _ in range(20)]
experts = [FuzzyTriangleArea(p, v) for p, v in rvalues]
merger1 = MergeExperts("vote", *experts)
print(merger1(3, 4, 5))
merger2 = MergeExperts("mean", *experts)
print(merger2(3, 4, 5))
```

The following example defines a class with which we can create abitrary polynomial functions:

```
class Polynomial:
def __init__(self, *coefficients):
self.coefficients = coefficients[::-1]
def __call__(self, x):
res = 0
for index, coeff in enumerate(self.coefficients):
res += coeff * x** index
return res
# a constant function
p1 = Polynomial(42)
# a straight Line
p2 = Polynomial(0.75, 2)
# a third degree Polynomial
p3 = Polynomial(1, -0.5, 0.75, 2)
for i in range(1, 10):
print(i, p1(i), p2(i), p3(i))
```

You will find further interesting examples of the `__call__`

function in our tutorial in the chapters Decorators and Memoization with Decorators. You may also consult our chapter on Polynomials.