Python基础(5)-函数

函数

一、函数的好处

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

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

增强可读性与可扩展性


二、函数定义和调用

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有三种情况:

  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—>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))

十、迭代器

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

迭代器优点:

  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 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 的区别?

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

yield作用?

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

十三、递归

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

递归的原则:

  1. 必须有一个明确的结束条件
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 递归效率不高,递归层次过多会导致毡溢出(在计算机中,函数调用是通过栈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)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值