Spice up your code with wrappers! In Python, a wrapper, also known as a decorator, is simply encapsulating a function within other functions.
@wrapper def my_func(a, b=2): print(b) @meta_decorator(foo="bar") def my_other_func(**kwargs): print(kwargs)
In Reusables, all the wrappers take arguments, aka meta decorators, so you will at minimum have to end them with parens ()
s. Let’s take a look at the one I use the most first.
time_it
import reusables @reusables.time_it() def test_func(): import time, random time.sleep(random.randint(2, 5)) test_func() # Function 'test_func' took a total of 5.000145769345911 seconds
time_it documentation
The message by default is printed. However it can also be sent to a log with a customized message. If log=True
it will log to the Reusables
logger, however you can also specify either a logger by name (string) or pass in a logger object directly.
reusables.add_stream_handler('reusables')
@reusables.time_it(log=True, message="{seconds:.2f} seconds")
def test_time(length):
time.sleep(length)
return "slept {0}".format(length)
result = test_time(5)
# 2016-11-09 16:59:39,935 - reusables.wrappers INFO 5.01 seconds
print(result)
# slept 5
It’s also possible to capture time taken in a list.
@reusables.time_it(log='no_log', append=my_times) def test_func(): import time, random length = random.random() time.sleep(length) for _ in range(10): test_func() my_times # [0.4791555858872698, 0.8963652232890809, 0.05607090172793505, # 0.03099917658380491,0.6567622821214627, 0.4333975642063024, # 0.21456404424395714, 0.5723555061358638, 0.0734819056269771, # 0.13208268856499217]
unique
The oldest wrapper in the Reusables
library forces the output of a function to be unique, or else it will raise an exception or if specified, return an alternative output.
import reusables import random @reusables.unique(max_retries=100) def poor_uuid(): return random.randint(0, 10) print([poor_uuid() for _ in range(10)]) # [8, 9, 6, 3, 0, 7, 2, 5, 4, 10] print([poor_uuid() for _ in range(1000)]) # Exception: No result was unique
lock_it
A very simple wrapper to use while threading. Makes sure a function is only being run once at a time.
import reusables import time def func_one(_): time.sleep(5) @reusables.lock_it() def func_two(_): time.sleep(5) @reusables.time_it(message="test_1 took {0:.2f} seconds") def test_1(): reusables.run_in_pool(func_one, (1, 2, 3), threaded=True) @reusables.time_it(message="test_2 took {0:.2f} seconds") def test_2(): reusables.run_in_pool(func_two, (1, 2, 3), threaded=True) test_1() test_2() # test_1 took 5.04 seconds # test_2 took 15.07 seconds
log_exception
It’s good practice to catch and explicitly log exceptions, but sometimes it’s just easier to let it fail naturally at any point and log it for later refinement or debugging.
@reusables.log_exception() def test(): raise Exception("Bad") # 2016-12-26 12:38:01,381 - reusables ERROR Exception in test - Bad # Traceback (most recent call last): # File "<input>", line 1, in <module> # File "reusables\wrappers.py", line 200, in wrapper # raise err # Exception: Bad
queue_it
Add the result of the function to the specified queue instead of returning it normally.
import reusables import queue my_queue = queue.Queue() @reusables.queue_it(my_queue) def func(a): return a func(10) print(my_queue.get()) # 10
New in 0.9
catch_it
Catch specified exceptions and return an alternative output or send it to an exception handler.
def handle_error(exception, func, *args, **kwargs): print(f"{func.__name__} raised {exception} when called with {args}") @reusables.catch_it(handler=handle_error) def will_raise(message="Hello"): raise Exception(message) will_raise("Oh no!") # will_raise raised Oh no! when called with ('Oh no!',)
retry_it
Once is never enough, keep trying until it works!
@reusables.retry_it(exceptions=(Exception, ), tries=10, wait=1) def may_fail(dont_do=[]): dont_do.append("x") if len(dont_do) < 6: raise OSError("So silly") print("Much success!") may_fail() # Much success!
This post is a follow-up of Reusables – Part 1: Overview and File Management.