函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,具体区别,我们后面会讲,编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可
特性:
1.代码重用
2.保持一致性
3.可扩展性
二 函数的创建
2.1 格式:
Python 定义函数使用 def 关键字,一般格式如下:
1
2
|
def 函数名(参数列表): 函数体 |
def hello():
print('hello')
hello()#调用
2.2 函数名的命名规则:
- 函数名必须以下划线或字母开头,可以包含任意字母、数字或下划线的组合。不能使用任何的标点符号;
- 函数名是区分大小写的。
- 函数名不能是保留字。
2.3 形参和实参
形参:形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)
实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参
区别:形参是虚拟的,不占用内存空间,.形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参
1
2
3
4
5
|
import time times = time.strftime( '%Y--%m--%d' ) def f(time): print ( 'Now time is : %s' % times) f(times) |
2.4 实例
实例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def show_shopping_car(): saving = 1000000 shopping_car = [ ( 'Mac' , 9000 ), ( 'kindle' , 800 ), ( 'tesla' , 100000 ), ( 'Python book' , 105 ), ] print ( '您已经购买的商品如下' .center( 50 , '*' )) for i ,v in enumerate (shopping_car, 1 ): print ( '\033[35;1m %s: %s \033[0m' % (i,v)) expense = 0 for i in shopping_car: expense + = i[ 1 ] print ( '\n\033[32;1m您的余额为 %s \033[0m' % (saving - expense)) show_shopping_car() |
实例2:
现在我们就用一个例子来说明函数的三个特性:
def action1(n): print ('starting action1...') with open('日志记录','a') as f: f.write('end action%s\n'%n) def action2(n): print ('starting action2...') with open('日志记录','a') as f: f.write('end action%s\n'%n) def action3(n): print ('starting action3...') with open('日志记录','a') as f: f.write('end action%s\n'%n) action1(1) action2(2) action3(3) ##***************代码重用 def logger(n): with open('日志记录','a') as f: f.write('end action%s\n'%n) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() ##***************可扩展和保持一致 ##为日志加上时间 import time def logger(n): time_format='%Y-%m-%d %X' time_current=time.strftime(time_format) with open('日志记录','a') as f: f.write('%s end action%s\n'%(time_current,n)) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() 函数的特性展示
三 函数的参数
- 必备参数
- 关键字参数
- 默认参数
- 不定长参数
必需的参数(位置参数):
1
2
3
4
5
6
|
def f(name,age): print ( 'I am %s,I am %d' % (name,age)) f( 'alex' , 18 ) f( 'alvin' , 16 ) |
关键字参数:
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
1
2
3
4
5
6
|
def f(name,age): print ( 'I am %s,I am %d' % (name,age)) # f(16,'alvin') #报错 f(age = 16 ,name = 'alvin' ) |
缺省参数(默认值参数):
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。下例会打印默认的age,如果age没有被传入:
1
2
3
4
5
6
7
8
9
|
def print_info(name,age,sex = 'male' ): print ( 'Name:%s' % name) print ( 'age:%s' % age) print ( 'Sex:%s' % sex) return print_info( 'alex' , 18 ) print_info( '铁锤' , 40 , 'female' ) |
不定长参数(动态传参:*args(动态接收位置参数)**kwargs(动态接收关键字参数))
之前我们说过了传参, 如果我们需要给⼀个函数传参, ⽽参数⼜是不确定的. 或者我给⼀个函数传很多参数, 我的形参就要写很多, 很⿇烦, 怎么办呢. 我们可以考虑使⽤动态参数.形参的第三种: 动态参数
动态参数分成两种:
1. 动态接收位置参数
首先我们先回顾下位置参数, 位置参数, 按照位置进行传参
def chi(quality_food, junk_food):
print("我要吃", quality_food, junk_food)
chi("大米饭", "小米饭") # "大米饭"传递给quality_food "小米饭"传递给junk_food
按照位置传
现在问题来了. 我想吃任意的食物. 数量是任意的, 食物也是任意的. 这时我们就要用到动态参数了
def chi(*food):
print("我要吃", food)
chi("大米饭", "小米饭")
结果:
我要吃 ('大米饭', '小米饭') # 多个参数传递进去. 收到的内容是元组tuple
动态接收参数的时候要注意: 动态参数必须在位置参数后面
def chi(*food, a, b):
print("我要吃", food, a, b)
chi("大米饭", "小米饭", "馒头", "茄")
这时程序运行会报错. 因为前面传递进去的所有位置参数都被*food接收了. a和b永远接收不到参数
Traceback (most recent call last):File "/Users/sylar/PycharmProjects/oldboy/fun.py", line 95, in <module>chi("大米饭", "小米饭", "馒头", "茄")
TypeError: chi() missing 2 required keyword-only arguments: 'a' and 'b'
所以必须改写成以下代码:
def chi(*food, a, b):
print("我要吃", food, a, b)
chi("大米饭", "小米饭", a="馒头", b="茄") # 必须用关键字参数来指定
这个时候a和b就有值了, 但是这样写呢位置参数就不能用了. 所以. 我们要先写位置参数,然后再写动态参数
def chi(a, b, *food):
print("我要吃", a, b, food)
chi("⼤⽶饭", "⼩⽶饭", "馒头", "⾯条") # 前两个参数由位置参数来接收, 后面的参数由动态参数接收
那默认值参数呢?
def chi(a, b, c='馒头', *food):
print(a, b, c, food)
chi("⾹蕉", "菠萝") # ⾹蕉 菠萝 馒头 (). 默认值生效
chi("⾹蕉", "菠萝", "葫芦娃") # ⾹蕉 菠萝 葫芦娃 () 默认值不生效
chi("⾹蕉", "菠萝", "葫芦娃", "⼝罩") # ⾹蕉 菠萝 葫芦娃 ('⼝罩',) 默认值不生效
我们发现默认值参数写在动态参数前面. 默认值只有这种情况可能会生效.
def chi(a, b, *food, c="娃哈哈"):
print(a, b, food, c)
chi("⾹蕉", "菠萝") # ⾹蕉 菠萝 () 娃哈哈 默认值生效
chi("⾹蕉", "菠萝", "葫芦娃") # ⾹蕉 菠萝 ('葫芦娃',) 娃哈哈 默认值生效
chi("⾹蕉", "菠萝", "葫芦娃", "⼝罩") # ⾹蕉 菠萝 ('葫芦娃', '⼝罩') 娃哈哈 默认值生效
这个时候我们发现所有的默认值都生效了. 这个时候如果不给关键字传参. 那么你的默认值是永远都生效的.
顺序: 位置参数, 动态参数*, 默认值参数
2. 动态接收关键字参数
在python中可以动态的位置参数, 但是*这种情况只能接收位置参数无法接收关键字参数.
在python中使用**来接收动态关键字参数
def func(**kwargs):
print(kwargs)
func(a=1, b=2, c=3)
func(a=1, b=2)
结果:
{'a': 1, 'b': 2, 'c': 3}
{'a': 1, 'b': 2}
这个时候接收的是⼀个dict顺序的问题, 在函数调用的时候, 如果先给出关键字参数, 则整个参数列表会报错.
def func(a, b, c, d):
print(a, b, c, d)
# 关键字参数必须在位置参数后, 否则参数会混乱
func(1, 2, c=3, 4)
所以关键字参数必须在位置参数后. 由于实参是这个顺序. 所以形参接收的时候也是这个顺序. 也就是说位置参数必须在关键字参数前. 动态接收关键字参数也要在后
最终顺序(*):
位置参数 > *args > 默认值参数 > **kwargs
这四种参数可以任意的进行使用
如果想接收所有的参数:
def func(*args, **kwargs):
print(args, kwargs)
func("麻花藤","⻢晕",wtf="胡辣汤")
动态参数的另一种传参方式:
def fun(*args):
print(args)
lst = [1, 4, 7]
fun(lst[0], lst[1], lst[2])
fun(*lst) # 可以使用*把整个列表按顺序打散
s = "⾂妾做不到"
fun(*s) # 字符串也可以打散, (可迭代对象)
在实参位置上给个序列,列表,可迭代对象前加个*表把这个序列按顺序打散. 在形参的位置上的* 表把接收到的参数组合成一个元组。如果是个字典, 那么也可以打散. 不过需两个*
def fun(**kwargs):
print(kwargs)
dic = {'a':1, 'b':2}
fun(**dic)
函数的注释:
def chi(food, drink):
"""
这⾥是函数的注释, 先写⼀下当前这个函数是⼲什么的, ⽐如我这个函数就是⼀个吃
:param :param food: 参数food是什么意思
:param :param drink: 参数drink是什么意思
:return :return: 返回的是什么东东
"""
print(food, drink)
return "very good"
print(chi.__doc__) #打印注释内容
补充(高阶函数):
高阶函数是至少满足下列一个条件的函数:
-
-
- 接受一个或多个函数作为输入
- 输出一个函数
-
1
2
3
4
5
6
7
8
9
10
11
|
def add(x,y,f): return f(x) + f(y) res = add( 3 , - 6 , abs ) print (res) ############### def foo(): x = 3 def bar(): return x return bar |
四 函数的返回值
要想获取函数的执行结果,就可以用return语句把结果返回
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
- return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出。
五 作用域
命名空间
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 由于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
def fun():
a = 10
print(a)
fun()
print(a) # a不存在了已经..
我们给存放名字和值的关系的空间起了个名字叫: 命名空间. 我们的变量在存储的时候就是存储在这片空间中的.
命名空间分类:
1. 全局命名空间--> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间
2. 局部命名空间--> 在函数中声明的变量会放在局部命名空间
3. 内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
加载顺序:
1. 内置命名空间
2. 全局命名空间
3. 局部命名空间(函数被执行的时候)
取值顺序:
1. 局部命名空间
2. 全局命名空间
3. 内置命名空间
a = 10
def func():
a = 20
print(a)
func() # 20
作用域: 就是作用范围, 按照生效范围来看分为 全局作用域和局部作用域
全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循从上到下逐行执行).
局部作用域: 在函数内部可以使用.
作用域命名空间:
1. 全局作用域: 全局命名空间 + 内置命名空间
2. 局部作用域: 局部命名空间
我们可以通过globals()函数来查看全局作用域中的内容, 也可以通过locals()来查看局部作用域中的变量和函数信息
a = 10
def func():
a = 40
b = 20
def abc():
print("哈哈")
print(a, b) # 这⾥使⽤的是局部作用域
print(globals()) # 打印全局作用域中的内容
print(locals()) # 打印局部作用域中的内容
func()
5.2 作用域产生
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的,如下代码:
1
2
3
|
if 2 > 1 : x = 1 print (x) # 1 |
这个是没有问题的,if并没有引入一个新的作用域,x仍处在当前作用域中,后面代码可以使用。
1
2
3
|
def test(): x = 2 print (x) # NameError: name 'x2' is not defined |
def、class、lambda是可以引入新作用域的。
5.3 变量的修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
################# x = 6 def f2(): print (x) x = 5 f2() # 错误的原因在于print(x)时,解释器会在局部作用域找,会找到x=5(函数已经加载到内存),但x使用在声明前了,所以报错: # local variable 'x' referenced before assignment.如何证明找到了x=5呢?简单:注释掉x=5,x=6 # 报错为:name 'x' is not defined #同理 x = 6 def f2(): x + = 1 #local variable 'x' referenced before assignment. f2() |
5.4 global关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
1
2
3
4
5
6
7
8
9
|
count = 10 def outer(): global count print (count) count = 100 print (count) outer() #10 #100 |
5.5 nonlocal关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了
1
2
3
4
5
6
7
8
9
10
11
|
def outer(): count = 10 def inner(): nonlocal count count = 20 print (count) inner() print (count) outer() #20 #20 |
global与nonlocal练习如下:
a = 10 def func1(): a = 20 def func2(): nonlocal a a = 30 print(a) func2() print(a) func1() 结果: 加了nonlocal 30 30 不加nonlocal 30 20 a = 1 def fun_1(): a = 2 def fun_2(): nonlocal a a = 3 def fun_3(): a = 4 print(a) print(a) fun_3() print(a) print(a) fun_2() print(a) print(a) fun_1() print(a) 结果: 1 2 3 4 3 3 1 如果把nonlocal换成global,结果如下: 1 2 3 4 3 2 3
5.6 小结
(1)变量查找顺序:LEGB,作用域局部>外层作用域>当前模块中的全局>python内置作用域;
(2)只有模块、类、及函数才能引入新作用域;
(3)对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,就会使用外部作用域的变量;
(4)内部作用域要修改外部作用域变量的值时,全局变量要使用global关键字,嵌套作用域变量要使用nonlocal关键字。nonlocal是python3新增的关键字,有了这个 关键字,就能完美的实现闭包了。
六 递归函数
定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
实例1(阶乘)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def factorial(n): result = n for i in range ( 1 ,n): result * = i return result print (factorial( 4 )) #**********递归********* def factorial_new(n): if n = = 1 : return 1 return n * factorial_new(n - 1 ) print (factorial_new( 3 )) |
实例2(斐波那契数列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def fibo(n): before = 0 after = 1 for i in range (n - 1 ): ret = before + after before = after after = ret return ret print (fibo( 3 )) #**************递归********************* def fibo_new(n): #n可以为零,数列有[0] if n < = 1 : return n return (fibo_new(n - 1 ) + fibo_new(n - 2 )) print (fibo_new( 3 )) |
1
|
print (fibo_new( 30000 )) #maximum recursion depth exceeded in comparison |
在python中递归的深度最大到998
def foo(n):
print(n)
n += 1
foo(n)
foo(1)
递归的应用:
我们可以使用递归来遍历各种树形结构, 比如我们的文件夹系统. 可以使用递归来遍历该文件夹中的所有文件
import os def read(filepath, n): files = os.listdir(filepath) # 获取到当前文件夹中的所有文件 for fi in files: # 遍历文件夹中的文件, 这里获取的只是本层文件名 fi_d = os.path.join(filepath,fi) # 加入文件夹 获取到文件夹+文件 if os.path.isdir(fi_d): # 如果该路径下的文件是文件夹 print("\t"*n, fi) read(fi_d, n+1) # 继续进行相同的操作 else: print("\t"*n, fi) # 递归出口. 最终在这里隐含着return #递归遍历目录下所有文件 read('../oldboy/', 0)
递归函数的优点: 是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)
二分查找
二分查找. 每次能够排除掉一半的数据. 查找的效率非常高. 但是局限性比较大. 必须是有序序列才可以使用二分查找
要求: 查找的序列必须是有序序列.
# 判断n是否在lst中出现. 如果出现请返回n所在的位置 # 二分查找---非递归算法 lst = [22, 33, 44, 55, 66, 77, 88, 99, 101, 238, 345, 456, 567, 678, 789] n = 567 left = 0 right = len(lst) - 1 count = 1 while left <= right: middle = (left + right) // 2 if n < lst[middle]: right = middle - 1 elif n > lst[middle]: left = middle + 1 else: print(count) print(middle) break count = count + 1 else: print("不存在") # 普通递归版本二分法 def binary_search(n, left, right): if left <= right: middle = (left+right) // 2 if n < lst[middle]: right = middle - 1 elif n > lst[middle]: left = middle + 1 else: return middle return binary_search(n, left, right) # 这个return必须要加. 否则接收到的永远是None. else: return -1 print(binary_search(567, 0, len(lst)-1)) # 另类二分法, 很难计算位置. def binary_search(ls, target): left = 0 right = len(ls) - 1 if left > right: print("不在这里") middle = (left + right) // 2 if target < ls[middle]: return binary_search(ls[:middle], target) elif target > ls[middle]: return binary_search(ls[middle+1:], target) else: print("在这里") binary_search(lst, 567)
七 内置函数(Py3.5)
py2内置函数:https://docs.python.org/3.5/library/functions.html#repr
py3内置函数(思维导图):https://www.processon.com/mindmap/5da09c36e4b04913a12fc80b
重要的内置函数:
1 filter(function, sequence) 过滤函数
1
2
3
4
5
6
7
8
9
10
|
str = [ 'a' , 'b' , 'c' , 'd' ] def fun1(s): if s ! = 'a' : return s ret = filter (fun1, str ) print ( list (ret)) # ret是一个迭代器对象 |
在filter中会自动的把iterable中的元素传递给function. 然后根据function返回的True或者False来判断是否保留此项数据
对sequence中的item依次执行function(item),将执行结果为True的item做成一个filter object的迭代器返回。可以看作是过滤函数。
2 map(function, sequence) 映射函数
1
2
3
4
5
6
7
8
9
10
|
str = [ 1 , 2 , 'a' , 'b' ] def fun2(s): return s + "alvin" ret = map (fun2, str ) print (ret) # map object的迭代器 print ( list (ret)) # ['aalvin', 'balvin', 'calvin', 'dalvin'] |
对sequence中的item依次执行function(item),将执行结果组成一个map object迭代器返回.
map也支持多个sequence,这就要求function也支持相应数量的参数输入:
1
2
3
|
ef add(x,y): return x + y print ( list ( map (add, range ( 10 ), range ( 10 )))) ##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] |
3 reduce(function, sequence, starting_value)
1
2
3
4
5
6
7
8
|
from functools import reduce def add1(x,y): return x + y print ( reduce (add1, range ( 1 , 101 ))) ## 4950 (注:1+2+...+99) print ( reduce (add1, range ( 1 , 101 ), 20 )) ## 4970 (注:1+2+...+99+20) |
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用.
4 lambda 匿名函数
普通函数与匿名函数的对比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#普通函数 def add(a,b): return a + b print add( 2 , 3 ) #匿名函数 add = lambda a,b : a + b print add( 2 , 3 ) #========输出=========== 5 5 |
匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。
因为lamdba在创建时不需要命名,所以,叫匿名函数
注意:
1. 函数的参数可以有多个. 多个参数之间用逗号隔开
2. 匿名函数不管多复杂. 只能写一行, 且逻辑结束后直接返回数据
3. 返回值和正常的函数一样, 可以是任意数据类型
4.返回多个值时要用括号括起来
a=lambda x, y : ( x, y)
如果不括起来,那么a就成了一个元组,如 t=1,2 t就是一个元组
a=lambda x, y : x, y 这里a就是一个元组a=((lambda x, y : x), y)
匿名函数并不是说一定没有名字. 这里前面的变量就是一个函数名. 说他是匿名原因是我们通过__name__查看的时候是没有名字的. 统一都叫lambda. 在调用的时候没有什么特别之处.像正常的函数调用即可
5.format() 与具体数据相关, 用于计算各种小数, 精算等
# 字符串
print(format('test', '<20')) # 左对齐
print(format('test', '>20')) # 右对齐
print(format('test', '^20')) # 居中
# 数值
print(format(3, 'b')) # 二进制
print(format(97, 'c')) # 转换成unicode字符
print(format(11, 'd')) # 十进制
print(format(11, 'o')) # 八进制
print(format(11, 'x')) # 十六进制(大写字母)
print(format(11, 'X')) # 十六进制(小写字母)
print(format(11, 'n')) # 和d一样
print(format(11)) # 和d一样
# 浮点数
print(format(123456789, 'e')) # 科学计数法. 默认保留6位小数
print(format(123456789, '0.2e')) # 科学计数法. 保留2位小数(小写)
print(format(123456789, '0.2E')) # 科学计数法. 保留2位小数(大写)
print(format(1.23456789, 'f')) # 小数点计数法. 保留6位小数
print(format(1.23456789, '0.2f')) # 小数点计数法. 保留2位小数
print(format(1.23456789, '0.10f')) # 小数点计数法. 保留10位小数
print(format(1.23456789e+10000, 'F')) # 小数点计数法
6.zip() 函数 用于将可迭代的对象作为参数, 将对象中对应的元素打包成一个个元组, 然后返回由这些元组组成的开了表. 如果各个迭代器的元素个数不一致, 则返回列表长度与最短 的对象相同.
l1 = [1,2,3,]
l2 = ['a','b','c',5]
l3 = ('*','**',(1,2,3))
for i in zip(l1,l2,l3):
print(i)
结果:
(1, 'a', '*')
(2, 'b', '**')
(3, 'c', (1, 2, 3))
7.sorted()
排序函数.
语法: sorted(Iterable, key=None, reverse=False)
Iterable: 可迭代对象
key: 排序规则(排序函数), 在sorted内部会将可迭代对象中的每⼀个元素传递给这个函数的参数. 根据函数运算的结果进行排序
reverse: 是否是倒叙. True: 倒叙, False: 正序
lst = [1,5,3,4,6]
lst2 = sorted(lst)
print(lst) # 原列表不会改变
print(lst2) # 返回的新列表是经过排序的
dic = {1:'A', 3:'C', 2:'B'}
print(sorted(dic)) # 如果是字典. 则返回排序过后的key
和函数组合使用
# 根据字符串长度进行排序
st = ["麻花藤", "冈本次郎", "中央情报局", "狐仙"]
# 计算字符串长度
def func(s):
return len(s)
print(sorted(lst, key=func))
和lambda组合使用
# 根据字符串长度进行排序
lst = ["麻花藤", "冈本次郎", "中央情报局", "狐仙"]
# 计算字符串长度
def func(s):
return len(s)
print(sorted(lst, key=lambda s: len(s)))
lst = [{"id":1, "name":'alex', "age":18},{"id":2, "name":'wusir', "age":16},{"id":3, "name":'taibai', "age":17}]
# 按照年龄对学生信息进行排序
print(sorted(lst, key=lambda e: e['age']))
八 函数式编程
学会了上面几个重要的函数后,我们就可以来聊一聊函数式编程到底是个什么鬼
一 概念(函数式编程)
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。
而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如y=x*x函数计算x的平方根,只要x的平方,不论什么时候调用,调用几次,值都是不变的。
纯函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的(immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。
函数式语言的如条件语句,循环语句也不是命令式编程语言中的控制语句,而是函数的语法糖,比如在Scala语言中,if else不是语句而是三元运算符,是有返回值的。
严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。
二 实例
假如,现在你来到 baidu面试,面试官让你把number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]中的正数的平均值,你肯定可以写出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#计算数组中正整数的平均值 number = [ 2 , - 5 , 9 , - 7 , 2 , 5 , 4 , - 1 , 0 , - 3 , 8 ] count = 0 sum = 0 for i in range ( len (number)): if number[i]> 0 : count + = 1 sum + = number[i] print sum ,count if count> 0 : average = sum / count print average #========输出=========== 30 6 5 |
首先循环列表中的值,累计次数,并对大于0的数进行累加,最后求取平均值。
这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。
这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
那么,不用这种方式如何做到呢?
1
2
3
4
5
6
7
8
9
10
|
number = [ 2 , - 5 , 9 , - 7 , 2 , 5 , 4 , - 1 , 0 , - 3 , 8 ] positive = filter ( lambda x: x> 0 , number) average = reduce ( lambda x,y: x + y, positive) / len (positive) print average #========输出=========== 5 |
这段代码最终达到的目的同样是求取正数平均值,但是它得到结果的方式和 之前有着本质的差别:通过描述一个列表->正数平均值 的映射,而不是描述“从列表得到正数平均值应该怎样做”来达到目的。
再比如,求阶乘
通过Reduce函数加lambda表达式式实现阶乘是如何简单:
1
2
|
from functools import reduce print ( reduce ( lambda x,y: x * y, range ( 1 , 6 ))) |
又比如,map()函数加上lambda表达式(匿名函数)可以实现更强大的功能:
1
2
3
|
squares = map ( lambda x : x * x , range ( 9 )) print (squares) # <map object at 0x10115f7f0>迭代器 print ( list (squares)) #[0, 1, 4, 9, 16, 25, 36, 49, 64] |
三 函数式编程有什么好处呢?
1)代码简洁,易懂。
2)无副作用
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。