DEV Community

Taqmuraz
Taqmuraz

Posted on

Python : advanced way to reduce the amount of repetitive code

Let us imagine, that you have some list of objects.
You need to call one specific function on each object in that list.
Here is an example :

class Dog:
    def sound(self):
        print("Woof")
class Cat:
    def sound(self):
        print("Meow")
class Cow:
    def sound(self):
        print("Mooo")

animals = [Dog(), Cat(), Cow()]
for a in animals: a.sound()
Enter fullscreen mode Exit fullscreen mode

Nice. Now, imagine that each animal object must have at least two functions : sound and insight.
sound does write line to the output stream and returns nothing, insight does nothing but returns a string.
We would like to call sound in each function and to print list containing insight string of each animal :

class Dog:
    def sound(self):
        print("Woof")
    def insight(self):
        return "Humans are best!"
class Cat:
    def sound(self):
        print("Meow")
    def insight(self):
        return "Humans are slaves of cats"
class Cow:
    def sound(self):
        print("Mooo")
    def insight(self):
        return "Humans are violent indeed"

animals = [Dog(), Cat(), Cow()]
for a in animals: a.sound()
print(f"Insights : {[a.insight() for a in animals]}")
Enter fullscreen mode Exit fullscreen mode

It may seem nice, but we use for loop each time we need to do the same thing with each animal. How may we reduce number of such syntax constructs? We could declare wrapper-class to wrap list of animals, intercepting function calls and returning lists of results :

class PluralAnimal:
    def __init__(self, animals):
        self.animals = animals
    def sound(self):
        for a in animals:
            a.sound()
    def insight(self):
        return [a.insight() for a in self.animals]

plural = PluralAnimal(animals)
plural.sound()
print(f"Insights : {plural.insight()}")

Enter fullscreen mode Exit fullscreen mode

Looks much better? Yes, but I have one concern : now we have to declare such wrapper-class for each case of use. For animals, vehicles, employees we have to declare PluralAnimal, PluralVehicle, PluralEmployee.
How may we avoid that?

Dynamic function call interception

If we want to access some attribute of the object in runtime, using name of that attribute, we may use getattr function :

class Example:
    def func(self):
        print("Hello, example!")
obj = Example()
func = getattr(obj, "func")
func() # output : Hello, example!
Enter fullscreen mode Exit fullscreen mode

Let us write wrapper-class, using getattr function :

class Plural:
    def __init__(self, objs):
        self.objs = objs
    def __getattr__(self, name):
        def func(*args, **kwargs):
            return [getattr(o, name)(*args, **kwargs) for o in self.objs]
        return func

plural = Plural(animals)
plural.sound()
print(f"Insights : {plural.insight()}")

Enter fullscreen mode Exit fullscreen mode

That's all for today. Thank you for reading, I would be pleased to read and answer your comments. See you next time.

Top comments (0)