本章内容
1、函数
2、形参、实参
3、全局与局部变量
4、默认、位置、关键字参数
5、函数的递归
6、函数的嵌套
7、闭包函数
8、可变参数
9、函数的用法
10、匿名函数lambda
11、闭包函数
12、装饰器
13、高阶函数
14、列表生成式
15、生成器
16、迭代器
17、python内置函数
函数
使用函数的三大优点:
1、代码重用
2、保持一致性
3、可扩展性
return返回值:
1、当一个函数没有使用return显示定义的返回值时,python解释器会隐式的返回None
2、return一个值时,返回的是个object
3、ruturn多个值时,返回的是个tuple,里面的值可以是int、tuple、list、dict
函数是第一类对象的概念
1、函数可以被引用
f1 = func
f1() #f1可以被执行
2、函数可以为参数,
3、函数的返回值可以是参数
4、函数可以作为容器类型元素,
cmd = {‘n’:func1,'b':func2}
形参、实参
def hello(a,b): #这里的a,b属于形参
print 'hello,a,b'
a= 'python
b = 'jim'
hello(a,b) #这里的的a,b属于实参
形参:只有在被调用的时候才会分配内存,调用结束后,立即释放内存,值仅在函数内部有效(局部变量)
实参:有确定的值的参数,所有的数据类型都可以被当做参数传递给函数。可以向函数内传递任意的数据类型。
python解释器有自己的内存回收机制,a='hello' ,在内存中开辟了一个内存空间,a 变量去引用这个内存的空间,相当于a是这快地址的门牌号,如果解释器看到这个内存空间没有门牌号的话,这个内存空间则会被释放。
全局与局部变量
在子程序中定义的变量成为局部变量,在程序的一开始定义的变量成为全局变量。全局变量的作用于整个程序,局部变量的作用于是定义该变量的子程序,当全局变量与局部变量同名时,在定义局部变量的子程序内,局部变量起作用,在其他地方全局变量起作用。
局部变量:作用域只在当前的函数内部,外部变量默认不能被函数内部修改的,只能引用,如果想在函数内部修改全区变量,必须golbal,但是强烈建议不要这么用,调试不方便。
上面说的不能修改的的变量类型是int str ,但是函数内部是可以修改列表,字典,集合,实例(class)
下列说明,变量为列表类型时不引用golbal就可以被修改。
names = ['jim','rose','jack']defchang_name():
names[0]= '吉姆'
print(names)
chang_name()print(names)
运行结果:
['吉姆', 'rose', 'jack']
['吉姆', 'rose', 'jack']
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:>>> a = abs # 变量a指向abs函数>>> a(-1)# 所以也可以通过a调用abs函数
默认、位置、关键字参数
默认参数
def hello(name='world',age=19):print 'my name is %s,and I am %s years old'%(name,age)
hello(name='Mr.python')------------------------------my nameis Mr.python,and I am 19years old
注:age我没有指定,则使用默认的值,19了,这个age就是设定了默认参数
位置参数
def hello(name='NB',age=20): #参数的默认值,调用时不声明,则使用默认参数
print 'my name is %s,%s years old'%(name,age)printhello()print hello('mypython',10) #这里面name和age对应的位置是不可以变动的。
关键字参数
defhello(name,age):print 'my name is %s,%s years old'%(name,age)print hello(age=1000,name='mypython') #这里可以通过关键字参数来随意的变更位置。
位置摆放:
默认参数,必须放在位置参数的后面
关键字参数,同上小练习:
输入一个数,计算阶乘
defjiecheng(number):for i in range(1,(number+1)):
sum= i *sumreturnsum
number= raw_input('please input a number')
请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return 只返回一个值,如果有多个结果的话那会已(a,b.......)元组的形式返回
函数的递归
尾递归是指,在函数返回的时候,调用自身本身
利用递归的方式来计算阶乘。 就是函数自己调用自己。
defjiecheng(number):if number ==1:return 1
return number*jiecheng(number-1) #其原理就是自己乘以自己上一个数的阶乘。
print jiecheng(4)
小点:当明确的结束条件时,循环到999次时就会报错,不然一直跑下去会吃死内存。
递归特性:
1.必须有个明确的结束条件:
2.每次进入更深一层递归时,问题规模相比上次递归都应该减少
3.递归的效率不高
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
pass还可以用在其他语句里,比如:
if age >= 18:
pass
缺少了pass,代码运行就会有语法错误。
函数的嵌套
分为两种:
1、嵌套定义:
小丽:
x = 1
def func1():
def func2():
def func3():
print (x)
return func3
return func2
func1()
嵌套的调用最多不要超过三层,不然的话可读性就会变差。
2、嵌套调用:一个调用另一个
小丽:
def my_max2(x,y): #这是比较两个数的大小,现在相比较四个数,咋办
if x > y:
return x
else:
return y
def my_max4(a,b,c,d): #划分为最细的情况其实就是两个数的比较
res1 = my_max2(a,b)
res2 = my_max2(res1,c)
res3 = my_max2(res2,d)
return res3
print(my_max4(100,2,-1,5))
闭包函数
下面由嵌套函数引出闭包函数
def func1():
x=1
def func2():
print (x)
return func2
f=func1()
f()
这就是一个闭包函数,没有看出什么可用之处吧,下面做个总结:
1.首先是func2中x变量的查找,只会在它的上一层查找,在函数外如果有x的赋值,也不会影响func2中的x这就相当于x与func2打包在一起了。
2.惰性运算,f=func1()后把func2()给了f,但是f没有运行,只有f+()后才会运行
根据这两个特性没有看到闭包函数的nb之处是吧,继续看小丽。
利用一会学到的爬虫来说明
from urllib.request import urlopen
def page(url):
def get():
return urlopen(url).read
return get
baidu = page('http://baidu.com/')
baidu()
只要一baidu()就会去爬百度的网页了,走到这还是没看出啥来是吧,那再来个python=page('http://python.org') 直接python就会去爬python的网站了,这就相当于个接口,你把python传给别人,别人直接调用就可以,不用关系再去定义python的网址,因为python的网址已经隐藏到函数中了。
话说我随用随掉不可以吗?
def get(url):
return urlopen(url).read
get('http://www.baidu.com')
其实直接get('http://www.baidu.com') 与baidu() 没啥区别,区别在于闭包函数已经把变量(url=www.baidu.com)存在函数中了,什么时候用什么时候加()调用就可以了。
可变参数
*收集的是位置参数,不能收集关键参数,收集到的是元组---此方法名为可变参数。
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
** 收集的是关键字参数,收集到的是字典 ---又名关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def hello(name = 'world',*para,**par):print 'my name is %s'%(name)printparaprintpar
hello('Mr.python',1,2,3,4,5,6,7,8,haha='haha',nihao='nihao')
结果:
my nameisMr.python
(1, 2, 3, 4, 5, 6, 7, 8)
{'nihao': 'nihao', 'haha': 'haha'}
[Finishedin 0.1s]
小用法:向函数中的*args传递列表。
def foo(x,y,*args):
print (x)
print(y)
print(args)
l = ['a','b']
foo(1,2,*l) #*l的用法,把列表分成单个的项然后传递给函数,相当于*['a','b'] = *args, 然后把*去掉,args = ['a','b'] 然后传递到函数中,函数再已('a','b')的形式输出
函数的用法
1.函数可以当作参数来使用:
defoperate(fn,name):returnfn(name)defsayhello(name):return 'hello %s!'%(name)defhehe(name):return 'hehe %s !'%(name)print operate(sayhello,'MR.python')print operate(hehe,'mr.python')-----------------------------------hello MR.python!
hehe mr.python !
[Finishedin 0.1s]
2.结合sorted来使用:
arr2 = [('192.168.1.1',2),('192.168.1.2',1),('192.168.1.3',3)]defsort_fn(arg):return arg[1] #取的是列表内元素,各个元组的下表是1的关键字
print sorted(arr2,key=sort_fn)
arr3= [{'name':'python','age':10},{'name':'C++','age':8},{'name':'java','age':9}]defsort_dict(arg):return arg['age'] #取的是列表内元素,各个字典的下表age的关键字的值
print sorted(arr3,key=sort_dict)---------------------------------------------------------------------------[('192.168.1.2', 1), ('192.168.1.1', 2), ('192.168.1.3', 3)]
[{'age': 8, 'name': 'C++'}, {'age': 9, 'name': 'java'}, {'age': 10, 'name': 'python'}]
注释的地方容易理解吗?感觉只能靠记
下面通过写个简单的sorted,来做个练习。
实现了list的排序,但是列表和字典的怎么实现呢?
这里面设计到函数的引用,请查看过程。
匿名函数lambda
下面引入lambda,lambda 只能实现简单的函数
def qu_key1(arr):
return arr['age']
翻译为lambda 就是 lambda arr:arr['age']
下面用lambda来替代。
下面在利用lambda实现list也可以比较的功能。
利用函数的默认值,key=lambda x:x 来实现对list的比较,实际上就是没有是通过lambda什么都没有操作的。
贴上代码:
def my_sort(arr,key=lambdax:x):
length=len(arr)for i in range(length-1):for j in range(length-1-i):if key(arr[j]) > key(arr[j+1]):
arr[j],arr[j+1] = arr[j+1],arr[j]returnarr#def qu_key(arr):#return arr[1]
#def qu_key1(arr):#return arr['age']
a= [6,5,4,3,3,2,1]printmy_sort(a)
arr2= [('192.168.1.1',2),('192.168.1.2',1),('192.168.1.3',3)]print my_sort(arr2,key=lambda x:x[1])
arr3= [{'name':'python','age':10},{'name':'C++','age':8},{'name':'java','age':9}]print sorted(arr3,key=lambda x:x['age'])--------------------------------------------------------------------------[1, 2, 3, 3, 4, 5, 6]
[('192.168.1.2', 1), ('192.168.1.1', 2), ('192.168.1.3', 3)]
[{'age': 8, 'name': 'C++'}, {'age': 9, 'name': 'java'}, {'age': 10, 'name': 'python'}]
看到上面的代码是不是有疑惑,那lambda是什么呢?先来个简单的例子,首先得明确一点lambda其实就是个函数:
g= lambda x:x+1
print g(1)>>2当然,你也可以这样使用:lambda x:x+1(1)>>>2
可以这样认为,lambda作为一个表达式,定义了一个匿名函数,上例的代码x为入口参数,x+1为函数体,用函数来表示为:
1 def g(x):
2 return x+1
lambda 也可以多参数:
lambda x,y:x+y
非常容易理解,在这里lambda简化了函数定义的书写形式。是代码更为简洁,但是使用函数的定义方式更为直观,易理解。
lambda if 判断运算:
for i in map(lambda x:x*2 if x>5 else x-1 [1,2,3,4,5,6,7,8,9]):
注:lambda支持到最复杂的运算是三元运算,更复杂的运算不支持。
装饰器
装饰器:本质上是个函数,(装饰其他的函数)就是为了其他函数添加附加的功能。
1.最好不要修改源代码,有课能用的地方很多,错一点,会有连带反应。
2.不要更改调用的方式。
这不能动那不能动,想添加功能咋办?用装饰器喽。
原始装饰器:
标准格式:
defzhuangsq(fn):defnew_hello(name):print 'good morning' +namereturn 'haha' +fn(name)returnnew_hello#装饰起代码必须在上面,用@zhuangsq替代了上面的 hello = zhuangsq(hello)
@zhuangsqdefhello(name):return 'nice to meet you' +nameprint hello('MR.python')
哈哈,到了这里有一个参数或者不传递参数的装饰器都已经搞定了,但是如果有多个参数呢,再如果加上加上装饰器后再调用hello() 其实就是调用的new_hello()这个函数了,位置参数呢,哪有如何接受呢?别怕在new_hello()中用*args,**kwargs接受,但是接受了是个元组和字典啊,如何才能让内部的fn函数接受呢?哈哈,还是用*args,**kwargs,这里就会他元组和字典里的这些想
全部独立的分开。
效果如下:
new_hello(*arge,**kwargs):
print 'good morning' + name
return 'haha' + fn(*args,**kwargs)
如果在hello()函数中有return呢?现在我们知道装饰后其实就是运行的new_hello,这时候咋办new_hello中也需要return,那return什么?return fn这个函数。其实也就是hello()
装饰器之return 小例:
1 user,passwd = 'alex','12345'
2
3 defauth(func):4 def wrapper(*args,**kwargs):5 username = input('username:').strip()6 password = input('password:').strip()7
8 if user == username and passwd ==password:9 print('\033[32;1m User has passed authentication \033[0m')10 res = func(*args,**kwargs)11 returnres12 else:13 print('\033[31;1m User does not exit! \033[0m')14
15 returnwrapper16
17
18 @auth19 deflogin():20 print('This is login,I will login')21 return 'I love this web!!'
22
23 @auth24 defbbs():25 print('This is bbs')26
27 defindex():28 print('this is index')29
30 print(login()) #函数return有返回值的话需要print,把结果打印出来
31 bbs()32 index()33
34
35
36 运行结果:37 username:alex38 password:12345
39 User has passed authentication40 This islogin,I will login41 I love this web!!42 username:alex43 password:12345
44 User has passed authentication45 This isbbs46 this is index
return 使用
升级一下,如果使用装饰器,一部分页面需要本地验证,另一方面需要另外一套系统来做验证,那又能怎么办呢? --->对装饰器再添加一层!
1 defauth(auth_type):2 print('auth func is',auth_type)3 defouter_wrapper(func):4 def wrapper(*args, **kwargs):5 username = input('username:').strip()6 password = input('password:').strip()7
8 if user == username and passwd ==password:9 print('\033[32;1m User has passed authentication \033[0m')10 res = func(*args, **kwargs)11 returnres12 else:13 print('\033[31;1m User does not exit! \033[0m')14
15 returnwrapper16 returnouter_wrapper17
18 @auth(auth_type='local')19 deflogin():20 print('This is login,I will login')21 return 'I love this web!!'
22
23 @auth(auth_type='ldap')24 defbbs():25 print('This is bbs')26
27 defindex():28 print('this is index')29
30 print(login())31 bbs()32 index()33
34
35
36 运行结果:37
38 auth func islocal39 auth func isldap40 username:alex41 password:12345
42 User has passed authentication43 This islogin,I will login44 I love this web!!45 username:alex46 password:12345
47 User has passed authentication48 This isbbs49 this is index
装饰器中添加参数
高阶函数
注:其是调用ads函数的内存地址。
满足一下两个条件即可称之为高阶函数:
1、把函数的内存地址当做参数传给另外一个函数
2、一个函数把另外一个函数当做返回值返回。
高阶函数 + 嵌套函数 = 装饰器
列表生成式
[ i*2 for i in range(10) ]
转换为普通的代码:
a = []
for i in range(10):
a.append(i*2)
列表生成式的作用,就是使代码更简洁
先来个小练习a= [1,2,3,4,5,6,7,8,9] 里面的每个数值都加1
1.可以利用lambda:
map(lambda x:x+1,a)
2.第二种方法:根据每个数的索引来对每个数+1运算
注意:这时候不能用列表的index来去索引,因为如果有重复的,它只会取第一个,需要用enumerate
a= [1,2,3,4,5,6,7,8,9]
for index,i in enumerate(a):
a[index] = i+1
print(a)
3.列表生成式
a= [1,2,3,4,5,6,7,8,9]
b = [i+1 for i in a]
列表生成式最大的运算也是三目运算
a= [1,2,3,4,5,6,7,8,9]
b= [i*i if i >5 else i for i in a]
生成器
通过列表生成式,我们可以直接创建一个列表,但是,受到内存的限制,列表容量肯定是有限的,而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面的几个元素,那后面绝大数元素占用的空间就拜拜浪费了。
所以,如果列表元素可以按照某种算法来推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,
在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个
>>> L = [x * x for x in range(10)]>>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> g = (x * x for x in range(10))>>> g at 0x104feab40>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过generator的next()方法:
>>>g.next() 0>>> g.next() 1
>>> g.next() 4
>>> g.next() 9
>>> g.next() 16
>>> g.next() 25
>>> g.next() 36
>>> g.next() 49
>>> g.next() 64
>>> g.next() 81
>>>g.next()
Traceback (most recent call last): File"", line 1, in StopIteration
next(a)python2中的方法
a.__next__()python3中的方法
我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10))>>> for n ing:
...printn ...
01 4 9 16 25 36 49 64 81
所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。这里我们看到,这个迭代器只支持到三元运算,其实他本事不止这些,复杂的形式用函数的方式来实现:
斐波那契数
deffib(max):
n,a,b=0,0,1
while n
a,b= b,a+b
n+=1
return 'done'fib(10)
这里把print (b) 改为yeild就变为生成器了,可以利用for循环去调用,每想调用一个就调用一个,如果想执行其他的,期间也可以执行,这就是他的作用,现用现执行,不浪费内存,这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
现改之为生成器样式
deffib(num):
n,a,b= 0,0,1
while n
a,b= b,a+b
n+=1
return 'done'd= fib(10)whileTrue:try:print(d.__next__())exceptStopIteration as e:print(e)print('Generator return value',e.value)break
这里为什么要用try去捕获异常呢,因为使用next方法,执行到最后会抛出一个 StopIteration 的异常
利用yield来模拟单线程并发:
1 importtime2
3 defconsumer(name):4 print("%s 准备来吃包子了"%name)5
6 whileTrue:7 baozi = yield
8
9 print('包子[%s]来了,被[%s]吃了'%(baozi,name))10
11 defproducer(name):12 c = consumer('A')13 c2 = consumer('B')14 c.__next__()15 c2.__next__()16 print('lz开吃吃包子了')17 for i in range(10):18 time.sleep(1)19 print('做一个包子,分成两半')20 c.send(i) #传递给yield一个值,
21 c2.send(i)22
23
24 producer('Alex')25
26 执行结果:27 会每次两个客户同事输出,效果像并发28
29 A 准备来吃包子了30 B 准备来吃包子了31 lz开吃吃包子了32 做一个包子,分成两半33 包子[0]来了,被[A]吃了34 包子[0]来了,被[B]吃了35 做一个包子,分成两半36 包子[1]来了,被[A]吃了37 包子[1]来了,被[B]吃了38 做一个包子,分成两半39 包子[2]来了,被[A]吃了40 包子[2]来了,被[B]吃了41 做一个包子,分成两半42 包子[3]来了,被[A]吃了43 包子[3]来了,被[B]吃了44 做一个包子,分成两半45 包子[4]来了,被[A]吃了46 包子[4]来了,被[B]吃了47 做一个包子,分成两半48 包子[5]来了,被[A]吃了49 包子[5]来了,被[B]吃了50 做一个包子,分成两半51 包子[6]来了,被[A]吃了52 包子[6]来了,被[B]吃了53 做一个包子,分成两半54 包子[7]来了,被[A]吃了55 包子[7]来了,被[B]吃了56 做一个包子,分成两半57 包子[8]来了,被[A]吃了58 包子[8]来了,被[B]吃了59 做一个包子,分成两半60 包子[9]来了,被[A]吃了61 包子[9]来了,被[B]吃了
View Code
迭代器
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
>>>isinstance([], Iterable)
True>>>isinstance({}, Iterable)
True>>> isinstance('abc', Iterable)
True>>> isinstance((x for x in range(10)), Iterable)
True>>> isinstance(100, Iterable)
False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
>>> from collections importIterator>>> isinstance((x for x in range(10)), Iterator)
True>>>isinstance([], Iterator)
False>>>isinstance({}, Iterator)
False>>> isinstance('abc', Iterator)
False
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>>isinstance(iter([]), Iterator)
True>>> isinstance(iter('abc'), Iterator)
True
你可能会问,为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
在python3中 range 就是使用的迭代器封装的,而python2中不是,xrange是它的迭代方法,
小结
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
Python的for循环本质上就是通过不断调用next()函数实现的,例如:
实际上完全等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
python 内置函数