马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
各位喜欢学python的还可以看看我的日志(日志的内容会比较全),我们互相讨论学习,资源共享,期待能和大家一起进步
2019-1-19 00:09|个人分类:python日常学习笔记-函数
函数:输入:参数:来自于我们要用到的函数那里 输出:返回值,来自于调用函数那里 目录 什么“大事”呢?下面将要介绍Python编程的核心内容之一——函数。 对于Python编程,函数的重要性不言而喻。重要的事情讲三遍:函数实在是太重要,太关键了。 一、引入函数 之前,我们编写程序遵循的原则:根据业务逻辑从上到下实现功能,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,也就是将之前实现的代码块复制到现需功能处。这种编程方式虽然可以应付一般性问题,但是不能对付大多数问题。这不,下面就来个例子。 1 r1 = 12.3 2 r2 = 9.1 3 r3 = 64.21 4 s1 = 2 * 3.14 *r15 s2 = 2 * 3.14 * r26s3 = 2 * 3.14 * r3 圆是个神奇的图形。特别是π,它让人类陷入无限的遐想。OK,回归正题。为了求圆的周长,我们需要引入公式:周长 = 2 * π * r(半径)。看到这儿,某些读者可能会有疑惑:这跟函数有什么关系,之前的方式依然适用。是的,这的确是可以的,但这很麻烦,太重复啦。那如果现在需要把 π 更改为3.1415926535,那该怎么办呢?难道我们要一个一个地去改???Oh,my god!!!这时,我嗅到了函数的味道。 有了函数,我们就不再每次写c = 2 * 3.14 * x,而是写成更有意义的函数调用c = perimeter_of_circle(x),而函数perimeter_of_circle本身只需要写一次,就可以多次调用。 Python不但能非常灵活地定义函数,而且本身内置了很多有用的函数,可以直接调用。 是的,函数最大的优点:增强代码的重用性和可读性。Python中,函数就是最基本的一种代码抽象的方式。 二、函数定义 在Python中,函数有五大要点,分别是def、函数名、函数体、参数、返回值,以及两个英文版符号,分别是括号(括号内为参数)和冒号(:)。 def:函数的关键字,没它可不行。 函数名:函数的名称,根据函数名调用函数。 函数体:函数中进行一系列的具体操作。 参数:为函数体提供数据。 返回值:当函数执行完毕后,可以给调用者返回数据。 上述函数的要点中,最重要的是参数和返回值。 1.返回值 函数是一个功能块,该功能到底执行成功与否,需要通过返回值来告知调用者。 2.参数 定义函数时,参数是一定需要考虑的。Python的函数定义非常简单,但灵活度却非常大。 对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。 Python中,参数类型有:必选参数、默认参数、可变参数、关键字参数和命名关键字参数。函数中,参数定义的顺序必须是:必选参数、默认参数、可变参数、关键字参数、命名关键字参数。 3.空函数 空函数:什么事也不做,可以用pass语句。既然“一事不做”,那空函数还有什么用处?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。如此,运行代码程序就不会出现错误了。 1 #空函数 2 def nop(): 3 pass 三、函数参数 Python中,参数是非常灵活的。掌握参数就能领悟函数的真谛了。这是真的。参数是比较难理解的,特别是参数组合。 1.位置参数 既然说函数,就需要展示函数: 1 #位置参数(必选参数) 2 def involution(x): 3 return x * x 4>>>involution(3) 5 9 6>>>involution(5) 7 25 如代码所示,参数x就是一个位置参数。 2.默认参数(就是函数调用,如function()里面为空不指定时,里面已经默认的数)函数的默认参数值,即在定义参数的时候同时给它一个初始值(所以默认参数都有等于号)。在调用函数的时候,我们可以省略含有默认值的参数。也就是说,如果用户指定了参数值,则使用用户指定的值,否则使用默认参数的值。 1 #默认参数 2 def involution(x,n = 2): 3 s = 1 4 while n> 0: 5 n = n - 1 6 s = s * x 7 return s 8>>>involution(6) 9 36 10 >>>involution(5,3) 11 125 如代码所示,当我们调用involution(5),就相当于调用involution(5,2)。 注:1.设置默认参数时,必选参数在前,默认参数在后,否则Python的解释器会报错; 2.定义默认参数(默认参数L)要牢记:默认参数(默认参数L)必须指向(可以说为=号)不可变对象(元组(tuple)、数值型(number) (int和float)、字符串(string)均为不可变对象 )! 1 >>>def add_end(L=[]): 2 ... L.append('END') 3 ... return L 4 ... 5 >>>add_end() 6 ['END'] 7 >>>add_end() 8 ['END','END'] 9 >>>add_end() 10 ['END','END','END'] 上述代码展示的是默认参数不为不可变对象的现象。因此,默认参数必须指向不可变对象【如:字符串、None……】。 如: 默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下: 先定义一个函数,传入一个list,添加一个END再返回: - def add_end(L=[]):#都有等于号
- L.append('END')
- return L
当你正常调用时,结果似乎不错: - >>> add_end([1, 2, 3])
- [1, 2, 3, 'END']
- >>> add_end(['x', 'y', 'z'])
- ['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的: 但是,再次调用add_end()时,结果就不对了: - >>> add_end()
- ['END', 'END']
- >>> add_end()
- ['END', 'END', 'END']
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。 原因解释如下: Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。 所以,定义默认参数要牢记一点:默认参数必须指向不变对象! 要修改上面的例子,我们可以用None这个不变对象来实现: - def add_end(L=None):
- if L is None:
- L = []
- L.append('END')
- return L
现在,无论调用多少次,都不会有问题: - >>> add_end()
- ['END']
- >>> add_end()
- ['END']
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。 3.可变参数 函数参数的个数是可变的,例如下面的numbers就是可变参数,在Python函数中,还可以定义可变参数。顾名思义,可变参数就是输入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。 我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 +c2 + ……。 要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来(输入:参数),这样,函数可以定义如下: 1 #一般性函数 2 def calc(numbers): 3 sum = 0 4 for n in numbers: 5 sum = sum + n * n #连加的格式 6 return sum 如何调用calc()函数呢?需要调用时,需要为参数引入list或者tuple。 1 #函数调用 2 >>>calc([1, 2, 3]) 3 14 4 >>>calc((1, 3, 5, 7)) 5 84 然而,如果我们使用可变参数,我们可以进行简化,方法如下: 1 #可变参数 2 def calc(*numbers): 3 sum = 0 4 for n in numbers: 5 sum = sum + n * n 6 return sum 咋调用呢?这个可简单啦,再也不用list或者tuple了。参数调用只需如下所示: 1 #可变参数的魅力 2 >>>calc(1, 2, 3) 3 14 4 >>>calc(1, 3, 5, 7) 5 84 6 7 #参数调用不用calc([1,2,3]),括号内还用写中括号,好麻烦~~~ 定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple(不是list),因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数: 1 >>>calc(1, 2) 2 5 3 >>> calc() 4 0 如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做: 1 >>> nums= [1, 2, 3] 2 >>>calc(nums[0], nums[1], nums[2]) 3 14 这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去: 1 >>> nums= [1, 2, 3] 2 >>>calc(*nums) #即calc()括号里面是一串数字就行 3 14 4.关键字参数 可变参数允许你传入0个或任意个参数(宾语),这些可变参数在函数调用时自动组装为一个tuple(不是list)。而关键字参数允许你传入0个或任意个含参数名的参数(宾语),这些关键字参数在函数内部自动组装为一个dict。dict就是字典,它是键值对组合,益处多多~~~ 1 #引入关键字参数,默认为**kw 2 def person(name, age, **kw): 3 print('name:', name, 'age:',age, 'other:', kw) 函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数(必选参数必须全部传入,否则会出错),也可以传入关键字参数。注:关键字参数可是任意个的。 1 #调用关键字参数 2 >>>def person(name,age,**kw): 3 ... print('name:',name,'age:',age,'other:',kw) 4 ... 5>>>person('Jack') 6 Traceback (most recent call last): 7 File "<stdin>",line 1, in <module> 8 TypeError:person() missing 1 required positional argument: 'age' 9>>>person('Jack',36) 10 name:Jack age:36 other:{} 11>>>person('Jack',36,city='Hangzhou') 12 name:Jack age:36other:{'city':'Hangzhou'} 13>>>person('Jack',36,city='Hangzhou',job='Engineer') 14 name:Jack age:36other:{'city':'Hangzhou','job':'Engineer'} 关键字参数有什么用呢?其实,既然存在就有它的强大之处。就像自然界中的万物,物竞天择,适者生存。如果它能够在自然界中生存下来,那么它就有独特的生存本领。因此,关键字参数还是有用武之地的。 它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。 如何操作呢?我们可以先组装出一个dict,然后,把该dict转换为关键字参数传进去: 1 >>>extra = {'city': 'Hangzhou','job': 'Engineer'} 2 >>>person('Jack', 36, city=extra['city'], job=extra['job']) 3 name: Jack age:36 other: {'city': 'Hangzhou','job': 'Engineer'} 当然了,上面代码调用方式有点烦,通过dict键来查找值。我们可以通过关键字简化一下: 1 >>>extra = {'city': 'Hangzhou','job': 'Engineer'} 2 >>>person('Jack', 36, **extra) 3 name: Jack age:36 other: {'city': 'Hangzhou','job': 'Engineer'} **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict。 5.命名关键字参数 对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。 仍以person()函数为例,我们希望检查是否有city和job参数: 1 def person(name, age, **kw): 2 if 'city' in kw: 3 # 有city参数 4 pass 5 if 'job' in kw: 6 # 有job参数 7 pass 8 print('name:', name, 'age:',age, 'other:', kw) 如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下: 1 def person(name, age, *,city, job): 2 print(name,age, city, job) 和关键字参数*kw不同,命名关键字参数需要一个特殊分隔符,*后面的参数被视为命名关键字参数。
调用命名关键字参数方式如下: 1 #调用命名关键字参数 2 >>>person('Jack', 36, city='Hangzhou', job='Engineer') 3 Jack 36 HangzhouEngineer 那如果参数中有可变参数,那该怎么办呢? 若可变参数后面跟着命名关键字参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。 1 def person(name, age, *args,city, job): 2 print(name,age, args, city, job) 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。而命名关键字参数可以有缺省值,从而简化调用: 1 def person(name, age, *, city='Hangzhou', job): 2 print(name,age, city, job) 由于命名关键字参数city具有默认值,调用时,可不传入city参数: 1 >>>person('Jack', 36, job='Engineer') 2 Jack 36 HangzhouEngineer 6.参数组合 目前,函数中共有5种常用的参数类型。若只传入一种类型的参数,这太简单了。难点在哪?难点就在参数组合使用,那是相当恶心。不过,平时最好不要混合使用参数,不然容易搞得“乌烟瘴气”。 OK!言归正传,不然跑题啦。 Python中,定义一个函数,我们可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。 下面来定义一个函数,该函数参数包含一种或几种参数。 1 def f1(a, b, c=0, *args, **kw): 2 print('a =', a, 'b =',b, 'c =', c, 'args=', args, 'kw =', kw) 3 4 def f2(a, b, c=0, *, d, **kw): 5 print('a =', a, 'b =',b, 'c =', c, 'd =',d, 'kw =', kw) 在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。 1 >>>f1(1, 2) 2 a = 1 b = 2 c = 0args = () kw = {} 3 >>>f1(1, 2, c=3) 4 a = 1 b = 2 c = 3args = () kw = {} 5 >>>f1(1, 2, 3, 'a', 'b') 6 a = 1 b = 2 c = 3args = ('a', 'b')kw = {} 7 >>>f1(1, 2, 3, 'a', 'b',x=99) 8 a = 1 b = 2 c = 3args = ('a', 'b')kw = {'x': 99} 9 >>>f2(1, 2, d=99, ext=None) 10 a = 1 b = 2 c = 0d = 99 kw = {'ext': None} 最神奇的是通过一个tuple和dict,你也可以调用上述函数: 1 >>> args= (1, 2, 3, 4) 2 >>> kw ={'d': 99, 'x':'#'} 3 >>>f1(*args, **kw) 4 a = 1 b = 2 c = 3args = (4,) kw = {'d': 99, 'x': '#'} 5 >>> args= (1, 2, 3) 6 >>> kw ={'d': 88, 'x':'#'} 7 >>>f2(*args, **kw) 8 a = 1 b = 2 c = 3d = 88 kw = {'x': '#'} 所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。 然而,虽然函数参数类型多达5种,但不要同时使用太多的组合,否则函数接口的可理解性很差。哎,简简单单才是真啊。 7.函数参数小结 参数,作为函数传入值的媒介,这里有必要做一个总结。 一、Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数; 二、默认参数一定要用不可变对象(元组(tuple)、数值型(number) (int和float)、字符串(string)),如果是可变对象,程序运行时会有逻辑错误; 三、*args是可变参数,args接收的是一个tuple; 四、**kw是关键字参数,kw接收的是一个dict; 五、可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1,2, 3)); 六、关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a':1, 'b': 2}); 七、使用*args和**kw这样的参数名是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法; 八、命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值; 九、定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。 故而,为了学好Python中的函数部分,参数不容忽视。 四、函数调用 在学习了函数的定义之后,我们应该需要调用函数,获取我们想要的数据。 如何调用函数呢?语法:函数名(参数) Python中,大佬们内置了许多有点用的函数,而我们只需拿来就行(这让我想起了鲁迅的“拿来主义”)。 若要调用Python中的内置函数,我们首先要知道函数名和参数。哈哈,又是参数~~~ 比如我想要求某数的绝对值。如果你不知道Python有相关的内置函数,就只能这么做: 1 #求取绝对值 2 >>>def abs(num): 3 ... if num>= 0: 4 ... return num 5 ... else: 6 ... return (-num) 7 ... 8 >>>abs(9) 9 9 10 >>>abs(0) 11 0 12>>>abs(-8) 13 8 上述代码虽然可以实现求绝对值的功能,但是太繁琐,需要敲几行代码才能实现该功能。然而,Python中有这个函数可以直接调用并输出结果。 1 #Python内置函数:abs() 2>>>abs(-9) 3 9 4 >>>abs(9) 5 9 6 #获取帮助文档 7 >>>help(abs) 8 Help on built-in function abs in module builtins: 9 10 abs(x, /) 11 Return the absolute value of the argument. Python官方网站:https://docs.python.org/3/library/functions.html 对于函数参数,通常会遇到以下两个问题: 1.如果函数传入参数的数量错误,会如何呢?简单,直接Error呗。比如abs(): 1 #函数传入参数的数量错误 2 >>>abs(-9,89) 3 Traceback (most recent call last): 4 File "<stdin>",line 1, in <module> 5 TypeError: abs()takes exactly one argument (2 given) 2.如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型: 1 #传入的参数类型错误 2 >>> abs('a') 3 Traceback (most recent call last): 4 File "<stdin>",line 1, in <module> 5 TypeError: badoperand type for abs(): 'str' 五、常见内置函数(Built-in Functions) Python 3.x版本下官方网站:https://docs.python.org/3/library/functions.html。该网址内显示Python内置函数相关内容(Built-in Functions)。 1.数据结构相关:list()、tuple()、dict()、str()…… 2.数字相关:abs()、min()、max()、len()…… 3.其他:int()、float()…… 好,不一一例举了,直接上图吧~~~ 如果读者想知道图中函数的详细含义,请点击上述链接网址。调皮一下,这里就不附上网址啦~~~ 六、数据类型转换(简单学过) Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数: 1 #Python之数据类型转换(int、float、str……) 2 >>> int('123') 3 123 4 >>>int(12.34) 5 12 6 >>>float('12.34') 7 12.34 8 >>>str(1.23) 9 '1.23' 10 >>>str(100) 11 '100' 12 >>>bool(1) 13 True 14 >>>bool('') 15 False [url=] [/url] [url=] [/url] 函数别名 了解Linux的读者可能知道别名(alias,unalias)这个指令。Python中也有“别名”之说,比如把函数名赋给变量: 1 #函数“别名” 2>>>abs(-8) 3 8 4 >>>a = abs 5 >>>a(-9) 6 9 7 >>>a(0) 8 0 9 >>>a(9) 10 9 [url=] [/url] [url=] [/url] 七、递归函数递归,实现只需if解决前面几个就行,后面直接用本身解决就行 讲述递归函数之前,我想起一个东西:阶乘(n!)。举个例子,我们来计算阶乘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)用递归的方式写出来就是: 1 #递归函数 2 >>>def fact(n): 3 ... if n == 1: 4 ... return 1 5 ... else: 6 ... return fact(n - 1) * n 7 ... 8>>>fact(1) 9 1 10>>>fact(5) 11 120 12 #递归函数之栈溢出 13>>>fact(1000) 14 Traceback (most recent call last): 15 File "<stdin>",line 1, in <module> 16 File "<stdin>",line 5, in x 17 File "<stdin>",line 5, in x 18 File "<stdin>",line 5, in x 19 [Previous line repeated 994 more times] 20 File "<stdin>",line 2, in x 21 RecursionError:maximum recursion depth exceeded in comparison 如代码所示,使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。 针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。 什么是尾递归?这个请读者自行查询呗,这里就不介绍啦,嘿嘿~~~ 1、下面来个斐波拉契数列: (1) 1 #斐波拉契数列 2 >>>def fibo(arg1,arg2): 3 ... if arg1 == 0: 4 ... print(arg1,arg2) 5 ... arg3 = arg1 +arg2 6 ... print(arg3) 7 ... fibo(arg2,arg3) 8 ... 9>>>fibo(0,1) 上述代码展示的斐波拉契数列会一直计算,直至栈溢出: 1 #斐波拉契数列导致栈溢出 2488272859468887457959087733119242564077850743657661180827326798539177758919828135114407499369796465649524266755391104990099120377 3 Traceback (most recent call last): 4 File "<stdin>",line 1, in <module> 5 File "<stdin>",line 6, in fibo 6 File "<stdin>",line 6, in fibo 7 File "<stdin>",line 6, in fibo 8 [Previous line repeated 992 more times] 9 File "<stdin>",line 5, in fibo 10 RecursionError:maximum recursion depth exceeded while calling a Python object 1116602747662452097049541800472897701834948051198384828062358553091918573717701170201065510185595898605104094736918879278462233015981029522997836311232618760539199036765399799926731433239718860373345088375054249 (2)Python实现斐波那契数列的几种方法斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)。在程序中经常使用斐波那契数列来加深我们对一些结构的理解.下面我们就用python里面几种常见的结构来实现斐波那契数列: (1)递归实现,可求斐波那契数列的第n项的数值 递归主要是在函数内部调用自己. #递归实现只需if解决前面几个就行,后面直接用本身解决就行 def fib(n):
if n==1 or n==2:
return1
else:
returnfib(n-1)+fib(n-2)
print(fib(8)) 递归实现的代码,非常容易理解,代码也非常简洁,缺点是效率较低 (2)迭代实现,相当于for循环 迭代主要思想为: 循环代码中参与运算的变量同时是保存结果的变量,最常见的迭代为遍历列表 这里的fibonacci函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。而且,在每次函数运行都要保存一个列表,占内存. (3)使用生成器来实现。可求斐波那契数列的前n项的所有数值,用普通的for 循环也行。 所谓生成器函数,就是包含yeild关键字的函数,通俗一点说,当函数被唤醒的时候执行,遇到yield关键字就停下并返回yield右边的数值,当函数被再次唤醒的时候,接着前面的继续执行 deffibo_sequence(n): """生成器函数执行斐波那契数列前n项""" first =1 second =1 for i inrange(n): yield first first,second = second, first +second 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停。而可以使用next()函数和send()函数恢复生成器。 生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器 在这里返回值不再是一个列表,而是一个生成器. 如:<generator object fib at 0x000001966845A408>可以通过for in 或者next()来取值 如def fib(n):
a=1
b=1
for i in range(n):
yield a #使用yield返回值函数,遇到yield关键字就停下并返回yield右边的数值
a,b=b,a+b #当函数被再次唤醒的时候,接着前面的继续执行
k=fib(36) #在这里返回值不再是一个列表,而是一个生成器.如:<generator object fib at 0x000001966845A408>
print(next(k))# 函数next()通过调用其next ()方法从迭代器中检索下一个项目
print(next(k))# 每次调用next(k)的时候,返回可迭代对象的下一个元素。
print(next(k))# 如果所有元素均已经返回过,则抛出StopIteration 异常
print(next(k))
print(next(k))
print(next(k))最后,关于斐波那契数列的第一项到底是0还是1,这个各有各的说法,但是不影响斐波那契数列的特点,如果认为第一项是0的话,那么第一种递归函数可能就要稍加修改,但原理还是一样的 2、如何避免递归实现方法的栈溢出 递归函数最恶心的时候莫非栈溢出(Stackoverflow)。 如何解决? (1)人为设置递归深度 使用python写的递归程序如果递归太深, 那么极有可能因为超过系统默认的递归深度限制而出现错误。一般默认递归长度在1000左右。 RuntimeError:maximum recursion depth exceeded in comparison 显然此时我们可以人为修改 import sys sys.setrecursionlimit(1000000)#括号中的值为递归深度 (2)尾递归优化(其实没用) 注:这只是一种思维的科普 解决递归调用栈溢出的另一种方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。 尾递归是指,在函数返回的时候,调用自身本身,并且,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) 可以看到,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标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题 小结 方法一:人为修改默认递归长度 方法二:人为修改python解释器,将其优化,会十分有趣! 3、汉罗塔问题 (1) 匿名函数 对于汉罗塔问题,利用递归来解决该问题也是相当的简单,且代码清晰: 匿名函数语法:(lambda和def 功能相同,只是lambda定义的函数没名字,直接跟参数:函数体) lambda x: x * x(关键字lambda表示匿名函数,冒号前面的x表示函数参数) 1 def f(x): 2 return x *x 匿名函数的好处:因为函数没有名字,不必担心函数名冲突。 1 def is_odd(n): 2 return n %2==1 3 4 L = list(filter(lambda n: n%2==1,range(1,20))) 5 print(L) lambda函数的语法只包含一个语句,如下: lambda arg1,arg2,.....argn:expression(主要是看下面的例子)
代码示例: #-*- coding:utf-8 -*- #__author__ = "www.iplaypy.com" # 普通python函数 deffunc(a,b,c): return a+b+c print func(1,2,3) # 返回值为6 # lambda匿名函数 f = lambda a,b,c:a+b+c print f(1,2,3) # 返回结果为6 # 大家注意观察上面的Python示例代码,f = lambda a,b,c:a+b+c 中的关键字lambda表示匿名函数,
# 冒号:之前的a,b,c表示它们是这个函数的参数。
# 匿名函数不需要return来返回值,表达式本身结果就是返回值。 小结: 1.匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。 2.Python中,匿名函数可以作为返回值返回并输出结果。匿名函数不需要return来返回值,表达式本身结果就是返回值。 如: # lambda匿名函数 f = lambda a,b,c:a+b+c print f(1,2,3) # 返回结果为6 八、其他知识点 1.对Python的星号(*、**)的理解 在python开发过程中,有些函数的参数前面是带*的,和**的,也有的没有任何*,怎么理解,什么意思呢?下面我们就来说说。 一、用于函数定义时【即参数传递(一种是可变参数传递,一种是关键字传递)】 (1) * 会把传入的参数(在不知道要传入几个参数的情况下才用的这种方法)变为,名为args的元组。即传入的参数变为元组了如果在一个参数前面有*这样的符号,那么这就是可变参数。 (2) ** 会把传入的参数(在不知道要传入几个参数的情况下才用的这种方法)变为,名为kwargs的字典。即传入的参数变为字典了。如果在一个参数前面有**这样的符号,那么这就是关键字参数。主要作用,能够扩展函数的功能。 下面看一下例子: def prints(*arg): print arg; prints (1,2,3,4,5,6,7) 打印的结果是一个元组: (1,2,3,4,5,6,7) ,也就是说该参数(arg)将传进来的所有参数放在了一个元组中。将方法变形看一看这个方法的好处: def prints(*arg): n = len(arg) for i in xrange(n): print arg[i]; prints (1,2,3,4,5,6,7) 打印的结果是: 1 2 3 4 5 6 7 这表明当你的方法需要一些参数,但是你又不知道究竟会有多少的时候,使用*号作为可变参数列表,就可以在方法内对参数进行调用。 看一下**的例子: def prints(**arg): print arg prints (a=1,b=2,c=3,d=4,e=5,f=6,g=7) 打印的结果是: {‘a’: 1, ‘c’: 3, ‘b’: 2, ‘e’: 5, ‘d’: 4, ‘g’: 7, ‘f’: 6},也就是一个字典,所以具体有什么好处,可以在实践中逐渐摸索。 二、用于 unpack 可迭代的变量 例1:赋值语句中的 * 用于列表数值取出 # example 1 >>> a, *b, c = range(5) >>> a 0 >>> c 4 >>> b [1, 2, 3] # example 2 >>> ecord = ('ACME', 50, 123.45, (12, 18, 2012)) >>> name, *_1, (*_2, year) = record >>> print(name) 'ACME' >>> print(_1) [50,123.45] >>> print(_2) [12,18] >>> print(year) # example 3 >>> for a, *b in [(1, 2, 3), (4, 5, 6, 7)]: >>> print(b) [2, 3] [5, 6, 7] 例2:用于递归(列表求和) def sum(items): head, *tail = items return head + sum(tail) iftail else head items = [1, 10, 7, 4, 5, 9] print(sum(items)) 注意:*表达式不可单独使用! (1)错误用法: >>> *a = range(5) >>> a= *range(5) >>> print(*range(5)) (2)正确用法: >>> *a,= range(5) [0, 1, 2, 3, 4] >>> a = *range(5), (0, 1, 2, 3, 4) >>> *a,b = range(5) >>> print(*range(5),) 三、用于函数调用时【即list变量前面加星号,字典变量前面加两个星号时表示什么意思】 (1)列表前面加星号作用是将列表解开成两个独立的参数,传入函数, (2)字典前面加两个星号,是将字典解开成独立的元素作为形参。
1. def add(a, b):2. return a+b3. 4. data = [4,3]5. print add(*data) # 函数调用过程6. *[4,3]就是把[4,3]分解成两个数字4和37. #equals to print add(4, 3)8. data = {'a' : 4, 'b' : 3}9. print add(**data)10.#equals to print add(4, 3)又如: >>>def f(a, b, c, d): print(a, b, c, d, sep ='&') >>>f(1,2,3,4) 1&2&3&4 >>>f(*[1, 2, 3, 4]) 1&2&3&4 * 会将列表[1,2,3,4]中的数字取出来,分别赋给参数a,b,c,d 四、几个注意点(1) 可变位置参数*args是一个元组,是不可修改的。 >>> def foo(*args): ... args[0] = 5 ... >>> foo(1, 2, 3) Traceback (most recent call last): File"<stdin>", line 1, in <module> File"<stdin>", line 2, in foo TypeError: 'tuple' object does not support item assignment >>> l = [1, 2, 3] >>> foo(*l) Traceback (most recent call last): File"<stdin>", line 1, in <module> File"<stdin>", line 2, in foo TypeError: 'tuple' object does not support item assignment 无论我们怎么传入参数,args都是一个tuple类型(被*转化的),不能进行修改。 (2)对于字典类型的如果只使用一个型号*那么传入的只是字典的键。 >>> def foo2(*args, **kwarg): ... print args, kwarg ... >>> d = {'a':1, 'b':2, 'c':3} >>> foo2(*d) ('a', 'c', 'b') {} >>> foo2(**d) () {'a': 1, 'c': 3, 'b': 2} (3)(*)星号的参数传递主要是在不知道参数多少个的情况下可以准确的传递参数 史上最全的Python函数全部知识点总结(个人6天的学习成果) |