Last active
December 29, 2022 03:59
-
-
Save simonseo/50fe06f467dcac9d1dc0c4922c62e8bd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# examples from https://realpython.com/primer-on-python-decorators/ | |
import time | |
import functools | |
import random | |
######### Common decorators ######### | |
def timer(func): | |
"""Print the runtime of the decorated function""" | |
@functools.wraps(func) | |
def wrapper_timer(*args, **kwargs): | |
start_time = time.perf_counter() # 1 | |
value = func(*args, **kwargs) | |
end_time = time.perf_counter() # 2 | |
run_time = end_time - start_time # 3 | |
print(f"Finished {func.__name__!r} in {run_time:.4f} secs") | |
return value | |
return wrapper_timer | |
def debug(func): | |
"""Print the function signature and return value""" | |
@functools.wraps(func) | |
def wrapper_debug(*args, **kwargs): | |
args_repr = [repr(a) for a in args] # 1 | |
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] # 2 | |
signature = ", ".join(args_repr + kwargs_repr) # 3 | |
print(f"Calling {func.__name__}({signature})") | |
value = func(*args, **kwargs) | |
print(f"{func.__name__!r} returned {value!r}") # 4 | |
return value | |
return wrapper_debug | |
def repeat(num_times): | |
def decorator_repeat(func): | |
@functools.wraps(func) | |
def wrapper_repeat(*args, **kwargs): | |
for _ in range(num_times): | |
value = func(*args, **kwargs) | |
return value | |
return wrapper_repeat | |
return decorator_repeat | |
def singleton(cls): | |
"""Make a class a Singleton class (only one instance)""" | |
@functools.wraps(cls) | |
def wrapper_singleton(*args, **kwargs): | |
if not wrapper_singleton.instance: | |
wrapper_singleton.instance = cls(*args, **kwargs) | |
return wrapper_singleton.instance | |
wrapper_singleton.instance = None | |
return wrapper_singleton | |
######### General Template ######### | |
# basic form | |
def decorator(func): | |
"""Docstring for decorator""" | |
# do something upon function definition | |
return func | |
# advanced form | |
def decorator(func): | |
"""Docstring for decorator""" | |
# do something upon function definition | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
# do something before running function | |
value = func(*args, **kwargs) | |
# do something after running function | |
return value | |
# do something upon wrapper definition like `wraps` | |
return wrapper | |
# advanced form with arguments | |
def create_decorator(*decorator_args, **decorator_kwargs): | |
# do something before creating decorator | |
def decorator(func): | |
# do something upon function definition | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
# do something before running function | |
value = func(*args, **kwargs) | |
# do something after running function | |
# do something upon wrapper definition | |
return wrapper | |
# do something after creating decorator | |
return decorator | |
# advanced form that can either be argument-less or accept keyword arguments | |
def decorator_or_create_decorator(_func=None, *, | |
the_parameter='default value when not called with kwargs'): | |
def decorator(func): | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
return func(*args, **kwargs) | |
return wrapper | |
if _func is None: # there are keywords | |
return decorator # return decorator | |
else: # no keywords | |
return decorator(_func) # return wrapper | |
# Stateful form | |
def stateful_decorator(func): | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
# update wrapper.some_property | |
print(f"Property value {wrapper.num_calls} of {func.__name__!r}") | |
return func(*args, **kwargs) | |
wrapper.some_property = 0 | |
return wrapper | |
# stateful class form | |
class CountCalls: | |
def __init__(self, func): | |
functools.update_wrapper(self, func) | |
self.func = func | |
self.num_calls = 0 | |
def __call__(self, *args, **kwargs): | |
self.num_calls += 1 | |
print(f"Call {self.num_calls} of {self.func.__name__!r}") | |
return self.func(*args, **kwargs) | |
######### Registering functions ######## | |
PLUGINS = dict() | |
def register(func): | |
"""Register a function as a plug-in""" | |
PLUGINS[func.__name__] = func | |
return func | |
@register | |
def say_hello(name): | |
return f"Hello {name}" | |
@register | |
def be_awesome(name): | |
return f"Yo {name}, together we are the awesomest!" | |
def randomly_greet(name): | |
greeter, greeter_func = random.choice(list(PLUGINS.items())) | |
print(f"Using {greeter!r}") | |
return greeter_func(name) | |
######### Flask ######## | |
#### (better to use Flask-Login extension than to use this) | |
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(): | |
pass | |
######## Built-in class decorators ######## | |
#### @property, @classmethod, @staticmethod | |
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. | |
Class methods take the class as first parameter.""" | |
return cls(1) | |
@staticmethod | |
def pi(): | |
"""Value of π, could use math.pi instead though. | |
Static methods don't use anything inside the class.""" | |
return 3.1415926535 | |
######## Dataclass ######## | |
from dataclasses import dataclass | |
@dataclass | |
class PlayingCard: | |
rank: str | |
suit: str |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment