函数作用域,闭包
高阶函数
函数柯里化
装饰器

变量的作用域


全局作用域

  • 全局变量

局部作用域

  • 局部变量
x = 5 
def foo():         
    x += 1
    print(x) 
foo()
------------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-81-715f310d8cc6> in <module>()
      3     x += 1
      4     print(x)
----> 5 foo()

<ipython-input-81-715f310d8cc6> in foo()
      1 x = 5
      2 def foo():
----> 3     x += 1
      4     print(x)
      5 foo()

UnboundLocalError: local variable 'x' referenced before assignment

foo 函数中 x = x + 1,= 右边,重新定义局部变量 x,x 还没赋值就拿来做 + 1 运算了

global 语句

x = 5 
def foo():
    global x
    x += 1
    print(x) 
foo()
------------
6

使用 global 关键字的变量,将 foo 内的 x 声明为使用外部的全局作用域中定义的 x

def foo():
    global x
    x = 10
    x += 1
    print(x) 
foo()
print(x)
----------------
11
11

使用了 global,foo 中的 x 不再是局部变量了,它是全局变量

  • 总结:global 使用原则
    • 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
    • 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
    • 避免使用 global

默认值作用域

  • 函数对象把函数的默认值放在函数对象的__defaults__属性中,他是元组
# 对比如下两个例子
# 例1
def foo(a=1,b=1):
    a += 1
    b += 1
    print(a)
    print(b)
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
--------------
1497519961768 (1, 1)
2
2
1497519961768 (1, 1)
2
2
1497519961768 (1, 1)

# 例2
def foo(a=[], b=1):
    a.append(1)
    b += 1
    print(a)
    print(b)
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
----------------
1497519961632 ([], 1)
[1]
2
1497519961632 ([1], 1)
[1, 1] # 注:在函数对象__defaults__属性中保存是a对象的引用
2
1497519961632 ([1, 1], 1)
  • ++=
# 对比如下两个例子
# 例1
def x(a=[]):
    a += [5]   # 修改原列表,在其后追加一个列表,类似extend方法
print(x.__defaults__)
x()
x()
print(x.__defaults__)
-------------------------
([],)
([5, 5],)

# 例2
def x(a=[]):
    a = a + [5]  # 合并两个列表并返回一个全新的列表
print(x.__defaults__)
x()
x()
print(x.__defaults__)
--------------------------
([],)
([],)

闭包closure


  • 自由变量:未在本地作用域中定义的变量,如定义在内层函数外的外层函数的作用域中的变量
  • 闭包:在嵌套函数中,内层函数引用了外层函数的自由变量(内部函数访问外部函数的局部变量)
  • 只有涉及嵌套函数时才有自由变量和闭包的问题
# 对比如下两个例子
# 例1
def counter():
    c = [0]
    def inc():
        c[0] += 1 # 不报错,只是修改c的元素,并不是重新定义局部变量c
        return c[0]
    return inc

foo= counter()
print(foo())

# 例2
def counter():
    c = 0
    def inc():
        c += 1 # 报错,重新定义局部变量做+1运算前未赋值,可用global解决或者nonlocal解决
        return c
    return inc
foo = counter()
print(foo())
  • nonlocal
    • 将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中非全局作用域
def counter():
    c = 0
    def inc():
        nonlocal c # 声明变量c不是在本地作用域中
        c += 1 # 不报错,这样形成了内层函数引用了外部作用域的自由变量,即闭包
        return c
    return inc
foo = counter()
print(foo())
  • 综上:
  1. 闭包是指函数对象可以引用在函数外部定义的变量,并且在函数内部可以使用这些外部变量
  2. 在 Python 中,当函数定义在另一个函数内部时,内部函数可以引用外部函数的变量,即使外部函数已经执行完毕,内部函数仍然可以访问这些变量。这种函数和其所引用的外部变量的组合被称为闭包
  3. 闭包通常用于函数式编程和回调函数中。在函数式编程中,闭包可以用来创建类似于**柯里化(currying)和偏函数(partial function)**的效果。在回调函数中,闭包可以保存一些上下文信息,使得回调函数可以访问到一些在注册回调时不可知的数据
  4. 闭包可以用于封装数据和行为,保护数据不被外部直接访问和修改。通常情况下,我们可以将闭包看作是一个函数和它所引用的外部变量组成的包裹,可以将它作为一个整体进行操作和传递
