1、函数的定义:
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
定义一个函数:
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- 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、函数的调用:
定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从Python提示符执行。
3、return语句
return语句[表达式]退出函数,选择性地向调用方返回一个表达式。
没有return返回None
return1返回1
return1,2,3返回(1,2,3)元组
多个return只返回第一个
4、变量的作用域:
一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。两种最基本的变量作用域如下:
- 全局变量:定义在函数外的拥有全局作用域
- 局部变量:定义在函数内部的变量
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
#!/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
以上示例输出结果为:
函数内是局部变量 : 30 函数外是全局变量 : 0
5、传参:
在实参的角度:
规则:按位置传值必须在按关键字传值的前面
对一个形参只能赋值一次
1.按照位置传值
2.按照关键字传值
3.混着用
在形参的角度:
规则:默认参数必须放到位置参数的后面
1.位置参数
2.默认参数
3.*args (接收位置传值)
4.**kwargs(接收关键字传值)
6、函数嵌套嵌套
#嵌套调用 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))
#嵌套定义 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()
7、一切皆对象,函数对象
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>) 1 #代表f2引用了,f1中的x=1 2 #代表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
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、二分算法
l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def search(num, l, start=None, end=None): start = start if start else 0 end = end if end else len(l) - 1 mid = (end - start) // 2 + start if start > end: return None elif l[mid] > num: return search(num, l, start, mid - 1) elif l[mid] < num: return search(num, l, mid + 1, end) elif l[mid] == num: return mid print(search(66, l)) print(l.index(66))