DEV Community

Cover image for Improve your Python Skills with Classes, decorators, and implicit parameters
José Marrugo
José Marrugo

Posted on

Improve your Python Skills with Classes, decorators, and implicit parameters

Python is a very common programming language. It is easy to learn the basics and start using it right away, this is specially useful for data scientists, and people from other backgrounds that don't have a lot of coding experience.

This guide aims to introduce 3 concepts that if applied correctly, you could simplify your code, add functionalities to your current methods, and pass arguments without specifying them. Also, your code will look cooler 😎.

The concepts are:

  1. Classes.
  2. Decorators.
  3. *Args and **kwargs.

Classes

Python is an Object Oriented Programming Language (OOP Language), this means that we can use classes to organize our code and group related methods together.

A Python Class could be something like:

class Pokemon:
   """Class containing the available Pokemon methods"""
   def evolve(...):
   ...
Enter fullscreen mode Exit fullscreen mode

The interesting part about classes is that they introduce one of the most useful things of OOP, heritage.

For example, if we have multiple classes that are somewhat related, let's say, Bulbasaur, Squirtle, and Charmander, they all should have an evolve method:

class Bulbasaur:
    def evolve(...):
    ...

class Squirtle:
    def evolve(...):
    ...

class Charmander:
    def evolve(...):
    ...
Enter fullscreen mode Exit fullscreen mode

But using the example above, we could simply define a Pokemon parent class that contains the method, and all three pokemons will have it through heritage.

class Pokemon:
   """Class containing the available Pokemon methods"""
   def evolve(...):
   ...

class Bulbasaur(Pokemon):
    ...

class Squirtle(Pokemon):
    ...

class Charmander(Pokemon):
    ...

eric = Bulbasaur()
eric.evolve()
# >>Evolving to Ivisaur
Enter fullscreen mode Exit fullscreen mode

And with that all three pokemons could make use of the method defined in the parent class.

This helps us make our code scalable, maintainable and also easier to read.

Decorators

Decorators are a way of altering a method's functionality by using another method that receives the target method as an argument.

For example, let's say that we are going to make the evolve method of the pokemons to print the elapsed time that took the pokemon to evolve.

There are several ways of doing this, but we are using a decorator for it.

def measure_time(func):
    start = time.time()
    func()
    end = time.time()
    print(f"The elapsed time was {end - start} seconds!")
Enter fullscreen mode Exit fullscreen mode

To use it we should use the decorator with an "@" above the function definition, something like this:

@measure_time
def evolve(...)
    ...

print(evolve)
# >>The elapsed time was 7.152557373046875e-07 seconds!
Enter fullscreen mode Exit fullscreen mode

We could also have decorators with parameters. One useful resource for understanding this is this stackoverflow's answer.

Let's say that we have two Bulbasaurs and one of them tackles the other.

Example:

def print_used_attack(f):
    def attack(*xs, **kws):
        return f(*xs, **kws)
    print(f"Used {f.__name__}")
    return attack

class Bulbasaur():
    def __init__(self):
        self.health = 100

    @staticmethod
    @print_used_attack
    def tackle(target_pokemon):
        return target_pokemon.health - 15

eric = Bulbasaur()
tommy = Bulbasaur()

tommy.health = eric.tackle(tommy)
# >>Used Tackle
print(tommy.health)
# >>85
Enter fullscreen mode Exit fullscreen mode

Here, anytime the tackle method is used, a string like "Used attack_name" will be printed in the console, this is useful if we want to add logs to a method that receives multiple arguments.

And, the magic of this decorator is that if we define more attacks like vine_whip or leech_seed, and we use the decorator with them, a log will be printed in the console with the used attack!

*Args and **Kwargs

These are abreviations for Arguments and Key-arguments.

Note that the important part is the use of "*" and "**", the name could be different form args and kwargs, those names are just conventions.

*Args

The *args abreviation allow us to define a method without argument constraints.

Example:

damage_history = [3,3,5,6,6,7]

def sum_attack_damage(*args):
    total = 0
    for num in args:
        total += num
    return total

print(sum_attack_damage(*damage_history))
# >>30
Enter fullscreen mode Exit fullscreen mode

The use of the *args as a form of receiving the parameters give us the possibility of passing multiple arguments, and being able to sum them without limiting the number!

**Kwargs

The **kwargs abreviation allows us to define a method that could receive a dict with the parameters' names as keys.

Example:

def evolve_pokemon(pokemon, base_evolution, first_evolution, second_evolution):
    if pokemon == base_evolution:
         return first_evolution
    elif pokemon == first_evolution:
        return second_evolution
    else: return pokemon

pokemon_data={
    "base_evolution": "Pichu",
    "first_evolution":"Pikachu",
    "second_evolution":"Raichu"
}

print(evolve_pokemon("Pichu",**pokemon_data)) 
# >>Pikachu
print(evolve_pokemon("Raichu",**pokemon_data)) 
# >>Raichu
Enter fullscreen mode Exit fullscreen mode

As you see in the example, it is not necessary to pass parameter by parameter to the function, we just passed a dict with the ** at the beginning to signal that it should be treated as a kwarg.

Conclusion

Learning python could be a long process, but I hope that with this article, and some future ones, I could explain in a simple manner difficult concepts that are really useful for our journey.

I hope you find this interesting, and if you want to deepen on something, have suggestions, or want me to explain some other concept, you could leave a comment below.

Happy Programming!

Top comments (0)