diff --git a/primer-on-python-decorators/README.md b/primer-on-python-decorators/README.md index 441cbffee1..3334f0aad8 100644 --- a/primer-on-python-decorators/README.md +++ b/primer-on-python-decorators/README.md @@ -1,17 +1,11 @@ # Primer on Python Decorators -Code examples from the [Primer on Python Decorators](https://realpython.com/primer-on-python-decorators/) tutorial on [Real Python](https://realpython.com/). +Here you can find code examples from the [Primer on Python Decorators](https://realpython.com/primer-on-python-decorators/) tutorial on [Real Python](https://realpython.com/). ## Decorators -As noted in the article, most decorators are stored in the file [`decorators.py`](https://github.com/realpython/materials/blob/master/primer-on-python-decorators/decorators.py). The exceptions are those decorators that depend on third party packages ([Flask](http://flask.pocoo.org/) and [Pint](https://pint.readthedocs.io/)), which are available in [`decorators_flask.py`](https://github.com/realpython/materials/blob/master/primer-on-python-decorators/decorators_flask.py) and [`decorators_unit.py`](https://github.com/realpython/materials/blob/master/primer-on-python-decorators/decorators_unit.py), respectively. +You can find most decorators in the file `decorators.py`. The exceptions are those decorators that depend on the third-party packages ([Flask](http://flask.pocoo.org/) and [Pint](https://pint.readthedocs.io/)). These decorators are available in `secret_app.py`, `validate_input.py`, and `units.py`. ## Examples -Most of the code examples from the article are available in the [`examples.py`](https://github.com/realpython/materials/blob/master/primer-on-python-decorators/examples.py) file. - -## Cheat Sheet - -We’ve put together a short & sweet Python decorators cheat sheet for you that summarizes the techniques explained in this tutorial: - -[Get the decorators cheat sheet »](https://realpython.com/optins/view/decorators-cheatsheet/) +You can find many code examples from the tutorial in separate Python files. diff --git a/primer-on-python-decorators/calculate_e.py b/primer-on-python-decorators/calculate_e.py new file mode 100644 index 0000000000..4751b09fab --- /dev/null +++ b/primer-on-python-decorators/calculate_e.py @@ -0,0 +1,13 @@ +import math + +from decorators import debug + +math.factorial = debug(math.factorial) + + +def approximate_e(terms=18): + return sum(1 / math.factorial(n) for n in range(terms)) + + +if __name__ == "__main__": + print(approximate_e(5)) diff --git a/primer-on-python-decorators/circle.py b/primer-on-python-decorators/circle.py new file mode 100644 index 0000000000..00a37626a8 --- /dev/null +++ b/primer-on-python-decorators/circle.py @@ -0,0 +1,35 @@ +class Circle: + def __init__(self, radius): + self.radius = radius + + @property + def radius(self): + """Get value of radius""" + return self._radius + + @radius.setter + def radius(self, value): + """Set radius, raise error if negative""" + if value >= 0: + self._radius = value + else: + raise ValueError("radius must be non-negative") + + @property + def area(self): + """Calculate area inside circle""" + return self.pi() * self.radius**2 + + def cylinder_volume(self, height): + """Calculate volume of cylinder with circle as base""" + return self.area * height + + @classmethod + def unit_circle(cls): + """Factory method creating a circle with radius 1""" + return cls(1) + + @staticmethod + def pi(): + """Value of π, could use math.pi instead though""" + return 3.1415926535 diff --git a/primer-on-python-decorators/class_decorators.py b/primer-on-python-decorators/class_decorators.py new file mode 100644 index 0000000000..81e7464f55 --- /dev/null +++ b/primer-on-python-decorators/class_decorators.py @@ -0,0 +1,22 @@ +from decorators import debug, timer + + +class TimeWaster: + @debug + def __init__(self, max_num): + self.max_num = max_num + + @timer + def waste_time(self, num_times): + for _ in range(num_times): + sum([number**2 for number in range(self.max_num)]) + + +@timer +class TimeWaster2: + def __init__(self, max_num): + self.max_num = max_num + + def waste_time(self, num_times): + for _ in range(num_times): + sum([i**2 for i in range(self.max_num)]) diff --git a/primer-on-python-decorators/decorators.py b/primer-on-python-decorators/decorators.py index bb9bbea352..b9e77d5ee3 100644 --- a/primer-on-python-decorators/decorators.py +++ b/primer-on-python-decorators/decorators.py @@ -1,21 +1,10 @@ -"""Examples of decorators - -See https://realpython.com/primer-on-python-decorators/ - -The decorators with dependencies outside of the standard library (Flask -and Pint) are available in separate files. -""" - import functools import time - -PLUGINS = dict() # Dictionary used by @register to store plugins +PLUGINS = dict() def do_twice(func): - """Run the decorated function twice""" - @functools.wraps(func) def wrapper_do_twice(*args, **kwargs): func(*args, **kwargs) @@ -33,7 +22,7 @@ def wrapper_timer(*args, **kwargs): value = func(*args, **kwargs) end_time = time.perf_counter() run_time = end_time - start_time - print(f"Finished {func.__name__!r} in {run_time:.4f} secs") + print(f"Finished {func.__name__}() in {run_time:.4f} secs") return value return wrapper_timer @@ -45,25 +34,25 @@ def debug(func): @functools.wraps(func) def wrapper_debug(*args, **kwargs): args_repr = [repr(a) for a in args] - kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] + kwargs_repr = [f"{k}={repr(v)}" for k, v in kwargs.items()] signature = ", ".join(args_repr + kwargs_repr) print(f"Calling {func.__name__}({signature})") value = func(*args, **kwargs) - print(f"{func.__name__!r} returned {value!r}") + print(f"{func.__name__}() returned {repr(value)}") return value return wrapper_debug -def slow_down_1sec(func): +def slow_down_one_second(func): """Sleep 1 second before calling the function""" @functools.wraps(func) - def wrapper_slow_down(*args, **kwargs): + def wrapper_slow_down_one_second(*args, **kwargs): time.sleep(1) return func(*args, **kwargs) - return wrapper_slow_down + return wrapper_slow_down_one_second def register(func): @@ -73,8 +62,6 @@ def register(func): def repeat(_func=None, *, num_times=2): - """Run the decorated function the given number of times""" - def decorator_repeat(func): @functools.wraps(func) def wrapper_repeat(*args, **kwargs): @@ -91,12 +78,10 @@ def wrapper_repeat(*args, **kwargs): def count_calls(func): - """Count the number of calls made to the decorated function""" - @functools.wraps(func) def wrapper_count_calls(*args, **kwargs): wrapper_count_calls.num_calls += 1 - print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}") + print(f"Call {wrapper_count_calls.num_calls} of {func.__name__}()") return func(*args, **kwargs) wrapper_count_calls.num_calls = 0 @@ -104,8 +89,6 @@ def wrapper_count_calls(*args, **kwargs): class CountCalls: - """Count the number of calls made to the decorated function""" - def __init__(self, func): functools.update_wrapper(self, func) self.func = func @@ -113,7 +96,7 @@ def __init__(self, func): def __call__(self, *args, **kwargs): self.num_calls += 1 - print(f"Call {self.num_calls} of {self.func.__name__!r}") + print(f"Call {self.num_calls} of {self.func.__name__}()") return self.func(*args, **kwargs) @@ -139,7 +122,7 @@ def singleton(cls): @functools.wraps(cls) def wrapper_singleton(*args, **kwargs): - if not wrapper_singleton.instance: + if wrapper_singleton.instance is None: wrapper_singleton.instance = cls(*args, **kwargs) return wrapper_singleton.instance @@ -157,15 +140,5 @@ def wrapper_cache(*args, **kwargs): wrapper_cache.cache[cache_key] = func(*args, **kwargs) return wrapper_cache.cache[cache_key] - wrapper_cache.cache = dict() + wrapper_cache.cache = {} return wrapper_cache - - -def set_unit(unit): - """Register a unit on a function""" - - def decorator_set_unit(func): - func.unit = unit - return func - - return decorator_set_unit diff --git a/primer-on-python-decorators/decorators_flask.py b/primer-on-python-decorators/decorators_flask.py deleted file mode 100644 index e96f8c9533..0000000000 --- a/primer-on-python-decorators/decorators_flask.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Examples of decorators that work with Flask - -See https://realpython.com/primer-on-python-decorators/ - -These decorators depend on the third party package Flask: - - http://flask.pocoo.org/ -""" - -import functools - -# pip install Flask -from flask import abort, g, request, redirect, url_for - - -def login_required(func): - """Make sure user is logged in before proceeding""" - - @functools.wraps(func) - def wrapper_login_required(*args, **kwargs): - if g.user is None: - return redirect(url_for("login", next=request.url)) - return func(*args, **kwargs) - - return wrapper_login_required - - -def validate_json(*expected_args): - """Validate that a json-request contains the expected parameters""" - - def decorator_validate_json(func): - @functools.wraps(func) - def wrapper_validate_json(*args, **kwargs): - json_object = request.get_json() - for expected_arg in expected_args: - if expected_arg not in json_object: - abort(400) - return func(*args, **kwargs) - - return wrapper_validate_json - - return decorator_validate_json diff --git a/primer-on-python-decorators/examples.py b/primer-on-python-decorators/examples.py deleted file mode 100644 index 0cdeedec2e..0000000000 --- a/primer-on-python-decorators/examples.py +++ /dev/null @@ -1,243 +0,0 @@ -"""Examples using decorators - -See https://realpython.com/primer-on-python-decorators/ - -This file contains code for many of the examples in the text. You can -find the decorators themselves in the decorators.py file. -""" - -from datetime import datetime -import functools -import math -import random - -from decorators import ( - cache, - count_calls, - debug, - do_twice, - PLUGINS, - register, - set_unit, - singleton, - slow_down, - timer, -) - - -# First-Class Objects -def say_hello(name): - return f"Hello {name}" - - -def be_awesome(name): - return f"Yo {name}, together we are the awesomest!" - - -def greet_bob(greeter_func): - return greeter_func("Bob") - - -# Returning Functions From Functions -def parent(num): - def first_child(): - return "Hi, I am Emma" - - def second_child(): - return "Call me Liam" - - if num == 1: - return first_child - else: - return second_child - - -# Simple Decorators -def not_during_the_night(func): - def wrapper(): - if 7 <= datetime.now().hour < 22: - func() - else: - pass # Hush, the neighbors are asleep. - - return wrapper - - -# Syntactic Sugar! -def my_decorator(func): - def wrapper(): - print("Something is happening before the function is called.") - func() - print("Something is happening after the function is called.") - - return wrapper - - -@my_decorator -def say_whee(): - print("Whee!") - - -# Reusing Decorators -@do_twice -def say_whee_twice(): - print("Whee!") - - -# Returning Values From Decorated Functions -@do_twice -def return_greeting(name): - print("Creating greeting") - return f"Hi {name}" - - -# A Few Real World Examples -def decorator(func): - @functools.wraps(func) - def wrapper_decorator(*args, **kwargs): - # Do something before. - value = func(*args, **kwargs) - # Do something after. - return value - - return wrapper_decorator - - -# Timing Functions -@timer -def waste_some_time(num_times): - for _ in range(num_times): - sum([i**2 for i in range(10000)]) - - -# Debugging Code -@debug -def make_greeting(name, age=None): - if age is None: - return f"Howdy {name}!" - else: - return f"Whoa {name}! {age} already, you are growing up!" - - -# Apply a decorator to a standard library function -math.factorial = debug(math.factorial) - - -def approximate_e(terms=18): - return sum(1 / math.factorial(n) for n in range(terms)) - - -# Slowing Down Code (Revisited) -@slow_down -def countdown(from_number): - if from_number < 1: - print("Liftoff!") - else: - print(from_number) - countdown(from_number - 1) - - -# Registering Plugins -# -# The names of the plugins have been changed from the text to avoid name -# clashes with earlier examples -@register -def say_hi(name): - return f"Hi {name}" - - -@register -def be_cool(name): - return f"Yo {name}, together we are the coolest!" - - -def randomly_greet(name): - greeter, greeter_func = random.choice(list(PLUGINS.items())) - print(f"Using {greeter!r}") - return greeter_func(name) - - -# Example Using Built-in Class Decorators -class Circle: - def __init__(self, radius): - self._radius = radius - - @property - def radius(self): - """Get value of radius""" - return self._radius - - @radius.setter - def radius(self, value): - """Set radius, raise error if negative""" - if value >= 0: - self._radius = value - else: - raise ValueError("Radius must be positive") - - @property - def area(self): - """Calculate area inside circle""" - return self.pi() * self.radius**2 - - def cylinder_volume(self, height): - """Calculate volume of cylinder with circle as base""" - return self.area * height - - @classmethod - def unit_circle(cls): - """Factory method creating a circle with radius 1""" - return cls(1) - - @staticmethod - def pi(): - """Value of π, could use math.pi instead though""" - return 3.141_592_653_5 - - -# Decorating Classes -class TimeWaster: - @debug - def __init__(self, max_num): - self.max_num = max_num - - @timer - def waste_time(self, num_times): - for _ in range(num_times): - sum([i**2 for i in range(self.max_num)]) - - -# Nesting Decorators -@do_twice -@debug -def greet(name): - print(f"Hello {name}") - - -# Creating Singletons -@singleton -class TheOne: - pass - - -# Caching Return Values -@cache -@count_calls -def fibonacci(num): - if num < 2: - return num - return fibonacci(num - 1) + fibonacci(num - 2) - - -@functools.lru_cache(maxsize=4) # lru_cache is preferred to rolling your own -def fibonacci_lru(num): - print(f"Calculating fibonacci({num})") - if num < 2: - return num - return fibonacci(num - 1) + fibonacci(num - 2) - - -# Adding Information About Units -@set_unit("cm^3") -def volume(radius, height): - return math.pi * radius**2 * height diff --git a/primer-on-python-decorators/greeters.py b/primer-on-python-decorators/greeters.py new file mode 100644 index 0000000000..d703402a1d --- /dev/null +++ b/primer-on-python-decorators/greeters.py @@ -0,0 +1,15 @@ +def say_hello(name): + return f"Hello {name}" + + +def be_awesome(name): + return f"Yo {name}, together we're the awesomest!" + + +def greet_bob(greeter_func): + return greeter_func("Bob") + + +if __name__ == "__main__": + print(greet_bob(say_hello)) + print(greet_bob(be_awesome)) diff --git a/primer-on-python-decorators/hello_decorator.py b/primer-on-python-decorators/hello_decorator.py new file mode 100644 index 0000000000..ba174359f7 --- /dev/null +++ b/primer-on-python-decorators/hello_decorator.py @@ -0,0 +1,24 @@ +def decorator(func): + def wrapper(): + print("Something is happening before the function is called.") + func() + print("Something is happening after the function is called.") + + return wrapper + + +def say_whee(): + print("Whee!") + + +say_whee = decorator(say_whee) + + +@decorator +def say_whee2(): + print("Whee!") + + +if __name__ == "__main__": + say_whee() + say_whee2() diff --git a/primer-on-python-decorators/inner_functions.py b/primer-on-python-decorators/inner_functions.py new file mode 100644 index 0000000000..6ed904781e --- /dev/null +++ b/primer-on-python-decorators/inner_functions.py @@ -0,0 +1,33 @@ +def parent(): + print("Printing from parent()") + + def first_child(): + print("Printing from first_child()") + + def second_child(): + print("Printing from second_child()") + + second_child() + first_child() + + +def get_child(num): + def first_child(): + return "Hi, I'm Elias" + + def second_child(): + return "Call me Ester" + + if num == 1: + return first_child + else: + return second_child + + +if __name__ == "__main__": + parent() + + first = get_child(1) + second = get_child(2) + print(first()) + print(second()) diff --git a/primer-on-python-decorators/quiet_night.py b/primer-on-python-decorators/quiet_night.py new file mode 100644 index 0000000000..ff9ce6141d --- /dev/null +++ b/primer-on-python-decorators/quiet_night.py @@ -0,0 +1,21 @@ +from datetime import datetime + + +def not_during_the_night(func): + def wrapper(): + if 7 <= datetime.now().hour < 22: + func() + else: + pass # Hush, the neighbors are asleep + + return wrapper + + +def say_whee(): + print("Whee!") + + +say_whee = not_during_the_night(say_whee) + +if __name__ == "__main__": + say_whee() diff --git a/primer-on-python-decorators/secret_app.py b/primer-on-python-decorators/secret_app.py new file mode 100644 index 0000000000..02c6557c44 --- /dev/null +++ b/primer-on-python-decorators/secret_app.py @@ -0,0 +1,22 @@ +import functools +from flask import Flask, g, request, redirect, url_for + +app = Flask(__name__) + + +def login_required(func): + """Make sure user is logged in before proceeding""" + + @functools.wraps(func) + def wrapper_login_required(*args, **kwargs): + if g.user is None: + return redirect(url_for("login", next=request.url)) + return func(*args, **kwargs) + + return wrapper_login_required + + +@app.route("/secret") +@login_required +def secret(): + ... diff --git a/primer-on-python-decorators/decorators_unit.py b/primer-on-python-decorators/units.py similarity index 71% rename from primer-on-python-decorators/decorators_unit.py rename to primer-on-python-decorators/units.py index cb116f177c..1295477894 100644 --- a/primer-on-python-decorators/decorators_unit.py +++ b/primer-on-python-decorators/units.py @@ -1,15 +1,6 @@ -"""Examples of decorators dealing with units - -See https://realpython.com/primer-on-python-decorators/ - -These decorators depend on the third party package Pint: - - http://pint.readthedocs.io/ -""" - import functools -import pint # pip install Pint +import pint def set_unit(unit): diff --git a/primer-on-python-decorators/validate_input.py b/primer-on-python-decorators/validate_input.py new file mode 100644 index 0000000000..adf662675f --- /dev/null +++ b/primer-on-python-decorators/validate_input.py @@ -0,0 +1,28 @@ +import functools + +from flask import Flask, abort, request + +app = Flask(__name__) + + +def validate_json(*expected_args): + def decorator_validate_json(func): + @functools.wraps(func) + def wrapper_validate_json(*args, **kwargs): + json_object = request.get_json() + for expected_arg in expected_args: + if expected_arg not in json_object: + abort(400) + return func(*args, **kwargs) + + return wrapper_validate_json + + return decorator_validate_json + + +@app.route("/grade", methods=["POST"]) +@validate_json("student_id") +def update_grade(): + json_data = request.get_json() # noqa + # Update database. + return "success!"