Decoding Python Magic: __contains__ and __call__

Let’s enhance our understanding of Python by diving into the __contains__ and __call__.

Rahul Beniwal
Level Up Coding
Published in
3 min readApr 12, 2024

--

Hello everyone, thank you for reading my other articles in the “Decoding Python Magic Series.” It’s truly refreshing to write about these topics. Today, let’s cover the next two magic methods, which most of us do not know.

Image Credit Unsplash

__contains__

__contains__ simply give power to collections which allow in operator to be applied on them.

l = [1, 2, 3, 4, 5]
(1 in l) is (l.__contains__(1))
  • in operator calls the __contains__ internally.

Odd Number Container

from collections.abc import Container


class OddContainer(Container):
def __contains__(self, x):
if not isinstance(x, int) or not x % 2:
return False
return True


odd_container = OddContainer()
print(1 in odd_container) # True
print(2 in odd_container) # False
print(21111111112 in odd_container) # False
print("hello" in odd_container) # False

Fruit Bucket

from collections import UserList


class FruitBucker(UserList):
def add(self, fruit):
self.data.append(fruit)

def __contains__(self, item):
return item in self.data


bucket = FruitBucker(["apple", "banana", "orange"])
print("apple" in bucket) # True
print("mango" in bucket) # False
bucket.add("mango")
print("mango" in bucket) # True

__call__

The __call__ method in Python allows an object to be called as if it were a function. This method can be defined in a class to enable instances of that class to be callable. When you use the () operator on an object instance, Python internally calls the __call__ method of that object.

Self Adder

class Adder:
def __init__(self, n):
self.n = n

def __call__(self, x):
self.n += x
return self.n


# Create an instance of Adder
add_five = Adder(5)

# Call the instance as a function
result = add_five(10)
print(result, add_five.n) # Output: 15, 15
result = add_five(10)
print(result) # Output: 25

Animal Factory

class Animal:
def __init__(self, species):
self.species = species


class AnimalFactory:
def __call__(self, *args, **kwargs):
return Animal(*args, **kwargs)


create_animal = AnimalFactory()
dog = create_animal("Dog")
print(dog.species) # Output: Dog
cat = create_animal("Cat")
print(cat.species) # Output: Cat

Class Based Decorator Implementation

class Repeat:
def __init__(self, times):
self.times = times

def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.times):
func(*args, **kwargs)

return wrapper


class LogArgs:
def __init__(self, func):
self.func = func

def __call__(self, *args, **kwargs):
print(f"Arguments: {args}, {kwargs}")
return self.func(*args, **kwargs)


@Repeat(3)
@LogArgs
def greet(name):
print(f"Hello, {name}!")


greet("Alice")
Arguments: ('Alice',), {}
Hello, Alice!
Arguments: ('Alice',), {}
Hello, Alice!
Arguments: ('Alice',), {}
Hello, Alice!

Conclusion

Python’s readability and elegance make it a top contender for the best programming language each year, along with its practical applications. Magic methods play a crucial role, enhancing readability and maintainability.

Mastering Python without understanding these methods is challenging. They keep code clean and visually appealing, allowing customization of class behavior for more expressive and understandable code. Overriding these methods for custom data structures saves time and effort, crucial for maintainable code. Continual learning and exploration of these features are key to becoming a proficient Python programmer.

Other similar articles by me.

--

--