一 函数知识体系
什么是函数?
为什么要用函数?
函数的分类:内置函数与自定义函数
如何自定义函数
语法
定义有参数函数,及有参函数的应用场景
定义无参数函数,及无参函数的应用场景
定义空函数,及空函数的应用场景
调用函数
如何调用函数
函数的返回值
函数参数的应用:形参和实参,位置参数,关键字参数,默认参数,*args,**kwargs
高阶函数(函数对象)
函数嵌套
作用域与名称空间
装饰器
迭代器与生成器及协程函数
三元运算,列表解析、生成器表达式
函数的递归调用
内置函数
面向过程编程与函数式编程
二 函数基础
一 引子
1 1 什么是函数 2 函数1就是具备某一功能的工具 3 4 5 2 为什么要用函数 6 1 程序的组织结构不清晰,可读性差 7 2 代码冗余 8 3 可扩展性差 9 10 3 如何用函数 11 函数的使用必须遵循的原则:先定义,后调用 12 修理工事先准备工具的过程即函数的定义 13 修理工遇到应用场景拿来工具就用即函数的调用 14 15 语法: 16 def 函数名(参数1,参数2,...): 17 """ 18 文档注释 19 """ 20 code1 21 code2 22 code3 23 ...... 24 return 返回值 25 26 def:定义函数的关键字 27 函数名: 就相当于一个变量名,指向函数的内存地址, 28 注意:函数的内存地址()就可以出发函数体代码的执行 29 30 参数: 参数是函数的调用者为函数体代码传值的媒介,在python中函数的参数无需声明类型 31 """文档注释""" : 推荐写上 32 代码块:就是函数体功能的具体实现 33 return 返回值 :函数体代码块运行的成果 34 35 36 函数的使用分为两个阶段: 37 定义阶段: 只检测语法,不执行代码 38 调用阶段:运行函数体代码 39 def foo(): 40 xxx 41 print()
二 定义函数
三 调用函数
函数的调用:函数名加括号 1 先找到名字 2 根据名字调用代码
无return->None return 1个值->返回1个值 return 逗号分隔多个值->元组
四 函数的参数
#形参即变量名,实参即变量值,函数调用时,将值绑定到变量名上,函数调用结束,解除绑定
#1、位置参数:按照从左到右的顺序定义的参数 位置形参:必选参数 位置实参:按照位置给形参传值
总结:依位传值
#2、关键字参数:按照key=value的形式定义的实参 无需按照位置为形参传值 注意的问题: 1. 关键字实参必须在位置实参右面 2. 对同一个形参不能重复传值
总结:依关键词传值
#3、默认参数:形参在定义时就已经为其赋值 可以传值也可以不传值,经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参) 注意的问题: 1. 只在定义时赋值一次 2. 默认参数的定义应该在位置形参右面 3. 默认参数通常应该定义成不可变类型
总结:选择传值
#4、可变长参数: 可变长指的是实参值的个数不固定 而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs
总结:*元组与位置参数转换,**字典与关键字参数转换
#5、命名关键字参数:
*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递可以保证,传入的参数中一定包含某些关键字
总结:限制关键词传值
五 练习题
三 函数对象、函数嵌套、名称空间与作用域、装饰器
一 函数对象
#1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素
利用该特性,优雅的取代多分支的if
1 def foo(): 2 print('foo') 3 4 def bar(): 5 print('bar') 6 7 dic={ 8 'foo':foo, 9 'bar':bar, 10 } 11 while True: 12 choice=input('>>: ').strip() 13 if choice in dic: 14 dic[choice]()
二 函数嵌套
三 名称空间与作用域
#名称空间:存放名字的地方,三种名称空间,(之前遗留的问题x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)
python test.py #1、python解释器先启动,因而首先加载的是:内置名称空间 #2、执行test.py文件,然后以文件为基础,加载全局名称空间 #3、在执行文件的过程中如果调用函数,则临时产生局部名称空间
名字的查找顺序
局部名称空间--->全局名称空间--->内置名称空间
1 #1、作用域即范围 2 - 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效 3 - 局部范围(局部名称空间属于该范围):临时存活,局部有效 4 #2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下 5 x=1 6 def f1(): 7 def f2(): 8 print(x) 9 return f2 10 x=100 11 def f3(func): 12 x=2 13 func() 14 x=10000 15 f3(f1()) 16 17 #3、查看作用域:globals(),locals() 18 19 20 LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__ 21 locals 是函数内的名字空间,包括局部变量和形参 22 enclosing 外部嵌套函数的名字空间(闭包中常见) 23 globals 全局变量,函数定义所在模块的名字空间 24 builtins 内置模块的名字空间
1 #global:在局部声明一个名字是来自于全局作用域的,可以用来在局部修改全局的不可变类型 2 # x=1 3 # def foo(): 4 # global x 5 # x=2 6 # 7 # foo() 8 # print(x) 9 10 #nonlocal:声明一个名字是来自于当前层外一层作用域的,可以用来在局部修改外层函数的不可变类型 11 x=0 12 def f1(): 13 x=111 14 def f2(): 15 # global x 16 nonlocal x 17 x=222 18 f2() 19 print(x) 20 21 f1() 22 # print(x)
四 闭包函数
闭包的意义与应用
1 #闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域 2 #应用领域:延迟计算(原来我们是传参,现在我们是包起来) 3 from urllib.request import urlopen 4 5 def index(url): 6 def get(): 7 return urlopen(url).read() 8 return get 9 10 baidu=index('http://www.baidu.com') 11 print(baidu().decode('utf-8'))
1 # 为函数体传值的方式一:参数 2 # def func(x,y): 3 # print(x+y) 4 # 5 # func(1,2) 6 7 # 为函数体传值的方式二:闭包 8 # def outter(x,y): 9 # # x=1 10 # # y=2 11 # def func(): 12 # print(x+y) 13 # return func 14 # 15 # f=outter(1,2) 16 # f() 17 # f() 18 # f()
五 装饰器
装饰器的依据是闭包函数,通过复数闭包实现在不改变原函数的情况下插入操作的目的,即装饰。
1 无参装饰器: 2 def outter(func): 3 def wrapper(*args,**kwargs): 4 前部操作 5 res=func(*args,**kwargs) 6 后部操作 7 return res 8 return wrapper
# 有参装饰器的模板 def outter1(x,y,z): def outter2(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrapper return outter2
装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。 强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式 装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能
wraps补充:
functools.wraps 的作用是将原函数对象的指定属性复制给包装函数对象, 默认有 module、name、doc,或者通过参数选择。
1 from functools import wraps 2 3 def deco(func): 4 @wraps(func) #加在最内层函数正上方 5 def wrapper(*args,**kwargs): 6 return func(*args,**kwargs) 7 return wrapper 8 9 @deco 10 def index(): 11 '''哈哈哈哈''' 12 print('from index') 13 14 print(index.__doc__)
六 练习题
四 迭代器、生成器、面向过程编程
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器 #2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象? 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象
即:iter() 函数用来生成迭代器。
next() 返回迭代器的下一个项目。
1 dic={'a':1,'b':2,'c':3} 2 iter_dic=dic.__iter__() 3 while 1: 4 try: 5 k=next(iter_dic) 6 print(dic[k]) 7 except StopIteration: 8 break 9 等于 10 #基于for循环,我们可以完全不再依赖索引去取值了 11 dic={'a':1,'b':2,'c':3} 12 for k in dic: 13 print(dic[k]) 14 15 #for循环的工作原理 16 #1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic 17 #2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码 18 #3: 重复过程2,直到捕捉到异常StopIteration,结束循环
生成器:yield
1 def func(): 2 print('====>first') 3 yield 4 print('====>second') 5 yield 6 print('====>third') 7 yield 8 print('====>end') 9 10 f=func() 11 #f=f.__iter__() 12 print(next(f)) 13 print(next(f)) 14 15 C:\Users\王康杰\AppData\Local\Programs\Python\Python36-32\python.exe C:/Users/王康杰/PycharmProjects/python博客/8-2.py 16 ====>first 17 None 18 ====>second 19 None 20 21 进程完成,退出码 0
1 def func(): 2 print('====>first') 3 yield 1 4 print('====>second') 5 yield 2 6 print('====>third') 7 yield 3 8 print('====>end') 9 10 f=func() 11 #f=f.__iter__() 12 print(next(f)) 13 print(next(f)) 14 15 C:\Users\王康杰\AppData\Local\Programs\Python\Python36-32\python.exe C:/Users/王康杰/PycharmProjects/python博客/8-2.py 16 ====>first 17 1 18 ====>second 19 2 20 21 进程完成,退出码 0
五 三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数
三元表达式
1 name=input('姓名>>: ') 2 res='SB' if name == 'alex' else 'NB' 3 print(res)
列表推导式
1 #2、语法 2 [expression for item1 in iterable1 if condition1 3 for item2 in iterable2 if condition2 4 ... 5 for itemN in iterableN if conditionN 6 ]
生成器表达式
1 #1、把列表推导式的[]换成()就是生成器表达式 2 3 #2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性 4 >>> chicken=('鸡蛋%s' %i for i in range(5)) 5 >>> chicken 6 <generator object <genexpr> at 0x10143f200> 7 >>> next(chicken) 8 '鸡蛋0' 9 >>> list(chicken) #因chicken可迭代,因而可以转成列表 10 ['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',] 11 12 #3、优点:省内存,一次只产生一个值在内存中
递归
1、递归调用应该包含两个明确的阶段:回溯,递推 回溯就是从外向里一层一层递归调用下去, 回溯阶段必须要有一个明确地结束条件,每进入下一次递归时,问题的规模都应该有所减少(否则,单纯地重复调用自身是毫无意义的) 递推就是从里向外一层一层结束递归
2、
1 #总结递归的使用: 2 1. 必须有一个明确的结束条件 3 4 2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少 5 6 3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
匿名函数
lambda
内置函数
abs()求绝对值函数
all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。
any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。
# print(any([0,'',None,False]))
# bin 10——》2
# oct 10——》8
# hex 10——》16
bool() 函数用于将给定参数转换为布尔类型,如果没有参数,返回 False。
bool 是 int 的子类。
# res='你'.encode('utf-8')
# res='你'.encode('utf-8')
# res=bytes('你',encoding='utf-8')
# print(res,type(res))
# print(callable(len))
# print(chr(65))
# print(chr(90))
#
# print(chr(97))
# print(chr(122))
#
ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值,如果所给的 Unicode 字符超出了你的 Python 定义范围,则会引发一个 TypeError 的异常。
# import time
# print(dir(time))
python divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。
# print(divmod(3003,20))
# for i in enumerate(['a','b','c']):
# print(i)
# res=eval('[1,2,3]')
# print(res,type(res))
# s=set({1,2,3})
# s.add(4)
# print(s)
# fs=frozenset({1,2,3}) frozenset() 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。
函数是计算x的y次方,如果z在存在,则再对结果进行取模,其结果等效于pow(x,y) %z
注意:pow() 通过内置的方法直接调用,内置方法会把参数作为整型,而 math 模块则会把参数转换为 float。
# print(pow(2,3,3)) #(2**3)%3
# l=[1,'a','3',3]
# l1=reversed(l)函数返回一个反转的迭代器。
# print(list(l1))
# print(round(3.5))round(数值,保留小数位数) 方法返回浮点数x的四舍五入值。
#
print(round(5656.3657,-2))
5700
l=['a','b','c','d','e']
s='helloworld'
slice() 函数实现切片对象,主要用在切片操作函数里的参数传递。
class slice(stop) class slice(start, stop[, step])
- start -- 起始位置
- stop -- 结束位置
- step -- 间距
obj=slice(1,5,2)
# print(l[1:5:2])
# print(l[obj])
# print(s[1:5:2])
# print(s[obj])
# print(vars() is locals())vars() 函数返回对象object的属性和属性值的字典对象。
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存
# l=[1,2,3,4,5,6,7]
# s='hello'
#
# res=zip(l,s)
# print(list(res))
__import__() 函数用于动态加载类和函数 。
如果一个模块经常变化就可以使用 __import__() 来动态载入。
# import 'time'
m=__import__('time')
print(m.time())
# 面向对象
object.__dict__
classmethod
staticmethod
property
delattr
hasattr
getattr getattr() 函数用于返回一个对象属性值。
setattr setattr 函数对应函数 getatt(),用于设置属性值,该属性必须存在
isinstance
issubclass
object
super
eval() 函数用来执行一个字符串表达式,并返回表达式的值。
1 >>>x = 7 2 >>> eval( '3 * x' ) 3 21 4 >>> eval('pow(2,2)') 5 4 6 >>> eval('2 + 2') 7 4 8 >>> n=81 9 >>> eval("n + 4") 10 85
exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。
1 >>>exec 'print "Hello World"' 2 Hello World 3 # 单行语句字符串 4 >>> exec "print 'runoob.com'" 5 runoob.com 6 7 # 多行语句字符串 8 >>> exec """for i in range(5): 9 ... print "iter time: %d" % i 10 ... """ 11 iter time: 0 12 iter time: 1 13 iter time: 2 14 iter time: 3 15 iter time: 4
1 x = 10 2 expr = """ 3 z = 30 4 sum = x + y + z 5 print(sum) 6 """ 7 def func(): 8 y = 20 9 exec(expr) 10 exec(expr, {'x': 1, 'y': 2}) 11 exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4}) 12 13 func()
六 函数草稿