python函数
本章内容
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'] def chang_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 name is Mr.python,and I am 19 years old 注:age我没有指定,则使用默认的值,19了,这个age就是设定了默认参数
位置参数
def hello(name='NB',age=20): #参数的默认值,调用时不声明,则使用默认参数 print 'my name is %s,%s years old'%(name,age) print hello() print hello('mypython',10) #这里面name和age对应的位置是不可以变动的。
关键字参数
def hello(name,age): print 'my name is %s,%s years old'%(name,age) print hello(age=1000,name='mypython') #这里可以通过关键字参数来随意的变更位置。
位置摆放:
默认参数,必须放在位置参数的后面
关键字参数,同上
小练习:
输入一个数,计算阶乘
def jiecheng(number): for i in range(1,(number+1)): sum = i * sum return sum number = raw_input('please input a number')
请注意,函数体内部的语句在执行时,一旦执行到
内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有
return
时,函数就执行完毕,并将结果返回。因此,函数return
语句,函数执行完毕后也会返回结果,只是结果为None
。return 只返回一个值,如果有多个结果的话那会已(a,b.......)元组的形式返回
函数的递归
尾递归是指,在函数返回的时候,调用自身本身
利用递归的方式来计算阶乘。 就是函数自己调用自己。
def jiecheng(number): if number ==1: return 1 return number*jiecheng(number-1) #其原理就是自己乘以自己上一个数的阶乘。 print jiecheng(4)
小点:当明确的结束条件时,循环到999次时就会报错,不然一直跑下去会吃死内存。
递归特性:
1.必须有个明确的结束条件:
2.每次进入更深一层递归时,问题规模相比上次递归都应该减少
3.递归的效率不高
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
这里有个递归的小练习,二分查找法,详见python小练习:
http://www.cnblogs.com/nopnog/p/6927870.html
空函数
如果想定义一个什么事也不做的空函数,可以用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、嵌套调用:一个调用另一个
小丽:
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) print para print par hello('Mr.python',1,2,3,4,5,6,7,8,haha='haha',nihao='nihao') 结果: my name is Mr.python (1, 2, 3, 4, 5, 6, 7, 8) {'nihao': 'nihao', 'haha': 'haha'} [Finished in 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.函数可以当作参数来使用:
def operate(fn,name): return fn(name) def sayhello(name): return 'hello %s!'%(name) def hehe(name): return 'hehe %s !'%(name) print operate(sayhello,'MR.python') print operate(hehe,'mr.python') ----------------------------------- hello MR.python! hehe mr.python ! [Finished in 0.1s]
2.结合sorted来使用:
arr2 = [('192.168.1.1',2),('192.168.1.2',1),('192.168.1.3',3)] def sort_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}] def sort_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=lambda x: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] return arr # def qu_key(arr): # return arr[1] # def qu_key1(arr): # return arr['age'] a = [6,5,4,3,3,2,1] print my_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.不要更改调用的方式。
这不能动那不能动,想添加功能咋办?用装饰器喽。
原始装饰器:
标准格式:
def zhuangsq(fn): def new_hello(name): print 'good morning ' + name return 'haha ' + fn(name) return new_hello #装饰起代码必须在上面,用@zhuangsq替代了上面的 hello = zhuangsq(hello) @zhuangsq def hello(name): return 'nice to meet you ' + name print 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 def auth(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 return res 12 else: 13 print('\033[31;1m User does not exit! \033[0m') 14 15 return wrapper 16 17 18 @auth 19 def login(): 20 print('This is login,I will login') 21 return 'I love this web!!' 22 23 @auth 24 def bbs(): 25 print('This is bbs ') 26 27 def index(): 28 print('this is index') 29 30 print(login()) #函数return有返回值的话需要print,把结果打印出来 31 bbs() 32 index() 33 34 35 36 运行结果: 37 username:alex 38 password:12345 39 User has passed authentication 40 This is login,I will login 41 I love this web!! 42 username:alex 43 password:12345 44 User has passed authentication 45 This is bbs 46 this is index
升级一下,如果使用装饰器,一部分页面需要本地验证,另一方面需要另外一套系统来做验证,那又能怎么办呢? --->对装饰器再添加一层!
1 def auth(auth_type): 2 print('auth func is ',auth_type) 3 def outer_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 return res 12 else: 13 print('\033[31;1m User does not exit! \033[0m') 14 15 return wrapper 16 return outer_wrapper 17 18 @auth(auth_type='local') 19 def login(): 20 print('This is login,I will login') 21 return 'I love this web!!' 22 23 @auth(auth_type='ldap') 24 def bbs(): 25 print('This is bbs ') 26 27 def index(): 28 print('this is index') 29 30 print(login()) 31 bbs() 32 index() 33 34 35 36 运行结果: 37 38 auth func is local 39 auth func is ldap 40 username:alex 41 password:12345 42 User has passed authentication 43 This is login,I will login 44 I love this web!! 45 username:alex 46 password:12345 47 User has passed authentication 48 This is bbs 49 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 <generator object <genexpr> 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 "<stdin>", line 1, in <module> StopIteration
next(a)python2中的方法
a.__next__()python3中的方法
我们讲过,generator保存的是算法,每次调用next()
,就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next()
方法实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10)) >>> for n in g: ... print n ... 0 1 4 9 16 25 36 49 64 81
所以,我们创建了一个generator后,基本上永远不会调用next()
方法,而是通过for
循环来迭代它。这里我们看到,这个迭代器只支持到三元运算,其实他本事不止这些,复杂的形式用函数的方式来实现:
斐波那契数
def fib(max): n,a,b=0,0,1 while n < max: print (b) a,b= b,a+b n +=1 return 'done' fib(10)
这里把print (b) 改为yeild就变为生成器了,可以利用for循环去调用,每想调用一个就调用一个,如果想执行其他的,期间也可以执行,这就是他的作用,现用现执行,不浪费内存,这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行
现改之为生成器样式
def fib(num): n,a,b = 0,0,1 while n < num: yield b a,b = b,a+b n+=1 return 'done' d = fib(10) while True: try: print(d.__next__()) except StopIteration as e: print(e) print('Generator return value',e.value) break
这里为什么要用try去捕获异常呢,因为使用next方法,执行到最后会抛出一个 StopIteration 的异常
利用yield来模拟单线程并发:
1 import time 2 3 def consumer(name): 4 print("%s 准备来吃包子了"%name) 5 6 while True: 7 baozi = yield 8 9 print('包子[%s]来了,被[%s]吃了'%(baozi,name)) 10 11 def producer(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]吃了
迭代器
我们已经知道,可以直接作用于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 import Iterator >>> 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()
函数实现的,例如:
1
2
|
for x in [ 1 , 2 , 3 , 4 , 5 ]: pass |
实际上完全等价于:
# 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break
python 内置函数