• 7. Function Decorators and Closures


    • A decorator is a callable that takes another function as argument (the decorated function). The decorator may perform some processing with the decorated function, and returns it or replaces it with another function or callable object.

    1. Variable Scope Rules

    b = 6
    def f1(a):
    	print(a)  # 3
    	print(b)  # 6
    f1(3)
    
    b = 6
    def f2(a):
    	print(a)  # 3
    	print(b)  # UnboundLocalError: local variable 'b' referenced before assignment
    	b = 9  
    f2(3)
    
    # 1. When Python compiles the body of the function, it decides that b is a local
    # variable because it's assigned within the function.
    # 2. Python does not require you to declare variables, but assumes that a variable
    # assigned in the body of a function is local
    
    b = 6
    def f3(a):
    	global b  # declare
    	print(a)  # 3
    	print(b)  # 6
    	b = 9  
    f3(3)
    

     2. Closures

    • Actually, a closure is a function with an extended scope that encompasses nonglobal variables referenced in the body of the function but not defined there. It does not matter whether the function is anonymous or not; what matters is that it can access nonglobal variables that are defined outside of its body
    • A closure is a function that retains the bindings of the free variables that exist when the function is defined, so that they can be used later when the function is invoked and the defining scope is no longer available.
    • The only situation in which a function may need to deal with external variables that are nonglobal is when it is nested in another function.
    def make_averager():
    	series = []
    	def averager(new_value):
    		series.append(new_value)
    		total = sum(series)
    		return total / len(series)
    	return averager
    
    avg = make_averager()
    print avg(10)  # 10
    print avg(12)  # 11
    print avg(14)  # 12
    print avg.__code__.co_varnames  # ('new_value', 'total')
    print avg.__code__.co_freevars  # ('series',)
    print avg.__closure__  # (<cell at 0x1030d1280: list object at 0x1030a97e8>,)
    print avg.__closure__[0].cell_contents  # [10, 12, 14]

    • free variable: a variable that is not bound in the local scope.

    3. The nonlocal Declaration

    def make_averager():
    	count = 0
    	total = 0
    	def averager(new_value):
    		count += 1
    		total += new_value
    		return total / count
    	return averager
    
    avg = make_averager()
    print avg(10)  # UnboundLocalError: local variable 'count' referenced before assignment
    
    # 1. We actually assign count in the body of averager, and that makes it a local variable.
    # 2. We didn't have this problem before, because we never assigned to the series name,
    # we only called series.append and invoked sum and len on it.
    # 3. With immutable types, if you try to rebind them, then you are implicitly creating a
    # local variable, it's no longer a free variable, therefore it is not saved in the closure.
    
    def make_averager():
    	count = 0
    	total = 0
    	def averager(new_value):
    		nonlocal count, total  #  **********
    		count += 1
    		total += new_value
    		return total / count
    	return averager
    
    avg = make_averager()
    print(avg(10))  # 10.0
    
    # 1. The nonlocal declaration was introduced in Python 3. It lets you flag a variable as a
    # free variable even when it is assigned a new value within the function.
    

     4. Implementing a Simple Decorator

    • Typical behavior of a decorator: It replaces the decorated function with a new function that accepts the same arguments and (usually) returns whatever the decorated function was supposed to return, while also doing some extra processing.
    def deco(func):
    	print('running deco()')
    	print('func -> %s' % str(func))
    	def inner():
    		print('running inner()')
    	return inner
     
    @deco  # target = deco(target)
    def target():
    	print('running target()')
    
    target()
    # running deco()  # executed immediately when the module is loaded
    # func -> <function target at 0x106c460c8>
    # running inner()
    print(target.__name__)  # inner (function is replaced)
    
    import time
    FMT = '[{elapsed:0.8f}s] {name}({arg_str}) -> {result}'
    def clock(func):
    	def clocked(*args, **kwargs):
    		t0 = time.time()
    		result = func(*args, **kwargs)
    		elapsed = time.time() - t0  # 用时
    		name = func.__name__
    		arg_lst = []
    		if args:
    			arg_lst.append(', '.join(repr(arg) for arg in args))
    		if kwargs:
    			pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
    			arg_lst.append(', '.join(pairs))
    		arg_str = ', '.join(arg_lst)
    		print(FMT.format(**locals()))  # any local variable of clocked
    		return result
    	return clocked
    
    @clock
    def snooze(seconds):
    	time.sleep(seconds)
    
    @clock
    def factorial(n):
    	return 1 if n < 2 else n*factorial(n-1)
    
    print('*' * 40, 'Calling snooze(.123)')
    snooze(.123)
    print('*' * 40, 'Calling factorial(6)')
    print('6! =', factorial(6))
    # ('****************************************', 'Calling snooze(.123)')
    # [0.12694907s] snooze(0.123) -> None
    # ('****************************************', 'Calling factorial(6)')
    # [0.00000286s] factorial(1) -> 1
    # [0.00004411s] factorial(2) -> 2
    # [0.00005984s] factorial(3) -> 6
    # [0.00007296s] factorial(4) -> 24
    # [0.00014281s] factorial(5) -> 120
    # [0.00015783s] factorial(6) -> 720
    # ('6! =', 720)

     5. Decorators in the Standard Library

    5.1 functools.wraps

    import functools
    def deco(func):
    	@functools.wraps(func)  # copy the relevant attributes from func to inner
    	def inner():
    		print('running inner()')
    	return inner
     
    @deco
    def target():
    	"""i'm target"""
    	print('running target()')
    
    target()  # running inner()
    print(target.__name__)  # target
    print(target.__doc__)  # i'm target
    

     5.2 functools.lru_cache

    • An optimization technique that works by saving the results of previous invocations of an expensive function, avoiding repeat computations on previously used arguments.
    • The cache is limited by discarding the entries that have not been read for a while.
    import functools
    @functools.lru_cache()  # Python 3
    @clock
    def fibonacci(n):
    	if n < 2:
    		return n
    	return fibonacci(n-2) + fibonacci(n-1)
    
    print(fibonacci(6))
    # [0.00000095s] fibonacci(0) -> 0
    # [0.00000215s] fibonacci(1) -> 1
    # [0.00036097s] fibonacci(2) -> 1
    # [0.00000167s] fibonacci(3) -> 2
    # [0.00039101s] fibonacci(4) -> 3
    # [0.00000095s] fibonacci(5) -> 5
    # [0.00041699s] fibonacci(6) -> 8
    # 8
    
    # @functools.lru_cache(maxsize=128, typed=False)
    # maxsize: how many call results are stored. (should be a power of 2)
    # typed: True -> store results of different argument types separately (1 / 1.0)
    #        False -> don't store
    
    # [notes]: lru_cache uses a dict to store the results, all the arguments
    #          taken by the decorated function must be hashable.

     5.3 functools.singledispatch

    • The new functools.singledispatch decorator in Python 3.4 allows each module to contribute to the overall solution, and lets you easily provide a specialized function even for classes that you can’t edit.
    • If you decorate a plain function with @singledispatch, it becomes a generic function: a group of functions to perform the same operation in different ways, depending on the type of the first argument.
    from functools import singledispatch
    from collections import abc
    import numbers
    import html
    
    @singledispatch  # mark the base function handles the obj type.
    def htmlize(obj):
    	content = html.escape(repr(obj))
    	return '<pre>{}</pre>'.format(content)
    
    
    @htmlize.register(str)  # @«base_function».register(«type»).
    def _(text):  # name of the specialized functions is irrelevant
    	return '<p>{0}</p>'.format(html.escape(text))
    
    @htmlize.register(numbers.Integral)
    def _(n):
    	return '<pre>{0} (0x{0:x})</pre>'.format(n)
    
    @htmlize.register(tuple)
    @htmlize.register(abc.MutableSequence)
    def _(seq):
    	inner = '</li>
    <li>'.join(htmlize(item) for item in seq)
    	return '<ul>
    <li>' + inner + '</li>
    </ul>'
    
    print(htmlize(str))  # <pre><class 'str'></pre>
    print(htmlize('<hello>'))  # <p><hello></p>
    print(htmlize(10))  # <pre>10 (0xa)</pre>
    print(htmlize(['alpha', {3, 2, 1}]))
    # <ul>
    # <li><p>alpha</p></li>
    # <li><pre>{1, 2, 3}</pre></li>
    # </ul>
    
    
    # 1. Register the specialized functions to handle ABCs (abstract classes)
    # instead of concrete implementations like int and list. (support more)
    # 2. A notable quality of the singledispatch mechanism is that you can
    # register specialized functions anywhere in the system, in any module.
    

     6. Stacked Decorators

    @d1
    @d2
    def f():
    	print('f')
    
    # f = d1(d2(f))
    

     7. Parameterized Decorators

    • Make a decorator factory that takes those arguments and returns a decorator, which is then applied to the function to be decorated.
    def outter(a=1):
    	def deco(func):
    		def inner():
    			if a == 1:
    				print(1)
    			else:
    				print(a)
    		return inner
    	return deco
     
    @outter()
    def target1():
    	pass
    
    @outter(9)
    def target2():
    	pass
    
    target1()  # 1
    target2()  # 9
    
  • 相关阅读:
    Linux系统下公式编辑器KLatexFormula
    C++11 std::chrono库详解
    Direct Visual-Inertial Odometry with Stereo Cameras
    改变机器人的初始位姿,让机器人的初始位置不在地图的原点处
    ubuntu为文件添加可执行权限
    oracle的启动和停用
    orcale创建用户、授权
    手工配置oracle数据库
    MySQL存储过程详解 mysql 存储过程
    mysql的navicat执行存储过程
  • 原文地址:https://www.cnblogs.com/lb477/p/10906849.html
Copyright © 2020-2023  润新知