python函数基础
1.函数的定义
在python中 ,函数是一个组织好的 ,可以重复使用的代码块 ,函数可以提高代码的重复利用率。
函数的特性:
函数可以没有参数
函数的参数可以是一个,也可以是多个
当你使用def 定义了一个函数后,只有调用这个函数才能让这个函数运行
return的作用是退出函数并返回函数的返回值,任何时候,只要执行了return语句就一定会退出函数
函数没有使用return语句,但仍然会有返回值,这种情况下,函数默认返回None
函数一次返回多个结果时,是以元组的形式返回的
2.函数参数
python函数的参数可以分为位置参数,默认参数,关键字参数,可变参数,函数可以没有参数,也可以有多个参数,参数是传递给函数的命名变量。
形参和实参 所谓形参,就是函数定义中的参数,形参在函数体内可以使用,而实参,则出现在调用过程中
必传参数(位置参数)
默认参数 定义函数是时,如果有多个默认参数,他们必须放置在参数列表的最后,不允许在中间放置一个必传参数。
关键字参数 关键字参数不是一个出现在函数定义时的概念,而是一个出现在函数调用时的概念。 关键字参数允许你以任何顺序传递参数,只要必传参数都以key=value的形式传值即可
可变参数 可变参数分为两种:
*args 接受任意多个实际参数 所有传入的参数将被放入到一个元组中,因此args的类型是tuple元组
**kwargs接收任意多个以关键字参数赋值的实际参数 在调用函数时,以关键字参数的形式进行参数传递,最终这些key-value对将组装成字典,kwargs的类型是dict。
3.函数名称
函数名称其实是一个变量名,def表示将保存在某块内存区域中的函数代码体赋值给函数名变量。
4.函数返回值
python的函数支持返回多个值。返回多个值时,默认以tuple的方式返回
5.函数作用域
作用域(scope)是指程序中变量的可见性和生命周期范围。 Python 中的作用域可以分为以下几类:
局部作用域(Local Scope)
嵌套作用域(Enclosing Scope)
全局作用域(Global Scope)
内置作用域(Built-in Scope) 这些作用域遵循一个称为 LEGB(Local, Enclosing, Global, Built-in)的规则来解析标识符。
局部作用域(Local Scope) 局部作用域是指在函数内部定义的变量和参数,它们只在函数内部可见和生效。当函数执行完毕后,局部变量会被销毁。
嵌套作用域(Enclosing Scope) 嵌套作用域指的是嵌套函数(一个函数内部定义的另一个函数)中的变量。内层函数可以访问外层函数的局部变量,但不能修改它们,除非使用 nonlocal 关键字。
def outer_function():
outer_var = 20 # 外层函数的局部变量
def inner_function():
print(outer_var) # 内层函数可以访问外层函数的变量
inner_function()
outer_function() # 输出: 20
如果需要在内层函数中修改外层函数的变量,可以使用 nonlocal 关键字:
def outer_function() -> None:
outer_var = 20 # 外层函数的局部变量
def inner_function() -> None:
nonlocal outer_var
outer_var = 80
print(outer_var) # 内层函数可以访问外层函数的变量
inner_function()
print(outer_var)
outer_function() # 输出: 80
全局作用域(Global Scope) 全局作用域指的是在模块级别定义的变量,它们在整个模块中都可以访问。全局变量在模块的生命周期内一直存在。
global_var = 50 # 全局变量
def my_function():
print(global_var) # 函数内部可以访问全局变量
my_function() # 输出: 50
print(global_var) # 输出: 50
如果需要在函数内部修改全局变量,可以使用 global 关键字:
内置作用域(Built-in Scope) 内置作用域是 Python 语言内置的一些标识符和函数,这些标识符在所有作用域中都可以访问。它们由 Python 内置模块(如 builtins 模块)提供。 LEGB 规则 Python 解析变量名时,按照 LEGB 规则依次查找:
Local(局部作用域):首先在当前函数内部查找。
Enclosing(嵌套作用域):然后在外层函数的作用域中查找。
Global(全局作用域):再然后在模块的全局作用域中查找。
Built-in(内置作用域):最后在内置作用域中查找。
如果在所有这些作用域中都找不到变量名,就会抛出 NameError 异常。
global_var = 'global'
def outer_function():
outer_var = 'outer'
def inner_function():
inner_var = 'inner'
print(inner_var) # 内部局部作用域
print(outer_var) # 外层局部作用域
print(global_var) # 全局作用域
print(len) # 内置作用域
inner_function()
outer_function()
6.函数类型
6.1 内置函数
6.2 匿名函数
匿名函数是指没有名称的函数,任何编程语言中,匿名函数都扮演着重要角色,它的功能非常灵活,但是匿名函数中的逻辑一般很简单,否则直接使用命名函数更好,匿名函数常用于回调函数、闭包等等。
lambda argl, arg2,... argN :expression statement
匿名函数的返回值是冒号后面的表达式计算得到的结果
6.3 嵌套函数
函数内部可以嵌套函数。一般来说,在函数嵌套时,内层函数会作为外层函数的返回值(当然,并非必须)。既然内层函数要作为返回值,这个嵌套的内层函数更可能会是lambda匿名函数
6.4 闭包函数
闭包(Closure)是指在一个函数内部定义的另一个函数,这个内部函数引用了外部函数的变量,并且外部函数返回了这个内部函数。这使得内部函数在外部函数执行完毕后,仍然能够访问和使用外部函数的变量。闭包是一个强大的编程工具,能够使得函数具有记忆功能和更高的抽象能力。 闭包包含以下几个要素:
一个外部函数,它定义了一些局部变量。
一个内部函数,它引用了外部函数的局部变量。
外部函数返回内部函数,使得内部函数能够在外部函数执行完毕后继续使用外部函数的变量。
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
print(closure(5)) # 输出: 15
print(closure(20)) # 输出: 30
闭包的应用场景
数据隐藏:闭包可以用来隐藏数据,只暴露接口。
工厂函数:闭包可以用来创建带有特定参数的函数。
回调函数:闭包可以携带状态信息,用于回调函数中。
# 数据隐藏
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter_a = make_counter()
print(counter_a()) # 输出: 1
print(counter_a()) # 输出: 2
counter_b = make_counter()
print(counter_b()) # 输出: 1
# 工厂函数
def power_factory(exponent):
def power(base):
return base ** exponent
return power
square = power_factory(2)
print(square(3)) # 输出: 9
cube = power_factory(3)
print(cube(2)) # 输出: 8
闭包的优势 状态保持:闭包可以保持函数执行时的状态,使得函数在多次调用时能够维持状态。 代码简洁:闭包可以简化代码,避免使用全局变量和类。 提高抽象层次:闭包可以创建高阶函数,提升代码的抽象层次和可读性。 闭包的注意事项 变量生命周期:闭包依赖于外部函数的变量,这些变量在外部函数执行完毕后仍然存在。 内存使用:由于闭包会持有外部函数的变量,可能会导致内存占用增加,需要注意内存管理
6.5 回调函数
有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function) 回调函数的定义 回调函数是指在某个操作完成后被调用的函数。通常,回调函数用于异步编程、事件处理、以及将逻辑解耦以提高代码的可维护性。 回调函数的应用场景 事件处理:在 GUI 或 Web 应用中,回调函数用于处理用户交互事件(如点击按钮)。 异步编程:在处理异步操作(如网络请求或定时器)时,回调函数用于处理操作完成后的结果。 策略模式:在设计模式中,回调函数用于实现策略模式,允许在运行时选择不同的算法。
7.函数属性
7.1 属性分析
python中的函数是一种对象,它有属于对象的属性。除此之外,函数还可以自定义自己的属性。注意,属性是和对象相关的,和作用域无关。
import dis
from typing import Callable
x = 3
def f(a: int, b: int, *args: complex, c: int) -> Callable[[int], int]:
a = 3
y = 10
print(a, b, c, x, y)
def g(z: int) -> int:
return a + b + c + x + z
return g
# f()的show_code结果
dis.show_code(f)
# f()的co_XXX属性
for i in dir(f.__code__):
if i.startswith("co"):
print(i+":",eval("f.__code__."+i))
# 闭包函数,注意,传递了*args参数
f1=f(3,4,"arg1","arg2",c=5)
# f1()的show_code结果
dis.show_code(f1)
# f1()的co_XXX属性
for i in dir(f1.__code__):
if i.startswith("co"):
print(i+":",eval("f1.__code__."+i))
co_name 函数的名称
co_filename 函数定义在哪个文件名中
co_firstlineno 函数声明语句在文件中的第几行。即def关键字所在的行号
co_consts 该函数中使用的常量有哪些。python中并没有专门的常量概念,所有字面意义的数据都是常量
co_kwonlyargcount keyword-only的参数个数
co_argcount 除去args之外的变量总数。实际上是除去和*所收集的参数以及keyword-only类的参数之后剩余的参数个数。换句话说,是或**前面的位置参数个数。
co_nlocals 本地变量个数
co_varnames 本地变量名称,变量名称收集在元组中
co_stacksize 本段函数需要在栈空间评估的记录个数。换句话说,就是栈空间个数。
co_names 函数中保存的名称符号,一般除了本地变量外,其它需要查找的变量(如其它文件中的函数名,全局变量等)都需要保存起来
co_cellvars co_freevars 这两个属性和嵌套函数(或者闭包有关),它们是互相对应的,所以内容完全相同,它们以元组形式存在
co_cellvars 是外层函数的哪些本地变量被内层函数所引用
co_freevars 是内层函数引用了哪些外层函数的本地变量
7.2 属性和字节码对象PyCodeObject
对于python,通常都认为它是一种解释型语言。但实际上它在进行解释之前,会先进行编译,会将python源代码编译成python的字节码(bytecode),然后在python virtual machine(PVM)中运行这段字节码,就像Java一样。但是PVM相比JVM而言,要更”高级”一些,这个高级的意思和高级语言的意思一样:离物理机(处理机器码)的距离更远,或者说要更加抽象。 源代码被python编译器编译的结果会保存在内存中一个名为PyCodeObject的对象中,当需要运行时,python解释器开始将其放进PVM中解释执行,执行完毕后解释器会”根据需要”将这个编译的结果对象持久化到二进制文件*.pyc中。下次如果再执行,将首先从文件中加载(如果存在的话)。 所谓”根据需要”是指该py文件是否只运行一次,如果不是,则写入pyc文件。至少,对于那些模块文件,都会生成pyc二进制文件。另外,使用compileall模块,可以强制让py文件编译后生成pyc文件。 但需要注意,pyc虽然是字节码文件,但并不意味着比py文件执行效率更高,它们是一样的,都是一行行地读取、解释、执行。pyc唯一比py快的地方在导入,因为它无需编译的过程,而是直接从文件中加载对象。 py文件中的每一个代码块(code block)都有一个属于自己的PyCodeObject对象。每个代码块除了被编译得到的字节码数据,还包含这个代码块中的常量、变量、栈空间等内容,也就是前面解释的各种co_XXX属性信息。 pyc文件包含3部分:
4字节的Magic int,表示pyc的版本信息
4字节的int,是pyc的产生时间,如果与py文件修改时间不同,则会重新生成
PycodeObject对象序列化的内容
8.函数冻结
在functools模块中有一个工具partial(),可以用来”冻结”一个函数的参数,并返回”冻结”参数后的新函数。
functools.partial(func, *args, **keywords)
partial()返回的其实是一个partial对象,这个对象包含了3个特殊的属性:
func表示该对象所封装的原始函数
args表示”冻结”的位置参数列表
keywords表示”冻结”的关键字参数 另外需要注意的是,partial()不会保留封装函数的元数据,比如注释文档、注解等。
9.函数装饰器
9.1 基础
谁可以作为装饰器(可以将谁编写成装饰器):
函数
方法
实现了__call__的可调用类
装饰器可以去装饰谁(谁可以被装饰):
函数
方法
类
9.2 定义
@funcA
def funcB():...
def funcB():...
funcB = funcA(funcB)
也就是说,将函数funcB作为函数funcA的参数,funcA会重新返回另一个可调用的对象(比如函数)并赋值给funcB。 所以,funcA要想作为函数装饰器,需要接收函数作为参数,并且返回另一个可调用对象(如函数)。 函数可以同时被多个装饰器装饰,后面的装饰器以前面的装饰器处理结果为基础进行处理: 装饰器函数可以用来扩展、增强另外一个函数。实际上,内置函数中staticmethod()、classmethod()和property()都是装饰器函数,可以用来装饰其它函数。
10.代码块
python 程序是由代码块构建的。块是作为一个单元执行的一段 Python 程序文本。
常见代码块的类型:
模块文件是一个代码块
函数体是一个代码块
class的定义是一个代码块
交互式(python idle)的每一个命令行都是一个独立的代码块
脚本文件是一个代码块
脚本命令是一个代码块(python -c “xxx”)
eval()和exec()中的内容也都有各自的代码块
代码块的作用是组织代码,同时意味着退出代码区块范围就退出了作用域范围. 同一个代码块内,虽然仍然是读一行解释一行,但在退出这个代码块之前,不会忘记这个代码块中的内容,而且会统筹安排这个代码块
11.思考
x = 3
def f1():
print(x)
x=4
f1()
当执行到def语句的时候,因为def声明函数,函数体是一个代码块,所以按照代码块的方式读取属于这个代码块中的内容。首先读取print(x),但并不会直接解释,而是会记住它,并继续向下读取,于是读取x=4,这意味着x是一个本地变量。然后统筹安排整个代码块,将print(x)的x认为是本地变量而非全局变量。注意,直到def退出的时候都还没有进行x的赋值,而是记录了本地变量x,赋值操作是在函数调用的时候进行的。当调用函数f()的时候,发现print(x)中的x是本地变量,但因为还没有赋值,所以报错.
def f1():
for i in range(5):
def n():
print(i)
return n
f1()()
def f1():
L = []
for i in range(5):
def n(i=i):
print(i)
L.append(n)
return L