函数——基本介绍
函数是什么?
函数一词源于数学,但编程的函数概念,与数学中的函数是有很大不同的,编程的函数再英文中也有很多不同的叫法。再BASIV中叫做subroutine(子过程或子程序),
再Pasca中叫做procedure(过程)和function,再C中只有function,再Java里面叫做method
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
定义:函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
语法:
def sayhi():#(函数名)
print('hello,I'mnobody')#首先定义这一块东西是占内存的
sayhi()#调用函数 #如果不加(),打印的就是内存地址
可以带参数
#下面这段代码
a,b = 5,8 # 意思是连着定义了两个变量,a = 5, b = 8
c = a**b #意思时c = a的b次方
print(c)
#改成用函数写
def calc(x,y): #x,y就是接下来传进去的参数
res = x**y
return res#返回函数执行结果
c = calc(a,b)
print(c)
以上是函数最基本的定义
特性:
1.减少重复代码,(主要作用)
2.使程序变得可扩展(和第三个作用有点儿像)(就是说只需要改一个地方,都会变。)
3.使程序变得易维护
例1:(现在我们用一个例子说明函数的三个特性:)
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:
import time
times = time.strftime('%Y-%m-%d')
def func(time):
print("Now time is :%s"%times)
func(times)
# 结果:Now time is :2018-03-24
例2:
def show_shoppingcart():
balance = 100000
shopping_cart = [
('mac',9000),
('kindle',800),
('tesla',100000),
('python_book',120),
]
print("shopping_cart".center(50,'*'))
for i,v in enumerate(shopping_cart):
print('\033[35;1m %s: %s \033[0m'%(i,v))
expense = 0
for i in shopping_cart:
expense +=i[1]
print('\n\033[32;1m您的余额为 %s \033[0m'%(balance-expense))
show_shoppingcart()
# 结果:
# ******************shopping_cart*******************
# 0: ('mac', 9000)
# 1: ('kindle', 800)
# 2: ('tesla', 100000)
# 3: ('python_book', 120)
#
# 您的余额为 -9920
函数——默认参数
默认参数:
我们发现country这个参数基本都是‘CN’
就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就会是中国,这就是通过默认参数实现的,把country变成默认参数非常简单
默认参数必须要放在后面。
函数——关键参数
正常情况下,给函数传参数要按顺序,不想按顺序就用关键参数,只需指定参数名即可(指定了参数名的参数就叫关键参数),但记住一个要求就是,关键参数必须
放在位置参数(以位置顺序确定对应关系的参数)之后。
调用可以这样
就是说,位置参数后面加个 = 什么,就会变成关键参数,就会按照我的指定来,而且一个位置参数也不能传两个值,比如下面这两个都不行:
函数——非固定参数
这是一个元祖类型的
这是最终运行效果
这是一个字典形式的
Python中位置参数、默认参数、可变参数、命名关键字参数、关键字参数的区别
Python中必选参数、默认参数、可变参数、命名关键字参数、关键字参数的区别:
Num01–>必选参数(亦即位置参数):
定义:就是在给函数传参数时,按照顺序,依次传值。
先写一个下面的函数:
def power(m, n): result=1 while n>0: n=n-1 result=result*m return result # 调用函数并输出结果 print(power(4,3)) 解释说明: 函数power(m,n)中有两个参数,m和n,这两个参数都是位置参数,调用的时候,传入的两个值按照顺序,依次赋值给m和n。
Num02–>默认参数:
定义:就是在写函数的时候直接给参数传默认的值,调用的时候,默认参数已经有值,就不用再传值了。
作用:最大的好处就是降低调用函数的难度。
修改以上函数:
def power(m, n=3): result=1 while n>0: n=n-1 result=result*m return result # 调用函数并输出结果 print(power(4))
设置默认参数时,有两点注意事项:
第一:必选参数在前,默认参数在后,否则python解释器会报错。
第二:默认参数一定要指向不变对象!指向不变对象!指向不变对象!
(注意:python中的字符串,数字,元组都可以看做对象。)为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象
Num03–>可变参数:
定义:可变参数就是传入的参数个数是可变的,可以是0个,1个,2个,……很多个。
作用:就是可以一次给函数传很多的参数
特征:*args
修改代码如下:
def power(*args): result=0 for n in args: result=result+n*n return result # 调用函数 tupleArray=(1,2,3) # *tupleArray这种方式很常见,很重要 print(power(*tupleArray)) listArray=[1,2,3] # *listArray这种方式很常见,很重要 # *listArray表示把listArray这个list中所有元素作为可变参数传进去 print(power(*listArray))
Num04–>关键字参数:
定义:可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。在调用函数时,可以只传入必选参数:作用:扩展函数的功能
特征:**kw
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) 案例一: person('Michael', 30) name: Michael age: 30 other: {} 案例二: #定义一个字典数据 dictArray = {'city': 'Beijing', 'job': 'Engineer'} #调用函数 person('Jack', 24, **dictArray ) #输出结果 name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} >解释说明: **dictArray表示把dictArray这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict。
注意kw获得的dict是dictArray的一份拷贝,对kw的改动不会影响到函数外的dictArray。
Num05–>命名关键字参数:
定义:只接收city和job的参数,其他,不接收。
def person(name, age, *, city, job):
print(name, age, city, job)作用:限制要传入的参数的名字,只能传我已命名关键字参数。
特征:命名关键字参数需要一个特殊分隔符*,而后面的参数被视为命名关键字参数。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job): print(name, age, args, city, job)
Num06–>参数组合:
定义:把以上的五种参数组合在一起的参数组合
排放顺序:在Python中定义函数,可以用必选参数、默认参数、可变参数、命名关键字参数和关键字参数,这5种参数都可以组合使用。但是请注意,
参数定义的顺序必须是:必选参数–>默认参数–>可变参数–>命名关键字参数–>关键字参数
以下案例加以说明:
比如定义一个函数,包含若干种参数:
def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
>>> f1(1, 2) a = 1 b = 2 c = 0 args = () kw = {} >>> f1(1, 2, c=3) a = 1 b = 2 c = 3 args = () kw = {} >>> f1(1, 2, 3, 'a', 'b') a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} >>> f1(1, 2, 3, 'a', 'b', x=99) a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} >>> f2(1, 2, d=99, ext=None) a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
最神奇的是通过一个tuple和dict,你也可以调用上述函数:
>>> args = (1, 2, 3, 4) >>> kw = {'d': 99, 'x': '#'} >>> f1(*args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} >>> args = (1, 2, 3) >>> kw = {'d': 88, 'x': '#'} >>> f2(*args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
最后小结:
1,Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。 2,默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误! 3,要注意定义可变参数和关键字参数的语法: *args是可变参数,args接收的是一个tuple; **kw是关键字参数,kw接收的是一个dict。 4,以及调用函数时如何传入可变参数和关键字参数的语法: 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3)); 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。 5,使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。 6,命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。 7,定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
函数——返回值 (返回函数的执行结果,代表一个函数的终止,只要有它,无论后面有多少代码都不执行)
函数其实就是把一堆代码包装起来,然后再调用,可以是列表,字典等等,那么这堆代码可以做很多事情,比如可以发邮件,如果发邮件成功了,那么就是说
调用函数成功,接着还有别的代码往下走,如果不成功,则干其他的什么事情。
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的
定义:函数外部的代码要想获取函数的执行结果,就可以再函数里用return语句把结果返回,就是说谁调用的这个函数就返回给谁,下面是一个学员注册的例子:
记住,函数有且只能返回一个值,如果输入多个值,函数会把他变成一个元祖,但还是会当成一个元素返回
注意:
1.函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so也可以理解为return语句代表着函数的结束
2.如果未在函数中指定return,那这个函数的返回值为None
函数——全局与局部变量
就像上面提到的,函数里边被修改了,而外面并没有被修改,这就涉及到了局部变量。
全局变量:定义在函数外部以一级代码(不能再缩进)的变量,全局能用
局部变量(当调用完函数,函数里边的变量什么的东西都会结束,函数内部可以调用全局变量,如果全局和局部都有
一个变量,则函数查找变量的顺序是由内而外的):在函数里边定义的变量就叫局部变量
,就是说
和
两个name 是
完全独立的,我们可以通过看内存地址看他们的区别:
那么python解释器会自动的找 中的name变量,而不是找函数中的变量,
在函数里边改变量不是改变量,而是赋值了一个新的。
那么能不能再函数里边直接调用?
这是外面不能调用里边的:
按照逻辑来讲,应该是局部再全局,但是我们发现,他并没有打印name = ‘rain’,仍然是‘Black_girl’,所以其实他们是两个独立的小房子,
应该是先局部后全局。
函数——在函数里边修改列表数据
所以重新赋值时不可以的,但是修改列表里的元素却可以,相当于,函数调用的是names的整体元素,但是里边的元素是单独的,可以修改。
其实字典,列表,集合,对象,类,包含列表的元祖这类的数据类型都是可以修改的。但是,字符串,数字不能被修改。
(这是因为字符串和数字本身就是最小单元,不能被修改)
函数——在函数里边修改全局变量
1.如果非要再函数里边改全局变量,需要global关键字,(就是说如果要改names这个本身的内存地址,就需要global),本来的话,一旦声明,
就是重新创建一个,而不是修改。
其中,局部变量不能放global上边,(但是实际上不会用global,因为我们默认是不能再函数里修改局部变量的,会误导读代码的人)如下:
2.nonlocal关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,
这时就需要nonlocal关键字了
def outer():
count = 10
def inner():
nonlocal count
count = 20
print(count)
inner()
print(count)
outer()
#20
#20
3.总结
(1)变量查找顺序:LEGB,作用域局部>外层作用域>当前模块中的全局>python内置作用域;
(2)只有模块、类、及函数才能引入新作用域;
(3)对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,就会使用外部作用域的变量;
(4)内部作用域要修改外部作用域变量的值时,全局变量要使用global关键字,嵌套作用域变量要使用
nonlocal关键字。nonlocal是python3新增的关键字,有了这个 关键字,就能完美的实现闭包了。
函数——嵌套函数
题目一:
题目二:
题目三:
题目四:
要想实现拿到age=19,需要如下图所示:
题目五:
函数——作用域
python中,一个函数就是一个作用域,(和javascrip类似)局部变量放置再作用域里,而对于作用域,定义完成,作用域已经生成,作用域链向上查找。
而在C#, java中的作用域是一个{}
对于一个普通的函数返回值,如下,我们都知道执行结果:
而如果改成下面这样:
,(其中,函数名是可以被当作返回值的)
函数——匿名函数
匿名函数就是说定义一个函数不用名字,他的最重要的作用是和别的方法搭配使用。
作用:1.节省代码量
2.看着高级
作用一:
如图,打印出来匿名函数,是他的地址,但是他不能像普通函数一样直接在后面加(),那怎么调用呢?
其实我们可以给他赋一个变量名,然后再调用。如下:
所以匿名函数的语法是把多行语句合成了一行。
作用2:
其中lambda支持的最复杂的是三元运算:(如果是复杂的循环之类的,匿名函数是实现不了的)
下面是匿名函数和别的方法搭配使用的例子
接下来是用map()方法(这个方法就是说可以在里面放一个函数,然后这个列表里的每一个值都会交给这个函数当作参数去运行一次)实现这个功能。
函数——高阶函数
变量指向函数,函数的参数能接收变量,那么以一个函数就可以接收另一个函数作为参数,这种函数就称为高阶函数。
只需满足以下任意一个条件,即是高阶函数
- 接受一个或多个函数作为输入
- return 返回另外一个函数
解释:
#变量可以指向函数
f = lambda x : x*x
#为了调用这个匿名函数,就把变量f指向了这个函数
def calc(x):
return x*x
f = calc
f()#和上面的方法效果是一样的
#也就是说,变量不仅仅可以赋值字符串,数字,列表等,他还可以赋值函数等,什么都可以赋值。
#函数的参数能接收变量
def calc(x):
return x*x
n = 10
calc(n)
#那么就可以接收另一个函数作为参数
def func(x,y):
return x*y
def calc(x):
return x
n = func
calc(n)#相当于calc(func)
print(calc(func))
def add(x,y,f):
return f(x) + f(y)
res = add(3,-6,abs)
print(res)
函数——递归
递归与栈
递归就是再函数的执行过程中调用自己
def recursion(n): print(n) recursion(n+1) recursion(1)
执行结果:
1 2 3 4 ... 998
这是因为递归有一个限制,默认限制1000次,可以用sys模块表示并修改递归深度
import sys sys.getrecursionlimit() #可以得到默认的1000次 sys.setrecursionlimit(1500) #改为递归1500次 def recursion(n): print(n) recursion(n+1) recursion(1)
执行结果:
1 2 3 4 ... 1496 1497 1498
所以可以看到递归层次是可以限制的,那么为什么python要限制它的递归层次?
因为如果一直循环,每次执行下一个函数,上一个函数都没有结束(也就是说每次递归都要占用一个内存资源),如没有限制,很快就能把内存撑爆。
那么表面上,每次递归都要占用一个内存资源,所以会限制最大的递归次数,那么本质上的原因是什么呢?
函数的调用过程中,是通过栈实现的(栈是一种数据结构,再python中不存在),再python中,可以理解为一个弹匣,有大小限制,里边放一个一个的数据。其实函数在执行的过程中,里边的变量数据,函数代码,等等都是要占内存的,它就存在了栈里边,每次调用函数的时候,都会把函数压在像弹匣一样的栈里边,但栈是由大小限制的,如果放多了,就会导致栈溢出,就存不进去数据了。(再python中,它本身就封装了这些东西,所以不需要关心,但要明白实际上函数的执行过程就是通过栈实现的)。
题目一:把10不断除以2,直到不能除为止,打印每次结果
那么一种新的方法完成这个题目:
因为上面第二种方法是把函数一直调用很多次,如果换一个数,并不知道会调用多少次,所以我们可以让他自己动态的调用,就是说不自己再函数外面瞎写,而是在函数里边写。
对比高阶函数,是一个函数把另一个函数作为参数,我们可以把函数本身作为参数再调用一次
(超过了递归层次,这是因为他会一直递归下去,那么最里面一直循环,就会一直占内存,所以只能递归1000层,这是计算机防止对内存的消耗,有一个工具包sys,效果也是这样的)
递归执行过程分析
就像上面的结果,我们虽然实现了用递归得到我的结果,但她还是会一直调用,没有终止,也就是没有终止条件。
她其实是一层一层退出来的(这个可以通过断点严谨的看出来)
题目二:(阶乘)
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))
题目三:(斐波那契数列)
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))
print(fibo_new(30000))#maximum recursion depth exceeded in comparison
递归的返回值
题目:给一个数,比如188,还是一直整除,要求除5次后返回这个结果的值(面试可能用得到)
升级:
递归特性总结及练习题
递归特性:
1.必须要有一个明确的结束条件
2.每次进入更深一层的递归时,问题规模相比上次递归都应有所减少
3.递归效率不高,递归层次过多会导致栈溢出(再计算机中。函数调用时通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,函数就会减少一层栈帧,由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲:htttp://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
递归函数实际应用案例,二分查找
data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35]
def binary_search(dataset,find_num):
print(dataset)
if len(dataset) >1:
mid = int(len(dataset)/2)
if dataset[mid] == find_num: #find it
print("找到数字",dataset[mid])
elif dataset[mid] > find_num :# 找的数在mid左面
print("\033[31;1m找的数在mid[%s]左面\033[0m" % dataset[mid])
return binary_search(dataset[0:mid], find_num)
else:# 找的数在mid右面
print("\033[32;1m找的数在mid[%s]右面\033[0m" % dataset[mid])
return binary_search(dataset[mid+1:],find_num)
else:
if dataset[0] == find_num: #find it
print("找到数字啦",dataset[0])
else:
print("没的分了,要找的数字[%s]不在列表里" % find_num)
binary_search(data,66)
执行结果:
函数——内置方法
引子:函数可以调用是因为提前定义好了,相应的,类似len()之所以能调用,也是因为提前被定义了,这就是内置函数,内置方法,再python中,
有好几十种函数内置方法,再python解释器一启动的时候,这些内置函数就已经被定义好了,只需要加()调用就好了。
abs()#取绝对值
dict()#把一个数据转成字典
help()#帮助
min()#再同一个列表里边,取最小的值
max()#取最大值
#a = [1,2,4,-1,-5,3]
#min(a)
#>>>-5
#max(a)
#>>>4
bool()#判断是否为bool
#bool(0)
#>>>False
#bool(1)
#>>>True
#bool(-1)
#>>>True
all()#判断列表里边是否全为bool,如果是,返回True,不然False,其中一种特殊情况是空列表仍然返回True
#a = [1,2,4,-1,-5,3]
#all(a)
#>>>True
#a = [1,2,4,-1,-5,3,0]
#>>>False
any()#判断列表里边只要有一个是bool,就返回True,可以很快的判断哪些数据是True
#any([False,0])
#>>>False
#any([False,0,1])
#>>>True
dir()#打印当前程序的所有变量
#a = [1,2,4,-1,-5,3,0]
#>>>['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a']
#只有a是自己定义的变量,其他的都是python解释器自带的,可以直接调用
#比如,__name__
#>>>'__main__'
vars()#打印变量对应的变量名儿和变量值
#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 'alex'}
locals()#再函数里边运行,打印函数的局部变量
#def f():
# n =3
# print(locals())
#f()
#>>>{'n': 3}
globals()#打印全局变量
#>>>{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
#'a': 'alex', 'f': <function f at 0x00000249501326A8>}
hex()#判断一个数是否是16进制
#hex(12)
#>>>'0xc'
slice()#切片,先定义一个列表,然后定义好一个切片的规则,再直接放进来。
#s = slice(1,7,2)
#l = list(range(10))
#正常切片是这样的:
#l[1:3]
#>>>[1,2]
#内置方法:
#l[s]
#>>>[1,3,5]
divmod()#输入两个数,取整除,结果是divmod(x//y, x%y)
#divmod(8,4)
#>>>(2,0)
sorted()#排序
#l = [1,4,3,-2,9,5]
#sorted(l)
#>>>[-2, 1, 3, 4, 5, 9]
#以字典为例
#d = {}
#for i in range(20):
# d[i] = i-50
#d
#>>>{0: -50, 1: -49, 2: -48, 3: -47, 4: -46, 5: -45, 6: -44, 7: -43, 8: -42, 9: -41, 10: -40, 11: -39, 12: -38,
#13: -37, 14: -36, 15: -35, 16: -34, 17: -33, 18: -32, 19: -31}
#看起来是有序的,但字典是无序的,数据量多了就会看出来,所以要把字典变成列表再排序
#d.items()
#>>>dict_items([(0, -50), (1, -49), (2, -48), (3, -47), (4, -46), (5, -45), (6, -44), (7, -43), (8, -42), (9, -41),
# (10, -40), (11, -39), (12, -38), (13, -37), (14, -36), (15, -35), (16, -34), (17, -33), (18, -32), (19, -31)])
#现在不按key排序,而是按照value排序,从小大到排
#sorted(d.items())
#>>>[(0, -50), (1, -49), (2, -48), (3, -47), (4, -46), (5, -45), (6, -44), (7, -43), (8, -42), (9, -41), (10, -40),
# (11, -39), (12, -38), (13, -37), (14, -36), (15, -35), (16, -34), (17, -33), (18, -32), (19, -31)]
#那怎么检查上面的排序是正确的呢?
#sorted(d.items(),key=lambda x:x[1]) lambda就是把items里的每一个元素交给x,以value排序就是x[1]
#>>>[(0, -50), (1, -49), (2, -48), (3, -47), (4, -46), (5, -45), (6, -44), (7, -43), (8, -42), (9, -41), (10, -40),
# (11, -39), (12, -38), (13, -37), (14, -36), (15, -35), (16, -34), (17, -33), (18, -32), (19, -31)]
#其实已经默认按照从小到大排序了,下面换掉字典中的一个值试验一下
#d[0]=399
#>>>{0: 399, 1: -49, 2: -48, 3: -47, 4: -46, 5: -45, 6: -44, 7: -43, 8: -42, 9: -41, 10: -40, 11: -39, 12: -38,
# 13: -37, 14: -36, 15: -35, 16: -34, 17: -33, 18: -32, 19: -31}
#sorted(d.items(),key=lambda x:x[1])
#>>>[(1, -49), (2, -48), (3, -47), (4, -46), (5, -45), (6, -44), (7, -43), (8, -42), (9, -41), (10, -40), (11, -39),
#(12, -38), (13, -37), (14, -36), (15, -35), (16, -34), (17, -33), (18, -32), (19, -31), (0, 399)]
#还可以用reverse倒序排
#sorted(d.items(),key=lambda x:x[1],reverse=True)
#>>>[(0, 399), (19, -31), (18, -32), (17, -33), (16, -34), (15, -35), (14, -36), (13, -37), (12, -38), (11, -39),
#(10, -40), (9, -41), (8, -42), (7, -43), (6, -44), (5, -45), (4, -46), (3, -47), (2, -48), (1, -49)]
#所以sorted可以自定义一些排序的规则
enumerate##n = ['alex','join','rain']
#for i in enumerate(n):
# print(i)
#(0, 'alex')
#(1, 'join')
#(2, 'rain')
eval()#把字符串转成代码执行,而且只能处理单行代码,否则会报错
#f = '1+3/2'
#eval(f)
#>>>2.5
#eval(print'hello world')
#>>>hello world
exec#把字符串转成代码执行,而且能处理复杂代码
#n = '''
# if 3>5:
# print('aaaa')
# else:
# print('bbbb')
#>>>bbbb
#两个的区别是exec拿不到返回值
#res = exec('1+2+3')
#res2 = eval('1+2+3')
#print('res',res,res2)
#res None 6
ord()#
#ord('a')
#>>>97
#chr(97)
#>>>'a'
sum()#求和
#n = [1,2,4,-1]
#sum(n)
#s = 'abcd路飞'#元内存地址修改,不是复制,首先字符串是不能被修改的,看起来是修改,其实是开辟了一个新的内存地址,比如有一个500M的字符串传进来,改完成1G.
#s1 = s.encode('utf-8')
#print(s)
#b'abcd\xe8\xb7\xaf\xe9\xa3\x9e'
#s2=bytearray(s1)
#bytearray(b'abcd\xe8\xb7\xaf\xe9\xa3\x9e')
#s2[1]=65
#bytearray(b'Abcd\xe8\xb7\xaf\xe9\xa3\x9e')
#验证他的地址没有变化
#s=bytearray(b'Abcd\xe8\xb7\xaf\xe9\xa3\x9e')
#id(s)
#>>>3052733164880
#s[0]=67,print(s)
#s=bytearray(b'Bbcd\xe8\xb7\xaf\xe9\xa3\x9e')
#id(s)
#>>>3052733164880
#所以大字节修改成byte,大字符串修改成列表
map()#列表元素自乘
#list(map(lambda x: x*x, [1,2,3,4,5]))
#[1, 4, 9, 16, 25]
filter()#和map一样,先写一个lambda函数,后面写一个列表,但是filter是过滤的意思,就是取符合自己需求的的值
#list(filter(lambda x: x>3,[1,2,3,4,5]))
#[4,5]
functools.reduce()
#functools.reduce(lambda x,y: x+y,[1,4,5555,23])
#>>>5583
#functools.reduce(lambda x,y: x*y,[1,4,5555,23])
#>>>511066
#就是说,两个数两个数的运算,累乘或累加,这是和sum的区别
pow()#求多少次方,也就是返回多少次幂
print()#print不只是打印,还有一些小技巧
#end=''
#s='alex is her ter\n but not my ter'
#print(s,end='.....')
#alex is her ter
#but not my ter...>>>#打印后会自动换行,并且句尾加自己要加的内容
#sep()#起到分隔符的作用
#print()还可以直接把数据打印到文件里边
callable#判断变量是否是函数,是的话返回True,不然返回False
#def f():
# pass
#callable(f)
#True
format#格式化
#print('{0}+{1}={2}'.format(1,2,1+2))
#1+2=3
#forzenset() #让集合变得不可变
#s={12,3,4,4}
#s.discard(3)#删除一个值
#{12,4}
#s = frozenset(s)
#print(s)
#frozenset({12, 4})#此时已经时不可变集合
zip()#把列表整合成一个大列表
# a=[1,2,3,4,5] ,b=['a','b','c']
#list(zip(a,b))
#>>>[(1, 'a'), (2, 'b'), (3, 'c')]
#他只是一一对应,4,5没有就丢弃了
round()#保留几位小数
#round(1.234,1)
#>>>1
#round(1.234,2)
#>>>1.23
has()#把字符串变成数字
#hash('abc')
#>>>1831154042308444573
set()#把列表变成集合
函数式编程
学会了上面几个重要的函数后,我们就可以来聊一聊函数式编程到底是个什么鬼
一 概念(函数式编程)
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。
而函数式编程是面向数学的抽象,将计算描述为一种 表达式求值,一句话,函数式程序就是一个 表达式。
函数式编程中的 函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。
纯函数式编程语言中的 变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是 不可变的 (immutable),也
函数式语言的如条件语句,循环语句也不是命令式编程语言中的 控制语句,而是函数的语法糖,比如在Scala语言中, if else不是语句而是三元运算符,是有返回值的。
严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。
二.
实例1:
假如,现在你来到 baidu面试,面试官让你把number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]中的正数的平均值,你肯定可以写出:#计算数组中正整数的平均值
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的数进行累加,最后求取平均值。
这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。
这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
那么,不用这种方式如何做到呢?
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
这段代码最终达到的目的同样是求取正数平均值,但是它得到结果的方式和 之前有着本质的差别:通过描述一个列表->正数平均值 的映射,
而不是描述“从列表得到正数平均值应该怎样做”来达到目的。
实例2:求阶乘
通过Reduce函数加lambda表达式式实现阶乘是如何简单:
from functools import reduce
print (reduce(lambda x,y: x*y, range(1,6)))
实例3:map()函数加上lambda表达式(匿名函数)可以实现更强大的功能:
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)。
练习:
1,简述普通参数,指定参数,默认参数,动态参数的区别
普通参数:以正确的顺序传入函数,调用时数量必须和声明的一样
指定参数:参数和函数调用关系密切,函数调用使用关键字参数来确定传入的参数值,参数
允许函数调用时参数的顺序和声明时不一致
默认参数:函数进行调用时,如果没有新的参数传入则默认的情况下,就调用默认参数
动态参数:个别函数能处理比当初声明时更多的参数,这些参数就动态参数
2,写函数,计算传入的字符串中数字,字母,空格,以及其他的个数
def func(s):
al_num =0
space_num = 0
digit_num = 0
others_num = 0
for i in s:
if i.isdigit():
digit_num +=1
elif i.isspace():
space_num +=1
elif i.isalpha():
al_num +=1
else:
others_num +=1
return (al_num,space_num,digit_num,others_num)
result = func("asdsadjlk1212jdjakdk2 d d d d323233223下")
print(result)
result = func(" d d d d323233223下")
print(result)
3,写函数,判断用户传入的对象(字符串,列表,元组)长度是否大于5
def func(s,lis,tup):
zifuchuan = len(s)
liebiao = len(lis)
yuanzu = len(tup)
if zifuchuan>5:
print("大于5")
else:
print("小于5")
if liebiao >5:
print("大于5")
else:
print("小于5")
if yuanzu >5:
print("大于5")
else:
print("小于5")
return (zifuchuan,liebiao,yuanzu)
func('dadadad','[1,2,3]',{1,2,3})
4,写函数监测用户传入的对象(字符,列表,元组)的每一个元素是否有空内容
def func(n):
for i in a:
i = str(i)
if '' in i:
return ('空格: ',i)
else:
return ('没空格')
a = ('dasdsd dasd','ds')
res = func(a)
print(res)
5,写函数,检查传入列表的长度,如果大于2,那么仅仅保留前两个长度的内容,并将新内容返回给调用者
def func(li):
len_li = len(li)
if len_li>2:
print("列表长度大于2")
new_li = li[0:2]
return (new_li)
res = func([12,12,45,78,32,12])
print(res
6,写函数,检查获取传入列表或元组的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
def func(li,tup):
li = []
tup = []
for i in range(len(li)):
if i %2 ==1:
li.append(li[i])
print(li)
for j in range(len(tup)):
if j %2 ==1:
tup.append(tup[j])
print(tup)
return (li,tup)
res = func([1,2,3,4,5,6,7,8,9],(1,2,3,11,21,4,5,6,7))
print(res)
7.写函数,计算传入字符串中的【数字】、【字母】、【空格】和【其他】的个数
# 7,写函数,计算传入字符串中的【数字】、【字母】、【空格】和【其他】的个数
def func(strr):
digit_number = 0
space_number = 0
alpha_number = 0
else_number = 0
for i in strr:
if i.isdigit():
digit_number +=1
elif i.isspace():
space_number +=1
elif i.isalpha():
alpha_number +=1
else:
else_number +=1
return ("数字,空格,字母,其他内容分别有:",(digit_number,space_number,alpha_number,else_number))
res = func('sda!@#$%^&1234567dfghj da da ')
print(res)
# ('数字,空格,字母,其他内容分别有:', (7, 3, 12, 7))