# 补充示例
def outer_function(x):
    def inner_function(y):
        return x + y

    return inner_function


closure = outer_function(10)
print(closure(5))

高阶函数


接收函数为参数,或者把函数作为结果返回的函数为高阶函数

柯里化curring


  • 将原来接受两个参数的函数变成新的接受一个参数的函数的过程,如 add(4,5) -> add(4)(5)
  • 新的函数返回一个以原有第二个参数为参数的函数,类似 t = add(4) 是个函数, t(5) 完成调用
  • 用嵌套函数实现
# 将如下函数柯里化如add(4,5)
def add(x,y):
    return x + y

# 柯里化效果add(4)(5)
def add(x):
    def _add(y):
        return x + y
    return _add

装饰器decorator


  • 装饰器是一种函数或类,它可以接受另一个函数或类作为输入,并返回一个新的函数或类,用于修改或扩展输入函数或类的行为。可以将装饰器看作是一种“包装器”,用于在不改变原始函数或类代码的情况下,为其添加新的功能或行为
  • 装饰器是Python语言的一种语法糖,它允许在代码运行期间动态地修改函数或类的行为,可以使用 @functionname 语法糖方式调用
  • 无参装饰器和有参装饰器
  • 典型例子
# 例1
# 即对logger(fn,*args,*kwargs)柯里化写成嵌套结构
def logger(fn):
    def wrapper(*args,**kwargs):
        print('begin') #增强函数功能,在函数调用前做些事情
        x = fn(*args,**kwargs)
        print('end') # 在函数调用后做些事情
        return x
    return wrapper
# 无参装饰器实际上是一个单形参的函数       
@logger  #等价于add = logger(add)
def add(x,y):
    return x + y
print(add(45,40))
print(add.__name__)

@logger相当于add = logger(add)且更加简洁
add.__name__获取函数元信息(不再是以前的那个add()函数,而是被 wrapper() 函数取代了)。为了解决这个问题,通常使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)

例子2: 获取函数执行时间
import time
import functools

def timer(func):
    print("my decorator start....")

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        ret = func(*args, **kwargs)
        end = time.perf_counter()
        cost_time = end - start
        print("wrapper....")
        return ret, cost_time

    print("my decorator end.....")
    return wrapper


@timer
def add(*args):
    time.sleep(1)
    result = 0
    for i in args:
        result += i
    return result

@timer
def sub(x, y):
    time.sleep(1)
    return x - y

print(add(1, 2, 3, 4))
print(sub(10, 11))
print(add.__name__, sub.__name__)

文档字符串

  • 在函数语句块的第一行,可使用__doc__访问到
def add(x,y):
    """This is a function of addition"""
    return x + y

print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
--------------------------------
name=add
doc=This is a function of addition

Help on function add in module __main__:

add(x, y)
    This is a function of addition

None

装饰器属性替换问题

  • 一个计算函数执行时间的装饰器
import datetime
import time

def logger(fn):
    def wrapper(*args, **kwargs):
        '''I am wrapper'''
        # before 功能增强
        print("args={}, kwargs={}".format(args,kwargs))
        start = datetime.datetime.now() 
        # 计算时长
        ret = fn(*args, **kwargs)
        # after 功能增强
        duration = datetime.datetime.now() - start
        print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
        return ret
    return wrapper

@logger # 相当于add = logger(add)
def add(x, y):
    '''This is a function for add'''
    print("===call add===========")
    time.sleep(1)
    return x + y

