函数
一、函数的好处
解决代码冗余,增强代码复用
保持一致性,增强可维护性
增强可读性与可扩展性
二、函数定义和调用
def 函数名(arg1,arg2,arg3……):
‘描述信息’->print(foo.__doc__)可以打印出描述信息foo function
函数体
return (任意数据类型)
1、定义无参函数
def foo():
'foo function'
print('from the foo')
#可以打印出描述信息foo function
print(foo.__doc__)
2、定义有参函数
def foo(x,y):
res = x+y
return res
3、定义空函数
#空函数的作用:搭建程序整体结构
def foo():
pass
4、函数的调用
- 定义时无参,调用时无参
- 定义时有参,调用时必须有参
5、函数调用形式:
- 语句形式->无参数函数调用:foo()
- 表达式->有参数函数调用:res=bar(1,2)
- 例子:递归调用->bar(bar(1,2),3)
三、函数和过程
过程定义:过程就是简单特殊没有返回值的函数
这么看来我们在讨论为何使用函数的的时候引入的函数,都没有返回值,没有返回值就是过程
总结:
当一个函数/过程没有使用return显示的定义返回值时,python解释器会隐式的返回None
返回值数=0:返回None
返回值数=1:返回object
返回值数>1:返回tuple
四、函数的返回值
return有三种情况:
不写return():默认返回None
return()一个值:
def my_max(x,y):
res = x if x>y else y:
return(res)
- return()多个值:默认将结果包装成元组
总结:
返回值可以是任意类型
不写return()—->none
return 1 —–>1
return 1,2,3 —–>(1,2,3)
函数只能执行一次return
*变量的解压:(针对序列都是可以的)
需求:x= ‘hello’取第一个和最后一个值
x,y,z,m,n = ‘hello’
则x,y,z,m,n分别代表h,e,l,l,o
a,_,_,_,e = [1,2,3,4,5]
a,*_,e=[1,2,3,4,5]
输出a,e即为1,5
五、函数参数
1、函数的参数介绍
形参:在定义函数时写的参数,形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
实参:在调用函数是写的参数,实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
注意:
在调用函数的时候就相当于x=1,y=2,而且这种绑定关系只在函数内部有效
在定义函数的时候变量只和函数内部有关,不要和外部变量有关系
*扩展
在定义函数的时候就写明参数类型和返回值类型(但是实际上是不能严格限制的)
def my_sum(x:int,y:int)->int:
print(x+y)
print(my_sum.__annmotations__)
2、参数的传值与定义
实参的位置传值和关键字传值(标准调用:实参与形参位置一一对应;关键字调用:位置无需固定)
实参三种传值形式:
- 按位置传值:foo(1,2)——>x=1,y=2
- 按关键字传值:foo(y=2,x=1)——>x=1,y=2
-
按位置与按关键字混用:foo(1,y=2)——>x=1,y=2
- 按位置传递值必须在按关键字传递值的前面
- 对于一个形参只能赋值一次
形参的位置参数与默认参数:
- 位置参数:必须传值的参数
-
默认参数:定义的时候就有值,如果调用的时候传值了就用调用时候传递的,如果调用阶段没传就用默认的
- 默认参数必须放在位置参数的后面
3、可变参数:*args、**kwargs
1、*args:
实参的角度:把按位置传值多余的值统一交给处理,*会将多余的值保存成元组的形式
# *args要放在x后面
def foo(x,*args):
print(x)
print(args)
foo(1,2,3,4,5,6,'a')
>>>1
>>>(2,3,4,5,6,'a')
2、**kwargs
实参的角度:**会把按关键字传值的多余的值统一交给**处理,**会将多余的值保存成字典的形式
def foo(x, **kwargs):
print(x)
print(kwargs)
foo(1,y=2,a=3,b=4)
>>>1
>>>{'a': 3, 'b': 4, 'y': 2}
3、解释:只要遇见*就是将参数打散开来看
从形参的角度——对*的解释
#定义时形参用*args表示在函数调用的时候可以传递无限个数的参数,*args都会将它们统一保存成元组,foo(x,y,z……)
def foo(*args):
print(x)
print(y)
print(z)
……
从实参的角度——对*的解释
#见此部分标题所示:*就是代表将参数打散来看,在此例中,调用阶段foo(*(1,2,3))就可以看成foo(1,2,3),也就是*将(1,2,3)这个参数元组打散了,变成foo(1,2,3)之后就变为普通的实参中的位置参数,所以也就是x—>1,y—>2,z—>3
def foo(x,y,z):
print(x)
print(y)
print(z)
foo(*(1,2,3))
4、可变参数与不可变参数的混用
# 1、这样的情况y=1是不起作用的,y不会等于1,而是等于2,被传递的值覆盖了
def foo(x,y=1,*args):
print(x)
print(y)
print(args)
foo(1,2,3,4,5,6,'a')
>>>1
>>>2
>>>(3,4,5)
# 2、这样的情况y=1是起作用的,因为*args接收了除1外其他所有的实参,y是单独定义的,所以没有被覆盖这一说法
def foo(x,*args,y=1):
print(x)
print(y)
print(args)
foo(1,2,3,4,5,6,'a')
>>>1
>>>1
>>>(2,3,4,5,6,'a')
#3、混着用的位置问题:先不可变参数、再*args、再**kwargs
def foo(x, *args, **kwargs):
print(x)
print(args)
print(kwargs)
foo(1,y=2,z=3)
#4、以下形式可以接受任意形式的传参,按位置传值和按关键字传值都行,且传值的个数没有限制
def foo(*args, **kwargs):
print(args)
print(kwargs)
foo(1,y=2,z=3)
六、名称空间和作用域
1、名称空间分成三种
1、内置名称空间:解释器启动就有的,可以在任意位置引用
>>>import builtins
>>>dir(builtins)
>>>['ArithmeticError','AssertionError','AttributeError','BaseException','BlockingIOError','BrokenPipeError','BufferError','BytesWarning','ChildProcessError','ConnectionAbortedError','ConnectionError','ConnectionRefusedError','ConnectionResetError','DeprecationWarning','EOFError','Ellipsis','EnvironmentError','Exception','False','FileExistsError','FileNotFoundError','FloatingPointError','FutureWarning','GeneratorExit','IOError','ImportError','ImportWarning','IndentationError','IndexError','InterruptedError','IsADirectoryError','KeyError','KeyboardInterrupt','LookupError','MemoryError','NameError','None','NotADirectoryError','NotImplemented','NotImplementedError','OSError','OverflowError','PendingDeprecationWarning','PermissionError','ProcessLookupError','RecursionError','ReferenceError','ResourceWarning','RuntimeError','RuntimeWarning','StopAsyncIteration','StopIteration','SyntaxError','SyntaxWarning','SystemError','SystemExit','TabError','TimeoutError','True','TypeError','UnboundLocalError','UnicodeDecodeError','UnicodeEncodeError','UnicodeError','UnicodeTranslateError','UnicodeWarning','UserWarning','ValueError','Warning','WindowsError','ZeroDivisionError','__build_class__','__debug__','__doc__','__import__','__loader__','__name__','__package__','__spec__','abs','all','any','ascii','bin','bool','bytearray','bytes','callable','chr','classmethod','compile','complex','copyright','credits','delattr','dict','dir','divmod','enumerate','eval','exec','exit','filter','float','format','frozenset','getattr','globals','hasattr','hash','help','hex','id','input','int','isinstance','issubclass','iter','len','license','list','locals','map','max','memoryview','min','next','object','oct','open','ord','pow','print','property','quit','range','repr','reversed','round','set','setattr','slice','sorted','staticmethod','str','sum','super','tuple','type','vars','zip']
2、全局名称空间:
顶头定义的名字,也就是指全局中定义的变量名或者函数名;程序运行的时候生效,可以在定以后的任意位置引用。
3、局部名称空间:
在函数内部定义的名字,函数执行的时候生效,可以在函数内部引用。
2、作用域
先找局部作用域—>全局作用域—>内置作用域
Tips
print(globals()) 内置函数——查看全局名称空间
print(locals()) 内置函数——查看局部名称空间
七、函数的嵌套
1、函数的嵌套调用
#求两个数中的最大值
def my_max(x,y):
res=x if x > y else y
return res
print(my_max(10,100))
#求四个数中的最大值:
def my_max4(a,b,c,d):
res1=my_max(a,b)
res2=my_max(res1,c)
res3=my_max(res2,d)
return res3
print(my_max4(1,20,3,4))
2、函数的嵌套定义
#在外面不能调用f2()因为f2是在f1中定义的,相当于f1的局部定义的函数
def f1():
print("from f1")
def f2():
print("from f2")
def f3():
pass
八、闭包
1、函数是第一类对象的概念
名称空间与作用域的关系
全局作用域:内置名称空间、全局名称空间
局部作用域:局部名称空间
函数是第一类对象:函数可以被当做数据来传递,函数可以被赋值
#函数可以被当做参数传递
def bar(func):
print(func)
bar(foo) #打印出来就是foo的内存地址
bar(foo())
2、闭包
闭包函数:
首先必须是内部定义的函数,该函数包含对外部作用域而非全局作用域名字的引用
#作用域的关系是在定义阶段就定义好了,此例不以f()在全局调用就会返回全局的x=123,而是在定义的时候就定义好了x是f1的局部变量
x=123
def f1():
x=1
def f2():
print(x)
return f2
f=f1()
print(f)
#运行f()后结果是:1
f()
>>> 1
解释:参考下例
1、调用f()的时候不能知道f()的代码,而return的f2中还包含的了它作用域的引用
2、在return的f2中不仅包含了f2的功能,同时还包含的了它作用域的引用也就是x=2这个名字的引用闭包特性:
自带”干粮”,也就是自带x=2,而不受其他x的影响
#1、以下就不是闭包:
x=1
def f1():
def f2():
print(x)
#对外部作用域,而不是全局作用域的引用
#2、下面就是闭包
x=1
def f1():
x=2
def f2():
print(x)
return f2
f=f1()
f()
#结果是2,说明是闭包,引用了外部作用域
print(f.__closure__) #用__closure__来查看闭包中包含的元素
闭包的实际作用
#用以下来说明闭包在实际中的作用
#前提知识:
>>>import requests
>>>requests.get("www.baidu.com").text #返回页面源代码
or
>>>from urllib.request import urlopen
>>>urlopen("http://www.baidu.com").read()
#实际需求:爬取百度的页面
from urllib.request import urlopen
def get(url):
return urlopen(url).read()
print(get("http://www.baidu.com"))
#修改为闭包的形式如下:
#新需求:只爬百度的页面,也就是将百度的url保存下来
from urllib.request import urlopen
def get(url):
return urlopen(url).read()
def f1(url):
def f2():
print(urlopen(url).read())
return f2
f = f1("http://www.baidu.com")
f()
#f()执行之后返回的不光是f1自己还包括url地址,也就是返回了符f1()+url
闭包的精髓之处在于用户在想要爬取百度的网页时,不需要自己传参数,而只需要直接执行f()就可以了,简而言之,保存状态!
九、装饰器
1、装饰器的意义
开放封闭原则:
程序上线之后要尽量避免修改源代码,所以面对上线后的新需求就要利用装饰器来进行函数的功能增加什么是装饰器:
装饰器本身就是可调用对象,功能就是增加被装饰者的功能,且不修改被装饰者的源代码和调用方式
2、无参装饰器的简单实现
import time
def timmer(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func()
stop = time.time()
print("run time is %s" %(stop -start))
return wrapper
@timmer
def index():
time.sleep(3)
print("welcome to oldboy!")
#timmer就是装饰器,只要写了@decorate,就会将@decorate下面的一行的函数名当做参数传给装饰器,index = timmer(index)
3、有参装饰器实现
#需求:为index函数增加认证功能->根据用户认证的不同类型来进行不同操作,在auth外面包一层auth2,用闭包的方式传递一个auth_type,利用auth_type来进行判断区分
import time
def auth2(auth_type):
def auth(func):
def wrapper(*args, **kwargs):
if auth_type == "file":
name = input("username:>>>").strip()
password = input("password:>>>").strip()
if name == "yang" and password == "123"
print("auth successful !")
res = func(*args, **kwargs)
return res
else:
print("auth error!")
elif auth_type == "SQL":
print("用SQL")
return wrapper
return auth
@auth2(auth_type = "file") #@auth + auth_type =>index=auth(index) + auth_type
def index():
print("welcome to index page!")
@auth(auth_type = "SQL")
def home():
print("welcome to home page!")
index()
4、多个装饰器
#多个装饰器
@bbb
@aaa
def func():
pass
func = bbb(aaa(func))
#多个有参装饰器
@bbb("b")
@aaa("a")
def func():
pass
func = bbb("b")(aaa("a")(func))
十、迭代器
迭代器:一种不依赖索引遍历对象的工具
迭代器优点:
- 提供了一种不依赖索引的取值方式,这样可以遍历没有索引的可迭代对象,例如字典、集合、文件
- 迭代器更省内存
迭代器缺点:
- 无法获取迭代器的长度,使用不如列表索引取值灵活
- next()只能一次性执行,而且不能反向和重复
Tips
python解释器为数据类型内置一个__iter__方法,所以只要有这个方法就说明是的可迭代对象
可迭代对象:对象本身有__iter__方法,就说明其是可迭代的
d={“a”:1, “b”:2, “c”:3}
d.__iter__() => iter(d)i就是迭代器
i= d.__iter__()
迭代器本身又有一个内置的__next__方法
i.__next__()所以:
d={“a”:1, “b”:2, “c”:3}
i = d.__iter__()
while True:
print(i.__next__())d={“a”:1, “b”:2, “c”:3}
i = iter(d)
while True:
print(next(i))会抛出异常,所以:补充知识点—>异常捕捉
d={“a”:1, “b”:2, “c”:3}
i = iter(d)
while True:
try:
print(next(i))
except StopIteration:
breakd={“a”:1, “b”:2, “c”:3}
for k in d: # d.__iter__()
print(k)解释:
python的for循环,将in后面的对象首先调用器__iter__()方法再返回至对象中,所以实际遍历的其是就是迭代器,而且for循环
自动加上了异常处理机制,防止__next__()报异常错误查看可迭代对象与迭代器对象
from clooections import iterable,iterator
isinstance(i,iterator)
十一、生成器
1、生成器
生成器:将函数转化为迭代器,迭代器则是把数据类型转化为可迭代的生成器就是一个函数,函数内包含有yield关键字,就叫生成器。生成器本身就是迭代器。
def test():
print("first")
yield 1
g = test()
print(g)
#既然生成器本身就是迭代器,所以有next()方法
print(next(g))
2、yield
#每次运行生成器就是执行一次函数运行,并返回一个yield的值,如果有多个yield,则next一次取一个yield
def test():
print("one")
yield 1
print("two")
yield 2
print("three")
yield 3
g = test()
print(next(g))
>>>one
>>>1
print(next(g))
>>>two
>>>2
print(next(g))
>>>three
>>>3
#执行一次next就是在上一个yield的基础上继续向下执行,yield就是返回值
生成器yield与return 的区别?
- return只能返回一次值,函数就彻底结束了
- yield能返回多次值
yield作用?
- yield吧函数变成生成器—>迭代器
- return只能返回一次值,函数就彻底结束了,yield能返回多次值
- 函数在暂停以及继续下一次运行时的状态是由yield保存
3、生成器应用
1、惰性计算:自己控制循环值
def func():
while True:
yield n
n += 1
f = func()
print(next(f))
2、实现linux的tail读取文件新增内容功能
#原本功能介绍
import time
def tail(file_path):
with open(file_path, 'r') as f:
f.seek(0,2)
while True:
line = f.readline()
if not line:
time.sleep(0.3)
continue
else:
print(line,end='') #end=''表示打印完一行最后没有换行符
tail('/tmp/a,txt')
#用生成器改写上面的tail功能,并添加grep功能筛选带有error的文本
import time
#函数定义阶段
def tail(file_path):
with open(file_path, 'r') as f:
f.seek(0,2)
while True:
line = f.readline()
if not line:
time.sleep(0.3)
continue
else:
yield line
def grep(pattern, lines):
for line in lines:
if pattern in line:
print(''\033[45m%s\033[0m' %line)
#调用阶段
g = tail('/tmp/a.txt')
grep('error',g)
4、协程函数
协程函数:在函数当中yield是一个表达式来体现的,就叫协程函数
#如果函数中是通过表达式的形式来使用yield时,可以给yield传值
def eater(name):
print('%s start to eat food' %name)
while True:
food = yield
print('%s get %s to start eat' %(name,food))
print('done')
e = eater('alex')
next(e)
print(e.send('包子1')) #此处send会将'包子'传递给yield
print(e.send('包子2'))
print(e.send('包子3'))
print(e.send('包子4'))
#每次执行send都向yield传递值,一次一次的执行
5、为协程函数添加初始化装饰器
def init(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
next(res)
return res
return wrapper
@init #eater=init(eater)
def eater(name):
print('%s start to eat food' %name)
while True:
food = yield food_list
print('%s get %s to start eat' %(name,food))
food_list.append(food) #保存每次传递给yield的值
print('done')
e = eater('alex')
#next(e) 这一步被上面的装饰器做了
print(e.send('包子3'))
6、列表解析与生成器表达式
#列表生成式--传统方法:
egg_list = []
for i in range(100):
egg_list.append('egg%s' %i)
print(egg_list)
#列表生成式:
l = ['egg%s' %i for i in range(100)]
print(l)
#升级:
l = [1,2,3,4]
s = 'hello'
l_new = [(num,si) for num in l for s1 in s]
print(l_new)
#等同于:
l_new = []
for num in l :
for s1 in s:
t = (num,s1)
l_new.append(t)
print(l_new)
#生成器表达式:每次next生成一个值,节省内存
g = l = ('egg%s' %i for i in range(100))
print(next(g))
print(next(g))
print(next(g))
应用实例
#应用实例:将一个大文件的每行左右两端的空格删除掉
#传统方法:将文件的内容都读进内存中
f = open('a.txt')
l = []
for line in f:
line = line.strip()
l.append(line)
print(l)
#改写:用列表生成式的方式
f = open('a.txt')
g = (line.strip for line in f)
l = list(g) #若list后跟一个可迭代的对象,则就会将可迭代对象里的每一个值生成一个列表
print(l)
列表解析
#列表解析(列表生成式)
#声明式编程
l = [expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
for item3 in iterable3 if condition3
……
for itemN in iterableN if conditionN
]
#生成器表达式
g = (expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
for item3 in iterable3 if condition3
……
for itemN in iterableN if conditionN
)
十二、内置函数
abs():求绝对值
all():对所有课迭代参数进行布尔判断,全部为True则为True,否则为False
any():对所有课迭代参数进行布尔判断,任一为True则为True,全False为False
sum():求和
byes():将字符串转化为字节类型
str():转化为字符串
list():转化为列表
tuple():转化为元组
dict():转化为字典
set():转化为可变集合
frozenset():转化为不可变集合
callable():是否可以被调用
complex():
x = complex(1-2j)
print(x.real)
print(x.imag)
虚数-real实部、imag虚部
type():判断类型,适用于所有类型
isinstance():判断身份,适用于所有类型
defattr():
hasattr():
getattr():
setattr():
divmod():取整数,可用于前端分页
enumerate():枚举
hash():将参数转换为其hash值
hex():十六进制
oct():八进制
filter():
map():
#匿名函数:
def get_value(k):
return salaries[k]
f = lambda k:salaries[k]
print(f)
zip():
l1 = [1,2,3]
s = 'hel'
res = zip(l1, s) #迭代器
print(zip(l1, s))
>>>(1, 'h')
>>>(2, 'e')
>>>(3, 'l')
sorted():#排序 返回值是列表,默认升序—asc升序、desc降序
l = [3,4,1,0,9,10]
#降序
sorted(l,reverse = True)
#其他内置函数
pow(x,y,z) x ** y % z
repr()
reversed()反转
round()四舍五入
slice()切片
vars() 打印变量,无参数的时候与locals相同
locals() 打印局部变量
__import__('string') #其中string是模块名,__import__可以将字符串当做参数进行模块导入
Map()、Reduce()、Filter()、Round()修正
map() 映射
l = [1,2,3,7,5]
m = map(lambda item:item**2,l)
print(m)
print(list(m))
name_l = ['alex', 'egon, ''yuanhao', 'wupeiqi']
map(lambda name:name+'add', name_l)
print(list(m))
reduce() 合并结果
l = list(range(100))
reduce(lambda x,y:x+y,l)
filter() 过滤
name_l = [
{'name':'egon', 'age':18}
{'name':'dragonfire', 'age':18000}
{'name':'tom', 'age':12348}
]
filter(lambda d:d['age'] > 100, name_l)
round修正 原则:四舍六入五留双
round(11.5)
>>>12
round(12.5)
>>>12
十三、递归
迭代:重复做某件事
递归:自己调用自己,函数的嵌套调用(自己)
递归的原则:
- 必须有一个明确的结束条件
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致毡溢出(在计算机中,函数调用是通过栈stack这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧、由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
#例
def age(n):
if n == 1:
return 10
else:
return age(n-1)+2
print(age(5))
>>>18
#例:需求-用户输入一个数,要求找出用户所输入的数是哪个
data = [1,2,3,4,5,6,,7,9,11,14,16,17,18,20,22,24,27,29,31,35]
num = 17
while True:
if num == data[i]:
print('Find it !')
break
i+=1
#用递归的方法:(二分查找实例)
data = [1,2,3,4,5,6,,7,9,11,14,16,17,18,20,22,24,27,29,31,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:
#num在列表的右边
data = data[mid_index:]
search(num, data)
elif num < mid_value:
#num在列表的左边
data = data[:mid_index]
search(num, data)
else:
print('Find it !')
return
else:
print('====>',data)
search(17, data)