Function decorators in Python

In Python, decorator syntax refers to the desugaring of

into

f is called the “decorated” function, and g is called the “decorator.”

Here we look at several examples using decorators in Python, compare them to similar situations in Haskell, and discuss how lambdas let us achieve the same result conveniently without having a special syntax for decorators.

Uses of decorators

We broadly categorize decorators into two kinds of uses:

  1. To make some alteration to the decorated function
  2. To perform some side effect during module initializationAlthough Python a module usually looks like a declarative list of functions, it’s important to remember that a module is actually a script that performs side effects (even I/O!) as it loads.

Example: Property testing

Hypothesis (Python)

The Hypothesis library has a decorator called given. This is one that alters the decorated function.

In the example below, we want to verify the assertion that if you double a number x, the result will be greater than or equal to the input. We define a function test_double which expresses this.

And we also applied the given decorator to it. This replaces the test_double function we wrote with a function that runs our function on many inputs in search of an input for which the assertion fails.

>>> test_double()
Falsifying example: test_double(x = -1)

And we see it did find a counterexample: The property we’re verifying was wrong, because it doesn’t hold for negative numbers.

QuickCheck (Haskell)

QuickCheck has a function called quickCheck which serves a similar role:The actual type of quickCheck is more general than this, but here we give a specialized type for simplicity.

quickCheck transforms a function which expresses the property into an action which checks the property.

And we can run it and see that we get a similar result:

λ> testDouble
*** Failed! Falsifiable (after 7 tests and 2 shrinks):
-1

Why aren’t we suffering from lack of a decorator syntax in the Haskell example? Because we were able to express the property as a lambda

rather than as a named function

and we can pass a lambda directly as an argument to the quickCheck function without any fuss.

Example: Web routing

Flask (Python)

With Flask, we create a web server by first constructing a Flask instance and then adding routes via its route method.

>>> app.run(port = 8000)
 * Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
$ curl http://localhost:8000
Hello World!

$ curl http://localhost:8000/hello/Julie
Hello Julie!

Because Python has no syntax for writing multi-line anonymous functions, we needed two declarations to create each route –

  1. Define the route’s behavior by declaring a named function
  2. Apply app.route to the named function to create the route

– but decorator syntax gave us a nice concise way to perform the second step without having to repeat the name of the function.

Scotty (Haskell)

Now let’s build the same web server using Scotty.

We will use the addroute function which serves a similar role to Flask.route:

λ> scotty 8000 app
Setting phasers to stun... (port 8000) (ctrl-c to quit)

In contrast with the Python server, the Haskell routes didn’t require two steps. This is because we didn’t need to name the route behaviors; we just passed them directly as arguments to Scotty’s addroute function.

Example: Timing

Timing decorator (Python)

For this example we’ll write our own decorator. The stopwatch decorator modifies a function so that when the function runs, it also prints a line that says how long it took.

>>> delay(1)
delay: 0:00:01.001225

Timing function (Haskell)

Now we’ll do the same thing in Haskell. We’ll be using the Control.Concurrent module from base, and the Data.Time.Clock module from the time package.

λ> delay 1000000
delay: 1.001164885s

Why is the “decorator” (the stopwatch function) more concise in Haskell? Because Python requires a decorator to be curried – it is used via three separate invocations

`stopwatch(name)(action)(x)`

rather than taking all arguments at once

`stopwatch(name, action, x)`

– but Python syntax doesn’t easily support currying, so we had to use some funny workarounds like declaring a named function f and then returning it.

Using a higher-order function in Haskell is extremely similar to using a decorator in Python. That’s because that’s almost exactly what a decorator is. The Python glossary entry for “decorator” actually defines it as:We find this definition slightly inaccurate; more precisely, a Python decorator is a function that accepts a function argument, and usually (but not necessarily) returns a function.

A function returning another function

So it should be no surprise that this is something at which Haskell excels!

To emphasize the similarity between decorators in Python and higher-order functions in Haskell, let’s put the final examples side-by-side to see how close they are:

Type Classes offers courses and projects to get you started and make you an expert in FP with Haskell. For $29/month, you get access to the complete archive and all the latest content.