- 函数几个重要的特性
- 在 python 中,一切皆为对象。所以,函数也为对象,从而函数可以被赋值给变量
- 一个函数可以作为另一个函数的参数
- 一个函数可以作为另一个函数的返回值
- 一个函数可以嵌套定义在另一个函数中
- 在 python 中,一切皆为对象。所以,函数也为对象,从而函数可以被赋值给变量
- lambda表达式
- 定义函数的语法格式
- 当函数体中只有一行return语句时,函数的定义可以用一个lambda表达式来代替,其语法格式为:lambda[形参1, 形参2, …, 形参n]: 关于形参表达式
- 与定义函数的语法格式相比,lambda表达式:
- 1、没有函数名
- 2、没有关键字def
- 3、没有小括号
- 4、关于形式参数的表达式相当于函数的返回值
- 总结:lambda表达式就是匿名简化版的函数
- 在python中,一切皆为对象。所以,lambda表达式也是对象,从而lambda表达式可以被赋值给变量
- 因为lambda表达式是匿名简化版的函数,所以,lambda表达式可以作为函数的实参
- 因为lambda表达式是匿名简化版的函数,所以,lambda表达式可以作为函数的返回值
- 定义函数的语法格式
- 偏函数
- 定义函数时,可以给形参设置默认值,从而简化函数的调用,只有与默认值不符的形参,才需要传递额外的实参。
- 偏函数也可以简化函数的调用
- 可以将某个已有的函数转换为一个新函数,在转换的过程中指定最前面若干个位置实参以及关键字实参,这样,当调用新函数的时候,在其内部调用的仍然是转换前的函数,在传递实参时只需要传递剩余的位置实参和关键字实参就可以了。转换后的新函数被称为转换前的函数的偏函数
- 借助于标准库的模块 functools 中的 partial(func, args, *kwargs),可以将某个已有的函数转化为偏函数
- 例子
- 例1
- 例2
- 例3
- 例4
- 例1
- 闭包
- 如果在一个函数的内部嵌套定义了另外一个函数(姑且将外部的函数和内部的函数分别称之为外函数和内函数),内函数中引用了外函数中的变量,并且外函数的返回值是内函数,这样,就构成了一个闭包。
- 通常情况下,在函数调用结束后,函数内定义的变量将不再可用。
- 但是,对于闭包而言,在外函数调用结束后,外函数中被内函数引用的变量仍然是可用的,因为外函数中内内函数引用的变量会被绑定到内函数的特殊属性closure中。
- 在默认情况下, 在内函数中是不能修改外函数中的变量引用的对象(如果引用的变量是可变类型的,可以修改对象的内容)
- 如果想在内函数中修改外函数中的变量所引用的对象,可以在内函数中使用关键字 nonlocal 对变量进行声明,从而表明在内函数中并没有重新定义一个新的同名变量,而是使用外函数中该名称的变量。
- 对于内函数中引用的外函数的变量,在调用内函数后对该变量的修改,在下一次调用内函数时仍然是有效的,因为该变量会被绑定到内函数的特殊属性 closure中
- 如果在一个函数的内部嵌套定义了另外一个函数(姑且将外部的函数和内部的函数分别称之为外函数和内函数),内函数中引用了外函数中的变量,并且外函数的返回值是内函数,这样,就构成了一个闭包。
- 变量的作用域
- 变量的作用域指的是变量起作用的范围。变量的作用域由定义变量的位置决定的。
- 变量的作用有4种:
- 局部作用域(Local)
- 每次调用函数时都会创建一个局部作用域。
- 局部作用域(函数)中定义的变量称之为局部变量。
- 局部变量的作用域为:从定义变量处开始到函数结束。
- 函数调用结束后,其对应的局部作用域中的所有变量都会被销毁。
- 局部作用域(Local)
- 嵌套作用域(Enclosing)
- 每次调用嵌套函数中的外函数时都会创建一个嵌套作用域。
- 当在外函数内定义变量时,该变量的作用域为:从定义变量处开始到函数结束。
- 外函数调用结束后,其对应的嵌套作用域中的所有变量都会被销毁(闭包除外)。
- 嵌套作用域(Enclosing)
- 全局作用域(Global)
- 每次运行模块时都会创建一个全局作用域
- 全局作用域(模块)中定义的变量称之为全局变量
- 全局变量的作用域为:从定义变量处开始到模块结束
- 程序运行结束后,全局作用域中的所有变量都会被销毁
- 全局作用域(Global)
- 内置作用域(Built-in)
- 每次启动Python解释器都会自动加载内置模块,从而创建一个内置作用域
- 内置模块中的函数(内置函数),可以在程序中直接使用
- 停止解释器后,内置作用域中的所有变量都会被销毁
- 内置作用域(Built-in)
- 作用域范围
- 当在某个作用域中访问变量时,会按照LEGB的顺序依次搜索该作用域及后面的所有作用域,只要找到了就停止搜索,如果没有找到则抛出 NameError。因此,如果不同的作用域中定义了同名的变量,根据LEGB的搜索顺序,前面作用域中的变量会屏蔽掉后面作用域中定义的同名变量
- 在默认情况下,在局部作用域或嵌套作用域中不能修改全局变量所引用的对象(如果引用的对象是可变类型的,可以修改对象的内容),与闭包类似
- 如果想在局部作用域或嵌套作用域中不能修改全局变量所引用的对象,可以在局部作用域或嵌套作用域中使用关键字global对变量进行申明,从而表明在局部作用域或嵌套作用域中不会重新定义一个新的同名变量,而是使用该名称的全局变量。与闭包的nonelocal类似
- 流程控制语句和异常处理语句不会创建对应的作用域,因此,对于流程控制语句和异常处理语句中定义的变量,在语句执行结束之后仍然是可用的。
- 内置函数locals() 和 globals()
- 命名空间指的是某个作用域内所有名字和值得映射,用字典表示。
- 内置函数locals()可以返回在其所在局部作用域的命名空间
- 内置函数globals()可以返回其所在全局作用域的命名空间
- locals()并没有返回实际的命名空间,而是返回值得拷贝,所以通过locals()修改某个名字对应的值,对于实际的命名空间是没有影响的;但是,可以通过locals()向实际的命名空间中添加一个名字和值得映射。
- globals()返回的是实际的命名空间,所以,对globals()所做的任何修改,其实就是实际命名空间的修改。
- 可以在局部作用域或嵌套作用域中通过globals()访问全局作用域中被屏蔽的全局变量
- 对于内置函数vars(),查看其帮助信息可知:
- 函数装饰器
- 对于某个函数,如果我们希望在不改变该函数代码的情况下,为该函数增加额外的功能,那么就可以使用装饰器来装饰该函数。
- 装饰器是一个函数,装饰器接收一个函数作为参数(传入的实参是被装饰的函数),装饰器的内部嵌套定义另一个函数,内函数中会引用装饰器的参数,并且装饰器的返回值是内函数,这样,就构成了一个闭包。为了让内函数接收任意类型的参数,将内函数的形参定义为(args, *kwargs)。在函数中,首先完成为被装饰函数添加的新功能,然后调用被装饰的函数。
- 把装饰器应用到被装饰函数的语法为:在被装饰函数的前面添加”@装饰器的函数名”。在被装饰器函数add的前面添加@log后,相当于执行了语句:add = log(add),首先,被装饰的函数add会作为实参传递给装饰器log,然后,返回装饰器的内函数wrapper,最后,将函数wrapper赋值给名为add(被装饰函数的函数名)的变量,这样,再调用被装饰的函数add时,其实调用的是装饰器的内函数wrapper。
- 如果希望被装饰函数的特殊属性name的值为其函数名,而不是装饰器的内函数的函数名,可以在装饰器的内函数前面添加另外一个装饰器:@wraps(装饰器的参数名),其中wraps指的是标准库模块functools中的函数wraps。
- 把装饰器应用到被装饰的函数时,还可以传递额外的参数。此时,需要编写一个3层嵌套的装饰器。对于@log3(‘6月’, ‘18日’),相当于执行了语句:相当于 add3 = log(‘6月’, ‘18日’)(add3)。