print(add(4, y=7))
print("name={}, doc={}".format(add.__name__, add.__doc__))
--------------------------------------------------------------
args=(4,), kwargs={'y': 7}
===call add===========
function add took 1.000302s.
11
name=wrapper, doc=I am wrappe #add函数的__name__和__doc__属性被替换,怎么解决?

带参装饰器

  • 使用 @functionname(参数列表) 方式调用
# 例1: 自定义一个参数装饰器,控制函数执行次数
import functools

def repeat(num):
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(num):
                func(*args, **kwargs)
        return wrapper
    return my_decorator

@repeat(3)
def func():
    print("hello world")

func()
print(func.__name__)
  • 提供一个函数解决函数属性被替换的问题
例2: 不使用functools解决函数属性被替换的问题(利用带参装饰器)
def copy_properties(src): 
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy

def logger(fn):
    @copy_properties(fn) # 带参装饰器wrapper = copy_properties(fn)(wrapper)
    def wrapper(*args, **kwargs):
        '''I am wrapper'''
        start = datetime.datetime.now() 
        ret = fn(*args, **kwargs)
        duration = datetime.datetime.now() - start
        print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
        return ret
    return wrapper

@logger # 相当于add = logger(add)
def add(x, y):
    '''This is a function for add''' 
    time.sleep(1)
    return x + y

print(add(4, y=7))
print("name={}, doc={}".format(add.__name__, add.__doc__))
-----------------------------------------------------------------
function add took 1.000287s.
11
name=add, doc=This is a function for add # 解决了函数属性被替换的问题

类装饰器

class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

example()
example()
--------------
num of calls is: 1
hello world
num of calls is: 2
hello world

类也可以作为装饰器。类装饰器主要依赖于函数__call__(),每当你调用一个类的示例时,函数__call__()就会被执行一次
关于__call__可进一步参考魔法方法

装饰器嵌套

import functools

def my_decorator1(func):
    print('1111111111')
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)
    return wrapper


def my_decorator2(func):
    print('2222222222')
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)
    return wrapper

# 等价于 greet = my_decorator1(my_decorator2(greet))
@my_decorator1
@my_decorator2
def greet(message):
    print(message)

greet('hello world')
-----------------------
2222222222
1111111111
execute decorator1
execute decorator2
hello world
@decorator1
@decorator2
@decorator3
def func(): ...

它的执行顺序从里到外
上面的语句等效于:decorator1(decorator2(decorator3(func)))

  • 补充示例
def decorator_outer(func):
    print(3, "我是外层装饰器")  # 3

    def wrapper():
        print(5, '外层装饰器,函数运行之前')
        func()  # decorator_inner 的 wrapper
        print(9, '外层装饰器,函数运行之后')

    print(4, '外层装饰器闭包初始化完毕')  # 4
    return wrapper


def decorator_inner(func):
    print(1, "我是内层装饰器")  # 1

    def wrapper():
        print(6, '内层装饰器,函数运行之前')
        func()  # func
        print(8, '内层装饰器,函数运行之后')

    print(2, '内层装饰器闭包初始化完毕')  # 2
    return wrapper


@decorator_outer
@decorator_inner  # func = decorator_outer(decorator_inner(func))()
def func():
    print(7, "我是函数本身")


func()
###################输出结果###################
1 我是内层装饰器
2 内层装饰器闭包初始化完毕
3 我是外层装饰器
4 外层装饰器闭包初始化完毕
5 外层装饰器,函数运行之前
6 内层装饰器,函数运行之前
7 我是函数本身
8 内层装饰器,函数运行之后
9 外层装饰器,函数运行之后

wrapper外面的代码,内层装饰器先运行,外层装饰器后运行。但是wrapper里面的代码,是外层装饰器先开始运行,后运行完毕,内层装饰器后开始运行,先运行完毕

装饰器的作用

装饰器的主要作用是实现代码的复用和简化,它可以让代码更加易读、易维护、易扩展。常见的装饰器有函数装饰器和类装饰器,它们可以用来实现日志记录、性能测试、缓存、认证授权等功能

参考