Python基础(5)-函数

来源:互联网 发布:2017黑页源码带音乐 编辑:程序博客网 时间:2024/06/03 17:22

函数

一、函数的好处

解决代码冗余,增强代码复用

保持一致性,增强可维护性

增强可读性与可扩展性


二、函数定义和调用

def 函数名(arg1,arg2,arg3……):
‘描述信息’->print(foo.__doc__)可以打印出描述信息foo function
函数体
return (任意数据类型)

1、定义无参函数

def foo():    'foo function'    print('from the foo')#可以打印出描述信息foo functionprint(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有三种情况:

  1. 不写return():默认返回None

  2. return()一个值:

def my_max(x,y):    res = x if x>y else y:    return(res)
  1. 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、参数的传值与定义

实参的位置传值和关键字传值(标准调用:实参与形参位置一一对应;关键字调用:位置无需固定)

实参三种传值形式:

  1. 按位置传值:foo(1,2)——>x=1,y=2
  2. 按关键字传值:foo(y=2,x=1)——>x=1,y=2
  3. 按位置与按关键字混用:foo(1,y=2)——>x=1,y=2

    • 按位置传递值必须在按关键字传递值的前面
    • 对于一个形参只能赋值一次

形参的位置参数与默认参数:

  1. 位置参数:必须传值的参数
  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—>3def 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、再**kwargsdef 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 resprint(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 res3print(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=123def f1():    x=1    def f2():        print(x)    return f2f=f1()print(f)#运行f()后结果是:1f()      >>> 1  

解释:参考下例
1、调用f()的时候不能知道f()的代码,而return的f2中还包含的了它作用域的引用
2、在return的f2中不仅包含了f2的功能,同时还包含的了它作用域的引用也就是x=2这个名字的引用

闭包特性:
自带”干粮”,也就是自带x=2,而不受其他x的影响

#1、以下就不是闭包:x=1def f1():    def f2():        print(x)#对外部作用域,而不是全局作用域的引用#2、下面就是闭包x=1def f1():    x=2    def f2():        print(x)    return f2f=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 urlopendef get(url):    return urlopen(url).read()print(get("http://www.baidu.com"))#修改为闭包的形式如下:#新需求:只爬百度的页面,也就是将百度的url保存下来from urllib.request import urlopendef get(url):    return urlopen(url).read()def f1(url):    def f2():        print(urlopen(url).read())    return f2f = f1("http://www.baidu.com")f()#f()执行之后返回的不光是f1自己还包括url地址,也就是返回了符f1()+url

闭包的精髓之处在于用户在想要爬取百度的网页时,不需要自己传参数,而只需要直接执行f()就可以了,简而言之,保存状态!


九、装饰器

1、装饰器的意义

开放封闭原则:
程序上线之后要尽量避免修改源代码,所以面对上线后的新需求就要利用装饰器来进行函数的功能增加

什么是装饰器:
装饰器本身就是可调用对象,功能就是增加被装饰者的功能,且不修改被装饰者的源代码和调用方式

2、无参装饰器的简单实现

import timedef 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 timedef 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_typedef index():    print("welcome to index page!")@auth(auth_type = "SQL")def home():    print("welcome to home page!")index()

4、多个装饰器

#多个装饰器@bbb@aaadef func():    passfunc = bbb(aaa(func))#多个有参装饰器@bbb("b")@aaa("a")def func():    passfunc = bbb("b")(aaa("a")(func))

十、迭代器

迭代器:一种不依赖索引遍历对象的工具

迭代器优点:

  1. 提供了一种不依赖索引的取值方式,这样可以遍历没有索引的可迭代对象,例如字典、集合、文件
  2. 迭代器更省内存

迭代器缺点:

  1. 无法获取迭代器的长度,使用不如列表索引取值灵活
  2. 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:
break

d={“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 1g = test()print(g)#既然生成器本身就是迭代器,所以有next()方法print(next(g))

2、yield

#每次运行生成器就是执行一次函数运行,并返回一个yield的值,如果有多个yield,则next一次取一个yielddef test():    print("one")    yield 1    print("two")    yield 2    print("three")    yield 3g = test()print(next(g))>>>one>>>1print(next(g))>>>two>>>2print(next(g))>>>three>>>3#执行一次next就是在上一个yield的基础上继续向下执行,yield就是返回值

生成器yield与return 的区别?

  1. return只能返回一次值,函数就彻底结束了
  2. yield能返回多次值

yield作用?

  1. yield吧函数变成生成器—>迭代器
  2. return只能返回一次值,函数就彻底结束了,yield能返回多次值
  3. 函数在暂停以及继续下一次运行时的状态是由yield保存

3、生成器应用

1、惰性计算:自己控制循环值

def func():    while True:        yield n        n += 1f = func()print(next(f))

2、实现linux的tail读取文件新增内容功能

#原本功能介绍import timedef 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 linedef 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会将'包子'传递给yieldprint(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,否则为Falseany():对所有课迭代参数进行布尔判断,任一为True则为True,全FalseFalsesum():求和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 % zrepr()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)>>>12round(12.5)>>>12

十三、递归

迭代:重复做某件事
递归:自己调用自己,函数的嵌套调用(自己)

递归的原则:

  1. 必须有一个明确的结束条件
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 递归效率不高,递归层次过多会导致毡溢出(在计算机中,函数调用是通过栈stack这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧、由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
#例def age(n):    if n == 1:        return 10    else:        return age(n-1)+2print(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 = 17while 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)
原创粉丝点击