<jupyter_start><jupyter_text>Metaprogramming 0x01<jupyter_code>from time import time 
from functools import wraps

def timethis(func):
    ''' a decorator that reports the exec time '''
    
    @wraps(func)                           # crucial!! (keeping meta-info of the original)
    def wrapper(*args, **kwargs):          # whatever (funcname or its args)
        
        start  = time()                    # your own  
        result = func(*args, **kwargs)     # whatever (revoking original func)
        end    = time()                    # your own   
        
        print(func.__name__, end - start)  # your own
        
        return result
    
    return wrapper
@timethis 
def countdown(n: int):
    ''' only for counting :) '''
    
    while n > 0:
        n -= 1 
        
        
countdown(10000)
countdown(1000000)<jupyter_output>countdown 0.0007650852203369141
countdown 0.06827902793884277<jupyter_text>0x02<jupyter_code># using '@wraps(func)' can retain the meta-info (countdown)

countdown.__name__
countdown.__doc__
countdown.__annotations__

countdown.__wrapped__(1000)  # revoking the func was 'decorated' directly


# if not using '@wraps'

its_name = 'wrapper'  # 'wrapper' func in timethis
its_doc  = None       # nothin' 
its_anno = {}         # no anno in 'wrapper' func!<jupyter_output><empty_output><jupyter_text>0x03<jupyter_code>@timethis
def ct(n):  # ct() => timethis(ct)
    pass


def ct(n):
    pass

ct = timethis(ct)
class A:
    @classmethod    # same thing like '@property' etc.
    def hello(cls):
        pass
    
class B:
    def hello(cls):
        pass
    hello = classmethod(hello)<jupyter_output><empty_output><jupyter_text>0x04<jupyter_code>@timethis
def add(x, y):
    return x * y

add            (3, 4)  # wrapped
add.__wrapped__(3, 4)  # upwrapped (original)<jupyter_output>add 9.5367431640625e-07<jupyter_text>0x05<jupyter_code>from functools import wraps 

def deco_one(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Deco001')
        return func(*args, **kwargs)
    return wrapper 

def deco_two(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Deco002')
        return func(*args, **kwargs)
    return wrapper

@deco_one
@deco_two
def add(x, y):
    return x + y 
# well, normal as-fuck
add(2, 3)

# the attr is sometimes unreliable!
add.__wrapped__(2, 3)<jupyter_output>Deco001
Deco002
{'fraction_non_alphanumeric': 0.11115736885928393, 'fraction_numerical': 0.04454621149042465, 'mean_word_length': 2.7664576802507836, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '66119', 'license': 'Unlicense', 'n_tokens_mistral': 916, 'n_tokens_neox': 835, 'n_words': 201, 'path': 'Chapter09 - Full.ipynb', 'repo_name': 'codingEzio/code_py_book_cookbook', 'size': '2402'}