函数 复习
1、函数的定义:
函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
函数能提高应用的模块性,和降低代码的重复利用率。我们已熟知Python内部提供了很多内建函数,你也可以自己定义创建函数,但别和内部函数(模块)重名,叫做用户自定义函数。
定义一个函数:
可以自定义一个由自己想要功能的函数,以下是创建函数的简单规则:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号起始,并且缩进。
return [表达式] 结束函数,选择性地返回一个值给调用方,返回结果为元组类型。不写表达式return 或 return后没有值,返回的结果是 None。
语法:
def 函数名(参数1,参数2,参数3,,,,):
“描述信息”
函数体
return #用来定义返回值,可以跟任意数据类型
#举例
def print_line():
print("*"*13)
def print_msg():
print("alex lala")
print_line()
print_msg()
print_line()
*************
alex lala
*************
2、函数的调用:
定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接执行。
3、关于return
return语句[表达式] 有return会直接退出函数,选择性地向调用方返回一个表达式。
没有return返回None
return 1返回1 return1,2,3返回(1,2,3)元组 多个return只返回第一个
4、变量的作用域:
一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
两种最基本的变量作用域如下:
全局变量:定义在函数外的拥有全局作用域,py文件内任何位置都可以调用。
局部变量:定义在函数内部的变量,拥有局部作用域,仅能函数内部访问。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
#!/usr/bin/python # -*- coding: UTF-8 -*- total = 0; # 这是一个全局变量 # 可写函数说明 def sum( arg1, arg2 ): #返回2个参数的和." total = arg1 + arg2; # total在这里是局部变量. print "函数内是局部变量 : ", total return total; #调用sum函数 sum( 10, 20 ); print "函数外是全局变量 : ", total 以上示例输出结果为: 1 2 函数内是局部变量 : 30 函数外是全局变量 : 0
5、传参:
Python函数的两种类型参数:一种是函数定义里的形参,一种是调用函数时传入的实参。
从实参的角度:
规则:按位置传值必须在按关键字传值的前面
对一个形参只能赋值一次
1.按照位置传值
2.按照关键字传值
3.混着用
从形参的角度:
规则:默认参数必须放到位置参数的后面
1.位置参数
2.默认参数
3.*args (接收位置传值)
4.**kwargs(接收关键字传值)
6、函数的嵌套
1、python支持嵌套函数;
2、内层函数可以访问外层函数中定义的变量,但不能重新赋值(rebind);
3、内层函数的local namespace[局部名称空间]不包含外层函数定义的变量
函数的嵌套分为:函数的嵌套调用,函数的嵌套定义。这和作用域有关。
#嵌套定义
- 找值的过程:先在局部名称空间找,再到上一级的局部名称空间找,再到全局名称空间找,再到内置名称空间
- 注意:在函数内定义的函数 在外面不能用到
x=1111111
def f1():
x=1
print("----->f1",x)
def f2():
x=2
print("---->f2",x)
def f3():
x=3
print("--->f3",x)
f3()
f2()
f1()
#嵌套调用
- 将一个大的功能细化成各种小的函数功能并调用
def my_max(x,y)
res=x id x>y else y
return res
print(my_max(10,100))
def my_max1(a,b,c,d):
res1=my_max(a,b)
res2=my_max(res1,c)
res3=my_max(res2,d)
return res3
print(my_max1(1,23,34,4))
7、Python的高阶函数
Python的高阶函数,一切皆对象,函数对象
高阶函数就是把函数当做参数传递的一种函数。
函数本身可以赋值给变量,赋值后变量为函数;
允许将函数本身作为参数传入另一个函数;
允许返回一个函数。
def foo():#foo代表函数的内存地址
print('foo')
print(foo)#打印出的是foo函数的内存地址,内存地址加括号就可以调用该函数
#函数可以被赋值
f=foo
print(f)#打印的是foo函数的内存地址
f()#等于foo()
#把函数当成参数传递
def bar(func):
print(func)
func()
bar(foo)#传入的是foo函数的内存地址,运行结果是打印foo函数的内存地址和foo函数的运行结果
#把函数当成返回值
def bar(func):
print(func)
return func
f=bar(foo)
print(f)
f()
8、闭包
定义:首先必须是内部定义的函数,该函数包含对外部作用域而不是全局作用域名字的引用
#闭包 x=1000000000 def f1(): x=1 y=2 def f2(): print(x) print(y) return f2 #返回得f2不仅是返回了f2函数局部作用域还返回了引用的外部作用域的变量 f=f1() print(f) print(f.__closure__)#必须是闭包才能用此命令 print(f.__closure__[0].cell_contents)#查看值 print(f.__closure__[1].cell_contents) ------------------------------------------------------------------------------------------------------------------------------- ############################################################################################################################### <function f1.<locals>.f2 at 0x0000000000A7E1E0> (<cell at 0x0000000000686D08: int object at 0x000000005E5522D0>, <cell at 0x0000000000686D38: int object at 0x000000005E5522F0>) #代表f2引用了,f1中的x=1 #代表f2引用了,f2中的y=2
闭包用途:爬虫
#爬虫
from urllib.request import urlopen
def get(url):
return urlopen(url).read()
print(get('http://www.baidu.com'))
#专门爬百度页面
def f1(url):
def f2():
print(urlopen(url).read())
return f2 #返回的是f2的内存地址 和 url
baidu=f1('http://www.baidu.com')#等式右边就是return的值,也就是f2的内存地址 和 url<br><br>baidu()
9、递归函数
必须有一个明确的结束条件
每次进入更深一层递归时,问题规模相比上一次递归都应有所减少
递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出),栈的大小默认是1000可以修改,但是并没有意义。
import sys sys.setrecursionlimit(10000) 定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出: fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n 所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。 于是,fact(n)用递归的方式写出来就是: def fact(n): if n==1: return 1 return n*fact(n-1) #这种思想太牛逼了,感觉脑洞大开 print(fact(5)) 我们在计算fact(5),可以根据函数定义看到计算过程如下: ===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120
递归函数的优点就是定义简单,逻辑清晰。理论上,所有的函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000),最好别试。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
def fact(n): return fact_iter(n,1) def fact_iter(num,product): if num == 1: return product return fact_iter(num-1,num*product) print(fact(5)) 可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。 fact(5)对应的fact_iter(5, 1)的调用如下: ===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
总结:
使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
10、递归函数二分法理论
data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35]
def search(num,data):
print(data)
if len(data) > 1:
mid_index=int(len(data)/2)
mid_value=data[mid_index]
if num > mid_value:
data = data[mid_index:]
return search(num,data)
elif num < mid_value:
data = data[:mid_index]
return search(num,data)#这里的return是下一个search函数的return值
else:
print('find it')
return 666 #要想这个地方的return的值能有每个函数上面所有的search前面都要加return因为,这是一个递归函数,
else:
if data[0] == num:
print('find it')
return 777
else:
print('not found')
return 888
search(19,data)
print(search(19,data))
11、装饰器
定义一个Python函数,在不改变原代码的前提下,为函数增加额外功能。
语法及注意事项
1、书写规范 @ *** ***指装饰器的函数名
2、装饰器作为一个函数,他会把其下一行的主函数名作为变量,传递到自己的函数去调用。再重新赋值主函数。
3、装饰器必须放到被装饰的函数上边,并且独占一行;
4、某个函数上应用了多个装饰器,当定义的时候是按照由内到外定义,执行时是按照先外到内执行。
书写格式:
(无参装饰器)
def wrapper(func)
def inner(*args,**kwargs)
#要额外添加的方法
res = func(*args,**kwargs) #原函数,有返回值就接收没返回值为None
return res
return innner
(有参装饰器)
def foo(auth)
def wrapper(func)
def inner(*args,**kwargs)
if auth 进行判断
#要额外添加的方法
res = func(*args,**kwargs) #原函数,有返回值就接收没返回值为None
return res
return innner
return wrapper
@foo(auth=XXX) #带参数的认证
@wrapper
def index()
print("测试")
index()
装饰器执行流程:
1、首先明确,Python代码是自上而下执行的,对于函数仅是先定义不会执行函数内部的方法。
2、先定义wrapper 函数,然后走到@warpper【这是个语法糖,意思是把被装饰的函数名 index 以参数的形式传入wrapper】-->等价于warpper(index)
3、顺序执行回到wrapper函数,函数内部继续向下执行,定义了其内部函数 inner,执行原函数 index()并return,然后把 inner 函数定义 返回。其实就是将原来的 index 函数塞进另外一个函数中去执行。
内部执行:
def inner:
#新添加的方法
return index() # func是参数,此时 func 等价于 index
return inner # 返回的 inner,inner代表的是函数,非执行函数
4、将执行完的 wrapper 函数返回值 赋值 给@wrapper下面调用执行函数【index()】的函数名 。
此时,wrapper函数的返回值是:
def inner:
#新添加的方法
return 原来index()执行结果 # 此处的 index 表示原来的index函数
5、然后,将此返回值再重新赋值给 index,即:
新index = def inner:
#新添加的方法
return 原来index()
6、函数调用执行了新的 index(),不仅执行了原来的函数内容,并且也执行了新添加的方法。
多个装饰器存在,执行顺序情况
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
f(1)
#输出结果:
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
12、迭代器
可迭代对象:判断对象本身是否内置了__iter__方法,那就是可迭代对象。元组,字符串,字典,集合,列表都是 可迭代对象。文件 既是可迭代对象也是迭代器。
可迭代对象,执行对象下的__iter__(内置item())方法,就会得到一个返回值,得到的返回值就是迭代器。使用next()就能依次取出迭代器中的值(在迭代器元素个数之内,超出会报错,也是一次性的元素,不可重复取值),这样就不用再依赖下标的方式。
迭代器:迭代器提供了一个统一的访问的接口,只要是定义了iter()方法的对象,就可以使用迭代器进行访问。只要可以进行访问,就能被next()方法调用并不断返回下一个值的对象称为迭代器。换句话说,迭代器对象具有next()方法。
判断类型:isinstance()
from collections import Iterable,Iterator #导入模块,可迭代对象,迭代器
迭代器特点:
1、取值从第一个元素开始,直至所有元素全部遍历完毕。迭代器不能回退,只能往前进行迭代。
2、迭代器的长度未知,取值不灵活,只能一直next()去取值。
3、不依赖索引取值,这就导致迭代器是惰性计算,当next()取值迭代到某元素的时候才会计算该元素。
4、迭代器的内容是一次性的,取完就完了!
关于异常:
因为取值长度位置,所以一直next()取值的话,就有可能会溢出抛出异常。常用try except捕获异常
l=['a','b','c','d','e'] i=l.__iter__() while True: try: #监听代码是否会报异常 StopIteration print(next(i)) except StopIteration: #判断异常是否为 StopIteration,是break break
关于for循环:
for 循环本质就是内部封装了迭代器,对 可迭代的对象 进行遍历取值,同时会在遇到异常捕获的时候会自行处理,所以for 循环作用在迭代器上不报错。
可以使用for循环进行迭代的对象都是可迭代(Iterable)类型!可以调用next()方法的对象都是迭代器(Iterator)类型!
13、生成器
一言以蔽之:带有 yield 的函数被称为生成器
加入了yield函数的循环,所有的执行过程都会在yield函数这里停顿进行判定或是开始,当执行一周再次走到yield函数这里时,会再次停顿进行判定或是开始。
注意点:生成器是可迭代对象,也是迭代器对象。生成器的本质,就是将函数做成了一个迭代器,取名为生成器。所以生成器可以next()取值,也可以使用for 循环遍历取值
yield 函数到底干了什么事情:
1)yield 把函数变成生成器 ---> 迭代器;
2)用return 返回只能返回一次,而yield返回多次;
3)函数在暂停以及继续下一次运行时的状态,是由yield保存。
14、协程函数:
如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
生成器代码执行过程中,碰到yield 程序就会暂停,既然yield 以表达式的形式出现在函数中,就表明将 yield 暂停时从外部所携带来的值 传给等号左边的变量。
整个代码再通过next()函数触发,从yield处开始往下走,代码循环一圈之后,又回到yield这儿停止,以此循环。整个代码通过外部的send()函数给yield传值。
注意点:
1、协程函数第一次需要next()先激活;
2、协程函数需要send()传值。
带装饰器的协程函数,触发交给装饰器
def init(func):
def warpper(*args,**kwargs):
g=func(*args,**kwargs)
next(g)
return g
return warpper
@init #eater=init(eater) #eater=warpper
def eater():
print('start to eat')
while True:
food=yield
print('is eating food:%s' % food)
e=eater()#e=warpper()
e.send('北京烤鸭')
输出结果:
start to eat
is eating food:北京烤鸭
15、匿名函数:
定义:是指一类无需定义标识符(函数名)的函数或子程序。
语法格式:lambda 参数(可多个):表达式
应用:
x = int(input('please input a number:'))
#1、用户输入一个数字,生成一个1至此数的列表
la1 = lambda x : [i for i in range(1,x+1)]
print(la1(x))
#2、在列表的基础上,每一个数字都+1,返回一个新列表
la2 = lambda x : [i+1 for i in range(1,x+1)]
print(la2(x))
#3、在这个列表的基础上,所有的奇数都x2,返回一个新列表,只有奇数
la3 = lambda x : [i*2 for i in range(1,x+1,2)]
print(la3(x))
#4、在这个列表的基础上,变成["1+1=2","2+2=4"]返回一个新的字符串列表
la4 = lambda x :["%s+%s=%s"%(i,i,2*i) for i in range(1,x+1)]
print(la4(x))
#5、在这个列表的基础上,所有奇数乘以该奇数的下一个偶数
la5 = lambda x :["%sx%s=%s"%(i,i+1,i*(i+1))for i in range(1,x+1,2)]
print(la5(x))
16、内置函数:
abs(参数) 取绝对值 参数必须是整型
不是可迭代对象 '',None,0
all(可迭代对象) 判断是否是可迭代对象,返回布尔值 与运算,只要有一个不是就返回False 特殊的可迭代对象为空返回True
any(可迭代对象) 判断是否是可迭代对象,返回布尔值 或运算,只要有一个是就返回True 特殊的可迭代对象为空返回False
进制转换:参数为10进制数
bin(参数) #转为二进制
hex(参数) #转为十六进制
oct(参数) #转为八进制
ASCII码转换:
chr() #将数字转换成字符
ord() #将字符转换成数字
数据类型:强制转换为对应类型
int() #整型 str() #字符串 list() #列表 tuple() #元组 dict() #字典 set() #集合 frozenset() #不可变集合 bool() #布尔判断 返回True or False
bytes(字符串,编码格式encoding="") #字符串转成字节
type(参数) #查看参数的数据类型
isinstance() 函数来判断一个对象是否是一个已知的类型,返回布尔值 语法:isinstance(对象,类型)
callable(参数[一般为函数名]) #判断是否是函数,返回布尔值
dir(对象) 查看对象都能调用什么方法;
help()直接查看 函数 具体的帮助信息
id(变量名) #查看唯一标识身份信息
divmod(int1,int2) #除法计算 返回结果为元组(商,余数)
enumerate(可迭代对象) #枚举
hash(参数) #哈希算法
eval(字符串) #将字符串转换为表达式进行计算
reversed() 取反,本末倒置
zip(参数1,参数2) #拉链函数 参数是可迭代对象或是迭代器
一一对应取值,重组成一个个元组。不管哪方个数多,最终结果都只会输出对应的元组,多余的不对应的舍弃
sorted() #排序 返回值为列表 默认为升序!
语法格式:sorted(要排序参数,key=匿名函数lambda) 匿名函数定义排序判断的内容
max() or min() 最大值 or 最小值
max(*args,key=func) key可有可无。当需要用函数判断的时候,添加key. min与max的使用方法一致。
map() #映射 语法格式:map(func,list) #对列表内的每个元素按照func的方法处理,最终结果是返回一个迭代器,list()取值得到处理之后的列表
reduce() #合并规则 语法格式:reduce(func,list,初始值)
reduce()传入的函数func 必须接收两个参数,reduce() 对list的每个元素反复调用函数func,并返回最终处理完毕的结果
filter() #过滤器 语法格式:filter(函数func,可迭代对象list)
函数的作用是对迭代对象的每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉为 False 的元素,返回由符合条件元素组成的新list。
pow() 两个值求方,三个值是前两个求方然后值和第三个数再取余数
round() 带小数点的值,四舍六入五留双。。。
slice() 定义切片对象
vars() #局部变量
_import_('字符串') 将导入的字符串转成模块
issubclass(Me, object) # Me类是否为object类的子类
#部分代码举例,其他请自己测试
s = "hello"
l = [1,2,3,4,5,6]
print(list(zip(l,s)))
print(list(map(lambda x:x**2,l)))
from functools import reduce
print(reduce(lambda x,y:x+y,l,10))
salaries={
'egon':3000,
'alex':100000000,
'wupeiqi':10000,
'yuanhao':2500
}
#min同理
print(max(salaries))
print(max(salaries,key=lambda x:salaries[x]))
name_l =[
{"name":'egon','age':120},
{"name":"fsw","age":1000},
{'name':'wang','age':2000},
{'name':'jie','age':18}
]
f = filter(lambda d:d['age']>100,name_l)
print(list(f))
17、列表生成式 和 生成器表达式
列表生成式:
通俗的来讲,列表生成式由三部分拼接组成:当然每次写之前都应该先给出[],然后在里边添加。
1.expression 指要生成的元素(参数,变量),放在最前面
2.后面跟上for循环
3.for循环之后还可以加上if条件判断,以便进行筛选。
实际使用的过程中,若一个for循环不能完成问题,还可以往下嵌套。
例如:[i for i in range(3)]
生成器表达式:
生成器表达式,我个人认为还不如叫列表生成器,就是把列表表达式改变了一下,变成了一个生成器。而且这种改变非常简单,就是把外[]换成了()就创建了一个generator。
通过列表生成式,我们可以直接创建一个列表。但受到内存的限制,列表容量肯定是有限的,同时那么庞大的数据流,一下子拿出来什么机器得卡的受不了。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。generator生成器保存的是算法,每次通过next()触发取值,并且每次只取一个元素的值,直到计算到最后一个元素。没有更多的元素时,就会抛出StopIteration的错误。我们可以通过for循环来迭代它,并且不需要关心StopIteration的错误。
浙公网安备 33010602011771号