1.什么是函数,为什么要使用函数
1.1什么是函数
函数是对程序逻辑进行结构化或者过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝。
1.2为什么要使用函数
# 例:len函数的编写 # 在没有函数的时候输出字符串、列表的长度 s1 = 'asdfjkl;' count = 0 for i in s1: count += 1 print(count) l1 = 'asdfjkl;' count = 0 for i in l1: count += 1 print(count) ### 这样编写的缺点: # 重复代码多 # 可读性差
2.函数结构调用
2.1函数结构:
def 函数名(参数列表): # 函数名的命名规则与变量与变量相同
函数体
''' 规则: 1.函数名是由数字字母下划线任意组合。 2.函数名不能是数字开头。 3.函数名不能是Python中的关键字。 ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'] 4.函数名要具有可描述性。 5.函数名不能使用中文。 6.函数名不能过长。 推荐: 字母和下划线的组合 '''
2.2函数调用:
函数名()
# 例 def func1(): pass func1() # 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者 print(func1()) # 1- 执行函数func1(),得到返回值None # 2- 输出None
3.函数的返回值return
函数内部尽量不要使用print,因为函数是以功能为导向的。
函数返回值的作用
# 1- 遇到return,结束函数。 def func1(): print(11) print(22) return print(33) print(44) func1() # 11 # 22 # 2- 给函数的调用者(执行者)返回值 # 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者 # 返回值的个数对应返回内容 # 2.1- 无return 返回None def ret(): pass print(ret(), type(ret())) # None <class 'NoneType'> # 2.2- 无return # 不写或者None 返回None def ret(): pass return print(ret(), type(ret())) # None <class 'NoneType'> def ret(): pass return None print(ret(), type(ret())) # None <class 'NoneType'> # 返回单个数 返回相应数据类型 def ret(): pass return 6 print(ret(), type(ret())) # 6 <class 'int'> # 返回多个数据 将多个数放在元组中返回 def ret(): pass return 6, 'abc', [1, 2, 3] print(ret(), type(ret())) # (6, 'abc', [1, 2, 3]) <class 'tuple'> # 可以使用类似变量的分别赋值 a, b, c = ret() print(a) print(b) print(c) # 6 # abc # [1, 2, 3]
4.函数的传参
4.1实参与形参
''' 实参和形参 实参:实际参数,调用函数时传给函数的参数,可以是常量、变量、表达式、函数,传给形参 形参:形式参数,不占内存空间,形参变量只有在调用时才分配内存单元,目的是函数调用时接收实参 二者区别: 实参:是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参 形参:虚拟的,不占用内存空间,形参变量只有被调用时才分配内存单元 ''' # 例 s1 = 'asdfjkl;' def my_len(a): # 函数的定义()放的是形式参数,形参 count = 0 for i in a: count += 1 return count ret = my_len(s1) # 函数的执行()放的是实际参数,实参 print(ret) # 8
4.2按实参分类
# 按实参来分 # 1- 位置参数,实参个数必须与形参个数一一对应,而且有顺序的 # 例 def func1(x, y): print(x, y) func1(1) # func1(1) # TypeError: func1() missing 1 required positional argument: 'y' func1(1, 2) # 1 2 # 2- 关键字参数,实参个数必须与形参个数一一对应,不分顺序 # 例 def func1(x, y, z): print(x, y, z) func1(y=2, x=1, z=5) # 1 2 5 # 3- 混合参数,实参个数必须与形参个数一一对应,而且关键字参数必须在位置参数后面。 # 例 def func2(a, b, c): print(a) print(b) print(c) func2(1, 2, c=4) # 1 # 2 # 4 func2(1, c=4, 2) # 关键字参数必须在位置参数后面 # func2(1, c=4, 2) # SyntaxError: positional argument follows keyword argument
4.3比较大小练习
# 练习:比大小 def max(x, y): if x > y: return x else: return y print(max(5, 3)) # 5 ### 三元运算表达式 def max(x, y): ret = x if x > y else y return ret print(max(4, 3)) # 4 # 简写1 def max(x, y): return x if x > y else y print(max(4, 3)) # 4 # 简写2 def max(x, y):return x if x > y else y print(max(4, 3)) # 4
4.4按形参分类
# 1- 位置参数,形参个数必须与实参个数一一对应,而且有顺序的 # 例 def func1(x, y): print(x, y) func1(1) # func1(1) # TypeError: func1() missing 1 required positional argument: 'y' func1(1, 2) # 1 2 # 2- 默认参数,必须在位置参数后面 # 例:人员信息录入,可以减少大量重复信息的输入 def register(name, sex='男'): with open('register', encoding='utf-8', mode='a') as f1: f1.write('{} {} '.format(name, sex)) while True: name = input('请输入姓名:/q或Q退出 ') if name.upper() == 'Q': break if 'f' in name: sex = input('请输入性别:') register(name, sex) else: register(name) ''' 请输入姓名:/q或Q退出 张三 请输入姓名:/q或Q退出 李四 请输入姓名:/q或Q退出 f小红 请输入性别:女 请输入姓名:/q或Q退出 q # 张三 男 # 李四 男 # f小红 女 ''' # 3- 动态参数 *args,**kwargs 万能参数 def func2(*args, **kwargs): print(args) # 元组(实参的所有位置参数) print(kwargs) # 字典(实参的所有关键字参数的键值对形式) func2(1, 2, 3, 4, 5, 'alex', [2, 3, 4], a='www', b=222) # (1, 2, 3, 4, 5, 'alex', [2, 3, 4]) # {'a': 'www', 'b': 222}
4.5形参顺序
# 形参最终顺序:位置参数,*args,默认参数,**kwargs # 1- *args位置 def func3(a, b, sex='男', *args): print(a) print(b) print(sex) print(args) func3(1, 2, '老男孩', 'alex', 'wusir') # 如果默认参数在args前面,默认参数会被覆盖掉 # 1 # 2 # 老男孩 # ('alex', 'wusir') def func3(a, b, *args, sex='男'): print(a) print(b) print(sex) print(args) func3(1, 2, '老男孩', 'alex', 'wusir') # 1 # 2 # 男 # ('老男孩', 'alex', 'wusir') # **kwargs位置 # def func3(a, b, *args, **kwargs, sex='男'): # SyntaxError: invalid syntax 语法错误 def func3(a, b, *args, sex='男', **kwargs): print(a) print(b) print(sex) print(args) print(kwargs) func3(1, 2, '老男孩', 'alex', 'wusir', sex='女', name='wusir') # 1 # 2 # 女 # ('老男孩', 'alex', 'wusir') # {'name': 'wusir'}
4.6参数的聚合和打散(*、**)
# 例 def func1(*args, **kwargs): # 函数定义时*表示聚合 print(args) print(kwargs) l1 = [1, 2, 3, 4] l2 = ['alex', 'wusir', 4] func1(l1, l2) # ([1, 2, 3, 4], ['alex', 'wusir', 4]) func1(*l1, *l2) # 表示将列表l1和l2打散,把每一个元素当成位置参数,传入函数 # (1, 2, 3, 4, 'alex', 'wusir', 4) # 函数的执行:*打散功能,把每一个元素当成位置参数,传入函数 func1(1, 2, 3, 4, 'alex', 'wusir', 4) # (1, 2, 3, 4, 'alex', 'wusir', 4) dic1 = {'name1':'alex'} dic2 = {'name2':'laonanhai'} func1(dic1, dic2) # 不添加**表示两个位置参数,传入函数args,输出元组 ({'name1': 'alex'}, {'name2': 'laonanhai'}) func1(**dic1, **dic2) # 添加**表示将字典打散,当成两个关键字参数,传入函数kwargs,输出字典 # {'name1': 'alex', 'name2': 'laonanhai'}
5.名称空间
5.1名称空间的由来
Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。
# 例
# python是解释型编程语言,当程序开始执行时,将代码一行一行的解释成二进制,执行。 name = 'alex'
# 在内存中开辟一块空间,将变量name与值'alex'的对应关系存入内存
age = 12
# 在内存中开辟一块空间,将变量age与值12的对应关系存入内存
def func1():
# 定义函数的时候,解释器在内存中开辟一块空间,象征性的将函数名存入内存,**并不关系函数内部
name1 = 'wusir'
# 函数中的变量只在函数内部使用
age1 = 34
# 函数中的变量只在函数内部使用
func1() # 执行函数的时候,解释器会在内存中再开辟一块空间,才会关注函数内部的结构逻辑,而函数中的变量只在函数内部使用,随着函数的结束,这块内存中的内容也会被清空
5.2名称空间的分类
内置名称空间:中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。
全局名称空间:代码在运行伊始,创建的存储“变量名与值的关系”的空间
局部名称空间:在函数的运行中开辟的临时的空间,该空间在函数调用时生效,调用结束后失效
加载顺序:内置名称空间------>全局名称空间----->局部名称空间
名字的查找顺序:局部名称空间------>全局名称空间----->内置名称空间
5.3作用域
全局作用域:全局名称空间,内置名称空间
局部作用域:局部名称空间
5.4加载顺序,取值顺序
加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时)
取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间
# 例:取值顺序 name1 = 'wusir' def func1(): name1 = 'alex' print(name1) func1() # alex # 说明:函数先从小的作用域(局部作用域)取值,也就是函数内部取值,打印,结果就为alex
5.5输出全局名称空间变量和局部名称空间变量内置函数globals()和locals()
# globals() 输出全局名称空间的变量 name1 = 'wusir' def func1(): name1 = 'alex' print(globals()) func1() # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001D4349BB278>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/temp.py', '__cached__': None, 'name1': 'wusir', 'func1': <function func1 at 0x000001D432B42EA0>} # locals() 输出局部名称空间的变量 name2 = 'wusir' def func2(): name2 = 'alex' print(locals()) func2() # {'name2': 'alex'} # 在局部名称空间,可以对全局名称空间的变量进行引用,但是不能改变 count = 1 def func1(): count += 1 print(count) func1() # count += 1 # UnboundLocalError: local variable 'count' referenced before assignment
5.6关键字global、nonlocal
global
# global 1- 在局部名称空间声明一个全局变量,只要函数运行一次,全局变量即生效 # 2- 如果变量存在,可以更改全局变量 # 在全局名称空间引用局部名称空间变量会报错 def func1(): name = 'alex' return print(name) # print(name) # NameError: name 'name' is not defined # 添加global关键字,如果全局变量不存在,可以声明全局变量,只要函数运行一次,全局变量即生效 def func1(): global name name = 'alex' return func1() # 必须执行一次,函数内部的结构,才能加载到内存中 print(name) # alex # 不添加global,如果全局变量存在,全局变量不会改变 name = 'wusir' def func1(): # global name name = 'alex' return func1() print(name) # wusir # 添加global,如果全局变量存在,可以修改全局变量 name = 'wusir' def func1(): global name name = 'alex' return func1() print(name) # alex
nonlocal
# nonlocal 1- 不能修改全局变量。 # 2- 在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。 # 例1 name1 = 'alex' def func1(): nonlocal name1 name1 = 'wusir' func1() # nonlocal name1 # ^ # SyntaxError: no binding for nonlocal 'name1' found # 例2 def func1(): name1 = 'alex' print('1.'+name1) # 打印局部变量name1 -- alex def inner(): nonlocal name1 # 修改局部变量name1 -- wusir name1 = 'wusir' print('2.'+name1) # 打印修改后的局部变量name1 -- wusir inner() print('3.'+name1) # 依然在局部名称空间中,打印修改后的局部变量name1 -- wusir func1() # 1.alex # 2.wusir # 3.wusir
6.函数名
# 函数名-- 有普通变量的功能和加()就执行的功能 # 1.可以互相赋值,将函数赋值f1,变量加括号就执行 def func1(): print(666) f1 = func1 f1() # 666 # 2.函数名可以当成函数的参数 def func1(): print(666) def func2(argv): argv() print(777) func2(func1) # 666 # 777 # 3.可以当成容器类数据类型的参数 def func1(): print(666) def func2(): print(777) def func3(): print(888) l1 = [func1, func2, func3] for i in l1: i() # 666 # 777 # 888 # 4.函数名可以当成函数的返回值,普通变量有的功能函数名都有 def func1(): print(666) def func2(argv): print(777) return argv ret = func2(func1) ret() # 777 # 666
7.闭包
# 闭包 内层函数对外层函数非全局变量的引用,叫做闭包 # 闭包的好处:如果python检测到闭包 # 他有一个机制,你的局部作用域不会随着函数的结束而结束,爬虫的重复利用机制 # # 这种形式: def wrapper(): name1 = 'laonanhai' def inner(): print(name1) inner() wrapper() # laonanhai # 这不是闭包,因为引用了全局变量,所以返回None name1 = 'laonanhai' def wrapper(): def inner(): print(name1) inner() print(inner.__closure__) wrapper() # None # 这是闭包 内层函数对外层函数非全局变量的引用 def wrapper(argv): # argv = 'alex' def inner(): print(argv) inner() print(inner.__closure__) name = 'alex' wrapper(name) # (<cell at 0x00000159C8B885E8: str object at 0x00000159C8B4C378>,) # 例:简单爬虫 from urllib.request import urlopen def index(): url = "http://www.cnblogs.com/gnaix" def get(): return urlopen(url).read() return get content1 = index()() content2 = index()() print(content1)
7.装饰器
7.1用两种方式执行函数的内嵌函数
# 用两种方式执行函数的内嵌函数 def wrapper(): def inner(): name1 = 'alex' print(name1) wrapper() # 方法一: 在wrapper函数内执行inner函数 def wrapper(): def inner(): name1 = 'alex' print(name1) inner() wrapper() # 方法二:利用函数的返回值,将inner函数名返回wrapper函数的调用者 def wrapper(): def inner(): name1 = 'alex' print(name1) return inner ret = wrapper() ret() # 或 wrapper()() # alex # alex # 错误实例: def wrapper(): def inner(): name1 = 'alex' print(name1) inner() # 错误提示 # inner() # NameError: name 'inner' is not defined # 原因分析 # 取值问题:取值顺序,有内而外 # 加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时) # 取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间
7.2装饰器的推导过程
# 装饰器的产生,测试函数的执行效率(时间) # 1.代码方式检测 import time def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) start_time = time.time() func1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) # 晚上回去吃烧烤... # 函数的执行效率为 0.3010380268096924 # 2.简单函数方式,不可以重复利用 import time def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) def timer(): start_time = time.time() func1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) timer() # 晚上回去吃烧烤... # 函数的执行效率为 0.30023193359375 # 3.通过传参的函数方式,可以重复利用 # 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1) # 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题 import time def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) def func2(): print('晚上回去喝啤酒...') time.sleep(0.3) def timer(f1): start_time = time.time() f1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) timer(func1) timer(func2) # 晚上回去吃烧烤... # 函数的执行效率为 0.30021190643310547 # 晚上回去喝啤酒... # 函数的执行效率为 0.30055713653564453 # 4.通过传参的函数方式,可以重复利用 # 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1) # 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题 import time def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) def func2(): print('晚上回去喝啤酒...') time.sleep(0.3) def timer(f1): start_time = time.time() f1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) f = func1 func1 = timer func1(f) # 晚上回去吃烧烤... # 函数的执行效率为 0.30002880096435547 # 5.最简单版的装饰器 import time def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) def func2(): print('晚上回去喝啤酒...') time.sleep(0.3) def timer(f1): def inner(): start_time = time.time() f1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) return inner # 关系转换 func1 = timer(func1) # inner func1() # inner(),这个func1是一个新的变量 # 晚上回去吃烧烤... # 函数的执行效率为 0.3011753559112549 # 遇到等号,先执行等号右面的,右面等于执行timer(func1)这里的func1仅代表函数名 # func1 == inner # Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来, 但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。 # 等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中, 函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。 # 6.语法糖 # 装饰器,在不改变原函数即原函数的调用的情况下,为原函数 # 增加一些额外的功能,打印日志,执行时间,登录认证等等。 import time def timer(f1): def inner(): start_time = time.time() f1() end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) return inner @timer # 关系转换,func1 = timer(func1) # inner def func1(): print('晚上回去吃烧烤...') time.sleep(0.3) func1() # 晚上回去吃烧烤... # 函数的执行效率为 0.30006885528564453 # 7.被装饰函数带参数 import time def timer(f1): # f = func1 def inner(*args, **kwargs): start_time = time.time() f1(*args, **kwargs) end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) return inner @timer # 关系转换,func1 = timer(func1) # inner # func1 = timer(func1) # 遇到等号先执行等号右面的,timer(func1)此时func1是以函数名 # 的形式传入timer的参数,返回给timer的调用者inner函数名 # 所以func1 ==> inner def func1(a, b): print(a, b) print('晚上回去吃烧烤...') time.sleep(0.3) func1(111, 222) # ==>timer(func1)(111, 222) # 111 222 # 晚上回去吃烧烤... # 函数的执行效率为 0.3001117706298828 # 8.带返回值的装饰器 import time def timer(f1): def inner(*args, **kwargs): start_time = time.time() ret = f1(*args, **kwargs) # func1()的调用者 end_time = time.time() print('函数的执行效率为 %s' % (end_time - start_time)) return ret return inner @timer # func1 = timer(func1) inner def func1(a, b): print(a, b) print('晚上回去吃烧烤...') time.sleep(0.3) return 666 print(func1(111, 222)) # 111 222 # 晚上回去吃烧烤... # 函数的执行效率为 0.3004331588745117 # 666 # 9.最终版本装饰器 # 简单版 def wrapper(f1): def inner(): f1() return inner # 带参数,返回值版 def wrapper(f1): #f1被装饰函数的函数名 def inner(*args, **kwargs): '''调用函数之前的操作''' ret = f1(*args, **kwargs) '''调用函数之后的操作''' return ret return inner