Blog

Python Christmas Decoration

Now that Halloween is over and Advent is just around the corner, it is time for some christmas decorations. And what better way to get into the holiday spirit than with a Python 🐍 project?

Putting up christmas decorations can be quite a time intensive task, so letting your significant other do it for you is clearly the best way to go! Sadly, mine wouldn't go for it, though. Last year, we took a lot of breaks when putting up the decorations, which resulted in chaos. So this year, we will time any breaks to settle our discussion about if the lazy breaks really outnumbered the decoration time.

So lets time how long it really takes us by using a christmas_break_timer.

import time
def take_a_break():
    time.sleep(1)
break_start = time.time()
take_a_break()
break_end = round((time.time() - break_start))
print("The break took {timing} second(s).".format(timing=break_end))
The break took 1 second(s).

But clearly writing break_start and break_end every time we take a break would take even more time, so lets create a helpful christmas_break_timer.

from contextlib import contextmanager

@contextmanager
def christmas_break_timer():
    break_start = time.time()
    yield
    break_end = round((time.time() - break_start))
    print("The break took {timing} second(s).".format(timing=break_end))

Now we can just use the christmas_break_timer every time we take a break!

with christmas_break_timer():
    take_a_break()
The break took 1 second(s).

Great! This with ...: snytax is really convenient. And the @contextmanager decorator makes it really easy to implement. For a more sophisticated contextmanager, for timing the christmas dinner for example, we can implement the contextmanager without any additional libraries as a class.

class ChristmasDinnerTiming:
    def __init__(self, persons, course):
        self.persons = persons
        self.course = course
        self.total_meals = self.persons * self.course
        
    def __enter__(self):
        print("Begin preparation of {n_meals} meals for {n_persons} persons.".format(
            n_meals=self.total_meals,
            n_persons=self.persons,
        ))
        self.start_time = time.time() 
        return self

    def __exit__(self, *args):
        prepare_time = round((time.time() - self.start_time),1)
        prepare_time = prepare_time * self.total_meals
        print("The dinner preparation took {timing} hours.".format(timing=prepare_time))
def prepare_one_meal():
    time.sleep(0.5)

def set_the_table(n_persons):
    print("I set the table for {n_persons} persons.".format(n_persons=n_persons))
with ChristmasDinnerTiming(persons=6, course=3) as dinner_prep:
    set_the_table(n_persons=dinner_prep.persons)
    prepare_one_meal()
Begin preparation of 18 meals for 6 persons.
I set the table for 6 persons.
The dinner preparation took 9.0 hours.

So now that we got the timer ready, lets get on with the decoration. The @contextmanager decorator we used for our christmas_break_timer looks really good, lets make one of them!

But wait.. what is a decorator anyways?

A Decorator takes a function (and possibly some arguments) and returns that function plus the functionality of the decorator itself. So much like real christmas decorations! A little functional decoration and possibly some arguments.

from decorator import decorator
# A decorator function always need the arguments func, *args and **kw
@decorator
def decorative_christmas_break_timer(func, *args, **kw):
    break_start = time.time()
    res = func(*args, **kw)
    break_end = round((time.time() - break_start))
    print("The break time was {timing} seconds.".format(timing=break_end))
    return res
# Now we can use our decorative_break_timer as nice christmas decoration
@decorative_christmas_break_timer
def take_a_break(break_time):
    time.sleep(break_time)
    return break_time
nice_break = take_a_break(1)
The break time was 1 seconds.
@decorative_christmas_break_timer
def take_a_break_nap(nap_time):
    time.sleep(nap_time)
    return nap_time
nap_time = take_a_break_nap(5)
The break time was 5 seconds.

Not everyone has the means of just importing a @decorator for christmas. But you can still have a nice christmas decorator!

# Same as above, but without add-on module:
def decorative_christmas_break_timer(func):
    def wrapper(*args, **kw):
        break_start = time.time()
        res = func(*args, **kw)
        break_end = round((time.time() - break_start))
        print("The break time was {timing} seconds.".format(timing=break_end))
        return res
    return wrapper
# note that the arguments func, *args and **kw are still the same, but now the wrapper shows how the inner `func` is
# surrounded by the decorative code. Just like the christmas tree!

What a nice christmas decorator. But since christmas is the time of love and connecting, let's combine our contextmanager and the decorator to combine their functionality and allow the use of @decorative_break_timer and with decorative_break_timer at the same time!

from contextlib import ContextDecorator
class DecorativeChristmasBreakTimer(ContextDecorator):
    def __enter__(self):
        self.break_start = time.time()
        return self
    def __exit__(self, *args):
        break_end = round((time.time() - self.break_start))
        print("The break time was {timing} second(s).".format(timing=break_end))
def tv_break():
     time.sleep(2)

with DecorativeChristmasBreakTimer():
    tv_break()

@DecorativeChristmasBreakTimer()
def tv_break(x):
    time.sleep(x)
    return x
tv_time = tv_break(3)
The break time was 2 second(s).
The break time was 3 second(s).

Well, if this doesn't feel like christmas, I don't know. Maybe you should implement a decorator that plays last christmas?