函数

函数

# 函数:重复利用的工具
可以完成特定功能的代码块,函数就是存放代码块的容器

# 为什么用函数?
1. 避免代码的冗余
2. 让程序代码结构更加清晰
3. 让代码具有复用性,便于维护

# 如何用函数?
先定义,后调用

1、函数的定义(函数四部分)
# 1、fn:函数(变量)名
# 2、():参数列表,参数个数可以为0-n个,但()一定不能丢
# 3、return:函数的返回值
# 4、函数体:实现函数功能的具体代码

# def:声明函数的关键词
def fn(参数们):
	函数体
    return 函数的返回值

def fn(money):
    print('你付了%s元钱' % money)
    print('函数的一行代码')
    print('函数的二行代码')
    return '冰红茶'
# fn直接打印,只能看到函数存放代码的地址
print(fn,id(fn)) #<function fn at 0x00220810> 2230288

# 使用函数:先定义,后使用
函数名():拿到函数的地址,并执行函数中存放的代码块(函数体),执行完毕后,会得到函数的返回值,返回值就跟普通变量一样,可以直接打印、使用、运算
函数名(参数):执行函数并传入参数
# 函数的返回值也称为函数值,就是执行结束后的结果,可以用变量接收,可以直接打印,可以直接使用
msg = fn(10)
print(msg)
print('>>>>', fn(10) + '好喝')
# 函数执行的本质:执行函数体,得到函数返回值

2、从函数体分类
# 空函数:项目之初,知道项目由哪些功能,但还没有明确功能体,可以用pass对函数体填充
def computed():
    pass
# 非空函数:有函数体
def add_num(n1, n2):
    ''' 函数的注释
    :param n1: 第一个数
    :param n2: 第二个数
    :return: 两个数之和
    '''
    return n1 + n2
res = add_num(10, 20)
print(res)
a = 50
b = 100
print(add_num(a, b))

3、从参数列表分类
# 无参函数:函数体运行不需要外界提供参数
def print_msg():
    print("欢迎大家来到老男孩教育,奉献你的余生!!!")
print_msg()
# 有参函数:函数体运行需要外界提供参数
def print_school_msg(p1, p2):
    print("欢迎%s和%s领导莅临老男孩教育,敞开你的腰包!!!" % (p1, p2))
p1 = input("输入一号领导名:")
p2 = input("输入二号领导名:")
print_school_msg(p1, p2)

4、从返回值进行分类
# 1、没有return的函数:返回值为None
# 2、空return:返回值也为None
# 3、return一个值
# 4、return多个值

# 返回值没有类型和个数的限制
# 无return函数与空return函数的区别
空return:可以根据具体情况主动退出函数(像break结束循环一样)
def fn3(msg):
    print('fn3 第一行逻辑')
    # msg信息不正常:'' | None
    if msg == '' or msg is None:
        # 结束函数
        return
    print('msg信息正常: %s' % msg)
msg = input('msg: ')
fn3(msg)

# 一个值的返回就是一个值
def add_num(n1, n2):
    return n1 + n2
print(add_num(10, 15))

def computed(n1, n2):
    # 返回四则运算的结果
    r1 = n1 + n2
    r2 = n1 - n2
    r3 = n1 / n2
    r4 = n1 * n2
    return [r1, r2, r3, r4]
a, b, c, d = computed(50, 25)
print(a, b, c, d)

# 多个值的返回: 本质就是返回装有多个值的元组
def computed_sup(n1, n2):
    # 返回四则运算的结果
    r1 = n1 + n2
    r2 = n1 - n2
    r3 = n1 // n2
    r4 = n1 * n2
    return r1, r2, r3, r4
a, b, c, d = computed_sup(50, 25) #得到的是每个值
print(a, b, c, d) #75 25 1250 2.0
res = computed_sup(50, 25)  # 得到的是装有所有结果的元组
print(res) #(75,25,1250,2.0)

5、函数的嵌套调用
#在一个函数体中调用另一个函数
# 在解释过程中,不执行函数体,在函数被调用时,函数体才被执行
def fn1():
    print('fn1 run')
    fn2()
def fn2():
    print('fn2 run')
    fn3()
def fn3():
    print('fn3 run')
# 注:在fn1中调用了fn2,所以调用fn1的逻辑应该在fn2定义之后
fn1()

# 需求:用函数实现获取两个数的最大值
def max_2(n1,n2):
    if n1 > n2:
        return n1
    return n2
# 获取三个数的最大值
def max_3(n1,n2,n3):
    m2 = max_2(n1,n2)
    return max_2(m2,n3)
print(max_3(300,30,30))
#四个数呢?
def max_4(n1,n2,n3,n4):
    m3 = max_2(n1,n2)
    return max_3(m3,n3,n4)

# 5、指定文件名的文件复制
with open('001.jpg', 'rb') as r, open('100.jpg', 'wb') as w:
    for line in r:
        w.write(line)
with open('001.jpg', 'rb') as r, open('200.jpg', 'wb') as w:
    for line in r:
        w.write(line)
def copy_img(target_img):
    with open('001.jpg', 'rb') as r, open(target_img, 'wb') as w:
        for line in r:
            w.write(line)
copy_img('888.jpg')
copy_img('999.jpg')

def copy_file(source, target):
    '''
    :param source: 被复制的文件
    :param target: 复制得到的文件
    :return:
    '''
    with open(source, 'rb') as r, open(target, 'wb') as w:
        for line in r:
            w.write(line)

copy_file('cmd.txt', 'aaa.txt')
copy_file('temp.txt', 'ttt.txt')

6、函数实现登录注册
'''
1.可以循环登录注册,输入1代表选择登录功能,输入2代表注册功能,输入0代表退出,其他输入代表输入有误,重输
2.用户的账号密码信息存放在usr.txt文件中,保证用户注册成功后,重启系统,用户信息仍然保存
3.登录在账号验证通过才输入密码验证登录,账号验证三次失败自动进入注册功能,登录三次验证失败自动退出系统
4.第一次注册,文件写入 账号:密码 信息,再次注册追加写入 |账号:密码 信息
'''
# 详细代码:
# 获取文件中用户信息
def get_user_info():
    with open('user.txt','rt',encoding='utf-8') as f:
        info_dic = {}
        data = f.read()
        meg = data.split('|')
        for mes in meg:
            k,v = mes.split(':')
            info_dic[k] = v
    return info_dic    #返回用户信息字典

#注册
def register():
    print('注册页面')
    name = input("请输入要注册的用户名:")
    pwd = input("请输入您的密码:")
    with open('user.txt','at',encoding='utf-8') as w:
        w.write("|%s:%s"%(name,pwd))
    print("注册成功")
    start()

#登录
def login():
    print('登录页面')
    count = 0
    while count < 3:
        name = input('请输入登录名:')
        if name in get_user_info():
            break
        else:
            print('用户名错误')
            count += 1
    if count == 3:
        register()
    else:
        print("登录名验证成功")
        num = 0
        while num <= 3:
            pwd = input('请输入登录密码:')
            if pwd == get_user_info().get(name):
                print("登录成功。")
                return
            else:
                print("密码错误")
                num += 1
        print("登录失败,退出系统")
        return

#启动系统
def start():
    print('系统启动:')
    while True:
        tag = input("""请输入指令:
1  登录
2  注册
0  退出
>>>:""")
        if tag.isdigit():
            tag = int(tag)
        if tag == 0:
            break
        elif tag == 1:
            login()
            break
        elif tag == 2:
            register()
        else:
            print('输入有误,请重新输入:')
# 启动系统
start()
#循环什么时候用

参数

一、形参与实参
1、***** 在函数定义()中出现的参数:形参
# ***** 形参就是拷贝实参的值,随着函数的调用才产生,随着函数调用结束而销毁
def fn(a, b, c):
    # a,b,c必须要从外界获取值,才有意义
    print(a)
    print(b)
    print(c)
    # 外部提前产生了的变量,内部可以使用
    print(x)
    print(y)
    print(z)
# x,y,z是实际存在的值
x = 10
y = 20
z = 30
2、***** 在函数调用()中出现的参数(外界实际存在的值):实参
fn(10, 20, 30)  #常量
fn(x, y, z)  #变量
fn(x + 10, y * 2, z / 5)  #表达式
# ***** 调用函数时,实参可以由 常量、变量、表达式或三种的组合
# 实参作用:为形参传递值
# 在函数的外部不能直接使用函数的形参,原因:函数调用完毕后,形参被销毁了
print(a)  # NameError: name 'a' is not defined
print(b)  # NameError: name 'b' is not defined
print(c)  # NameError: name 'c' is not defined

二、实参的分类
1、位置实参: 按照位置的顺序,从左到右为形参传递值,必须按照顺序
def fn1(a, b, c):
    print(a, b, c)
fn1(10, 20, 30)
fn1(30, 20, 10)

2、关键字实参:可以不按顺序传递, 按形参名字取值
fn1(a=10, b=20, c=30)
fn1(c=30, b=20, a=10)

# 两者混用: 关键字实参必须出现在位置实参之后,否则报错
# 可以先位置实参后关键字实参传参
# 不能对一个形参重复赋值

# 强调:
位置实参:只能给位置形参|默认形参|可变长位置形参传参
关键字实参:可以给任意形参传参

fn1(10, c=20, a=30)  # 10按位置传参给a,a=30给a传参,c可以拿到20,但b没人传参
# TypeError:fn1() got multiple values for argument 'a' a被多次传值

# 合法传参 *****
fn1(10, c=30, b=20)
fn1(10, b=30, c=20)
def fn2(a, b, c, d, e, f):
    print(a, b, c, d, e, f)
fn2(10, 20, 30, e=100, f=500, d=200) #位置在前,关键字在后且关键字可以不按顺序

三、形参的分类
1、位置形参(positional argument)
# 但凡按照位置定义的形参,必须被传值,多一个不行,少一个也不行
def fn(a,b,c):
	print(a,b,c)
#位置形参可以由 位置实参 与 关键字实参 来传值
fn(10, 20, 30)
fn(a=10, b=20, c=30)

2、默认形参
# 默认形参具有默认值,用 = 进行赋值
def fn(a=10,b=20):
    print(a, b)
# 默认形参的值通常应该是不可变类型
# 默认形参可以由 位置实参 与 关键字实参 来传值,还可以不用传值(采用自身默认值)
fn(20, 30)    #20  30
fn(a=200, b=300)    #200  300
fn(100)    #100  20   (100会传给a)
fn(b=100)    #10  100
# 位置形参与默认形参同时存在,默认形参必须在后
def fn1(a,b,c=10,d=20):  #c、d必须要在a、b后面
    print(a, b, c, d)  
# 拥有默认值的形参可以传参也可以不用传参
# 位置形参必须传值,默认形参分情况传值
fn1(100, 200, d=1000)  #100  200 10 1000

3、可变长位置形参
# 在形参中带*:会将调用函数时溢出的位置实参保存成元组的形式,然后赋值给*后的变量名
def fn(x,y,*z):  #z=(3,4,5,6)
    print(x,y,z)  #1,2,(3,4,5,6)
fn(1,2,3,4,5,6)
# 可变长位置形参只能由 位置实参 来传值
def fn(a, b=10, *args):
    print(a, b)
    print(args)
# 可变长形参会以元组形式接受 位置形参与默认形参未接受完的 所有传入的位置实参
# 小细节:可变长形参只能接受位置实参的值,位置实参还必须在关键字实参前,导致默认形参只能由位置实参来传值
fn(1, 20, 100, 200)  # 1  20     (100,200)
fn(100)  # 100  10       ()

4、可变长位置形参整体赋值
def fn(*args):
    print(args)
fn(1, 2, 3, 4, 5)  # (1, 2, 3, 4, 5)
a = 'ab'
b = [1, 2]
c = (1, 2)
d = {1, 2}
fn(a)  # ('ab', )
fn(b)  # ([1, 2], )
fn(c)  # ((1, 2), )
fn(d)  # ({1, 2}, )

# 问:
# 就拿a,b,c,d进行传值,如何得到
'''
('a', 'b')
(1, 2)
'''
# 答:
# * 单列集合(保护字符串)将该变量进行 打散 传值 (本质传递的是地址)
fn(*a)  #('a','b')
fn(*b)  # (1,2)
fn(*c)  # (1,2)
fn(*d)  # (1,2)

# 打散
# 在实参中带*:但凡在实参中带*的,在传值前都先将其打散成位置实参,再进行赋值
def foo(x,y,*z): #z=(3,4,5,6)
    print(x,y,z)
foo(1,*[2,3,4,5,6])  #相当于foo(1,2,3,4,5,6) 结果:1 2 (3,4,5,6)

def foo(x,y,z):
    print(x,y,z)
foo(1,*[2,3,4,5,6]) #相当于foo(1,2,3,4,5,6) 结果:TypeError
foo(*(1,2,3)) #相当于foo(1,2,3) 结果:1 2 3
foo(*'hello') #TypeError
foo(*'abc') #a b c

def fn1(*args):
    print(args)
ls = [1, 2, 3, 4, 5]
fn1(ls)  # ([1,2,3,4,5], )
fn1(*ls)  # 将ls打散为1,2,3,4,5 再进行传值 => (1,2,3,4,5)

5、关键字形参
def tt(a, b=10, *, c, d=10, e):
    print(a, b, c, d, e)
# *为分割线,可以有变量名 *args, 也可以只用来做分隔
# a:位置形参
# b:默认形参
# c,e:无值关键字形参
# d:有值关键字形参

# 注:
# a和b有先后顺序
# c,d, e无先后顺序
# *****关键字形参必须由 关键字实参 来传值, 出现在*后的都是关键字形参

# 命名关键字参数:放到*与**之间的参数称之为命名关键字参数
# 注意:命名关键字参数必须按照key=value的形式传值
def fn(x,y,*args,m,n,**kwargs):  #凡是放到*后面的参数都必须用关键字参数传值

# keyword-only arguments
def fn(*, a, b=10, c):
    print(a, b, c)
# fn(10, 20, 30)  # 错误:TypeError: fn() takes 0 positional arguments but 3 were given,* 后面的形参必须由 关键字实参 来传值
fn(b=300, c=100, a=200) #200  300  100

6、可变长关键字形参
# 在形参中带**:会将调用函数时溢出的关键字实参保存成字典的形式,然后赋值给**后的变量名
def fn(x,y,**z):  #z={'a':1,'b':2,'c':3}
    print(x,y,z)
foo(1,y=2,a=1,b=2,c=3)
# 可变长关键字形参:用来接收没有被关键字形参接收完的关键字形参,也只能由关键字实参来传值
# 用字典来存放数据
def fn(*, a, b=20, c, **kwargs):
    print(a, b)
    print(kwargs)
fn(d=40, e=50, a=10, c=30)  #10  20    {'d':40,'e':50}

# 打散
# 注意:实参中带**的,只针对字典这个情况
# 在实参中带**:但凡在实参中带**的,在传值前都先将其打散成关键字实参,再进行赋值
def foo(x,y,**z):
    print(x,y,z)
foo(1,**{'a':100,'b':200}) #相当于foo(1,b=200,a=100)  结果:报错
foo(1,**{'a':100,'b':200,'y':111}) #相当于foo(1,b=200,a=100,y=111)  结果:1 111 {'a':100,'b':200}

# 整体赋值
dic = {'name': 'Owen', 'age': 18}
def fn1(**kwargs):
    print(kwargs)
fn1(**dic)  #{'name':'Owen','age':18}

dd = {
    'k1': [1, 2, 3, 4, 5],
    'k2': {"name":"Bob"}
}
fn1(**dd)  #{'k1':[1,2,3,4,5],'k2':{'name':'Bob'}}

def fn2(*args):
    print(args)
ll = [1, [1, 2], {"name":"Bob"}]
fn2(*ll)  # (1, [1, 2], {"name":"Bob"})

# 规范:在形参中带*与**的,*后的变量名应该为agrs,**后跟的变量名应该是kwargs
def fn(*args,**kwargs):  #args=(1,2,3,4,5) kwargs={'a':1,'b':2,'c':3}
    print(args)
    print(kwargs)
fn(1,2,3,4,5,a=1,b=2,c=3)
 
# ******当我们想要将传给一个函数的参数格式原封不动的转嫁给其内部的一个函数,应该使用下面这种形式
def bar(x,y,z):
    print(x,y,z)
def wrapper(*args,**kwargs): #args=(1,2) kwargs={'z':3}
    bar(*args,**kwargs) #bar(*(1,2),**{'z':3}) == bar(1,2,z=3)
wrapper(1,2,z=3) #虽然调用的是wrapper,但是要遵循的确是bar的参数标准



四、总结
'''
位置形参:位置实参与关键字实参都可以对其传参
默认形参:位置实参与关键字实参都可以对其传参,也可以不被传参
可变长位置形参:接收位置形参、默认形参没有接收完的所有位置实参
无初值关键字形参:只能由关键字实参传值
有初值关键字形参:只能由关键字实参传值,也可以不被传值
可变长关键字参数:接收两种关键字形参没有接收完的所有关键字实参

注:各种形参出现的先后
位置形参 - 默认形参 - 可变长位置形参 - 无|有初值关键字形参 - 可变长关键字形参
'''
def fn(a, b, c=10, *args, d, e=20, f, **kwargs):
    pass
# 位置形参:a、b
# 默认形参:c
# 可变长位置形参:args
# 无初值关键字形参:d、f
# 有初值关键字形参:e
# 可变长关键字参数:kwargs

函数对象

# 函数是第一类对象,指的是函数名指向的值(函数)可以被当作数据去使用
a = 10
print(a)
def fn():
    num = 10
    print('fn function run')
print(fn)  #<function fn at 0x0103F810>
# 函数名存放的就是函数的地址,所以函数名也是对象,称之为函数对象

# 函数对象的应用
1、可以直接被引用
func = fn
print(func)  #<function fn at 0x0103F810>
fn()  #fn function run
func()  #fn function run

2、可以当作函数参数传递
def add(a, b):
    return a + b
def low(a, b):
    return a - b
def jump(a, b):
    return a * b
def full(a, b):
    return a / b
# 计算: 通过该函数可以完成对任意两个数的四则运算某一运算
def computed(fn, n1, n2):
    # fn代表四则运算中的一种
    # res为运算结果
    res = fn(n1, n2)
    return res
# 完成两个数的某一运算,拿到结果
while True:
    cmd = input('cmd:')
    if cmd == 'add':
        result = computed(add, 100, 20)
    elif cmd == 'low':
        result = computed(low, 100, 20)
    else:
        print('输入有误')
        break
    print(result)
    
3、可以作为容器类型的元素
def add(a, b):
    return a + b
def low(a, b):
    return a - b
def jump(a, b):
    return a * b
def full(a, b):
    return a / b
def quyu(a, b):
    return a % b
def computed(fn, n1, n2):
    res = fn(n1, n2)
    return res
method_map = {
    'add': add,
    'low': low,
    'jump': jump,
    'full': full,
    'quyu': quyu,
}
while True:
    cmd = input('cmd: ')
    # 用户输入的指令只要有对应关系,就会自动去走对应的计算方法
    # 这样外界就不用去关心到底有哪些计算方法
    if cmd in method_map:
        cp_fn = method_map[cmd]  # 拿到计算方法
        result = computed(cp_fn, 100, 20)  # 通过计算方法得到计算结果
        print(result)
    else:
        print('输入有误,退出')
        break
        
4 可以作为函数的返回值
def add(a, b):
    return a + b
def low(a, b):
    return a - b
def jump(a, b):
    return a * b
def full(a, b):
    return a / b
def quyu(a, b):
    return a % b
def computed(fn, n1, n2):
    res = fn(n1, n2)
    return res
method_map = {
    'add': add,
    'low': low,
    'jump': jump,
    'full': full,
    'quyu': quyu,
}
# 根据指令获取计算方法
def get_cp_fn(cmd):
    if cmd in method_map:
        return method_map[cmd]
    return add  # 输入有误用默认方法处理
while True:
    cmd = input('cmd: ')
    if cmd == 'quit':
        break
    cp_fn = get_cp_fn(cmd)
    result = computed(cp_fn, 100, 20)
    print(result)
   

名称空间

# 名称空间:存放名字与内存空间地址对应关系的容器
# 但凡查找值一定要通过名字,访问名字必需去查找名称空间
# 作用:解决由于名字有限,导致名字重复发送冲突的问题
print(len('abc'))  #3
len = len('abcdef')
print(len)  #6
del len
# del len  #如果不注释,则下面一行代码会报错,因为第二次del删除的是内置的名称空间
print(len('000111222')) #9 (在上面一行del len不注释的情况下)
'''
[
    # ['len': 100001]  # 保存长度的变量
    ['len': 200001]  # 计算长度的函数
]
'''
def fn1():
    len = 10
    print(len)
def fn2():
    len = 20
    print(len)
fn1()  #10
fn2()  #20

# 三种名称空间
# Built-in:内置名称空间;系统级,一个;随解释器执行而产生,解释器停止而销毁
# Global:全局名称空间;文件级,多个;随所属文件加载而产生,文件运行完毕而销毁
# Local:局部名称空间;函数级,多个;随所属函数执行而产生,函数执行完毕而销毁
# 注:
# del 名字:可以移除查找最近的名字与内存空间地址的对应关系
# 加载顺序:Built-in > Global > Local
# 查询顺序:Local >Global >Built-in(基于当前所在位置往上查找)

# 注:
# 不同作用域之间名字不冲突,以达到名字的重用
# 查找顺序:Local > Enclosing > Global > Built-in
# ******名字的查找顺序,在函数定义阶段就已经固定死了(即在检测语法时就已经确定了名字的查找顺序),与函数的调用位置无关,也就是说无论在任何地方调用函数,都必须回到当初定义函数的位置去确定名字的查找关系
案列一:
len=111
def foo():
    len=222
    print(len)
foo()
print('站在全局找len: ',len)
x=111
def f1():
    x=222
    def f2():
        def f3():
            # x=444
            print(x)
        x=333
        f3()
    f2()
f1()

案列二:
x=111
def outer():
    def inner():
        print('from inner',x) # x访问的时全局名称空间中x
    return inner
f=outer()
# print(f)
x=222
f()  #from inner 222

案列三:
x=111
def outer():
    def inner():
        print('from inner',x) # x访问的时全局名称空间中x
    return inner
f=outer()
# x=222
def func():
    x=333
    f()
x=444
func()  #from inner 444

案列四:
x=111
def outer():
    def inner():
        print('from inner',x) # x是来自于当前层的名字
        x=2222222222
    return inner
f=outer()
f()  #local variable 'x' referenced before assignment

global关键词

def fn()
	global num
	num = 20
	print(num)
# global关键词可以将Local的名字提升为Global的名字
# 一个文件中的Global名字就是一个,所以函数内部外部使用的名字都是一个
fn()  #20 注:一定要调用函数,才能产生名字,并提升
print(num)  #20

nonlocal关键词

# nonlocal:声明一个名字是来自于当前层外一层作用域的,可以用来在局部修改外层函数的不可变类型
# 作用:将L与E(E中的名字需要提前定义)的名字统一
# 应用场景:如果想在被嵌套的函数中修改外部函数变量(名字)的值
# 案列:
def outer():
    num = 10
    print(num)  # 10 
    def inner():
        nonlocal num # 将 L 与 E(E中的名字需要提前定义) 的名字统一
        num = 20
        print(num)  # 20
    inner()
    print(num)  # 20
outer()
#print(num)  # 执行到这条语句会报错

函数嵌套

# 将函数直接定义到另一个函数内部,就可以使用外部函数中的名字
def outer():
    num = 20
    def inner():
        print(num)  # inner就可以直接使用outer中的名字
    inner()  #20
outer()

# 一个简单应用
from math import pi
def circle(radius,action=0):
    """
    圆形相关运算
    0代表求面积
    1代表求周长
    """
    def area(radius):
        return pi * (radius ** 2)
    def perimiter(radius):
        return 2 * pi * radius
    if action == 0:
        res = area(radius)
    elif action == 1:
        res = perimiter(radius)
    return res

作用域

# 作用域:名字起作用的范围
# 作用:解决同名字可以共存问题
len = 10
def outer():
    len = 20  # 外层函数的局部变量:Enclosing - 嵌套作用域
    def inner():
        len = 30
        print('1:', len)  # 30, inner -> outer -> global -> built-in
    inner()
    print('2:', len)  # 20, outer -> global -> built-in
outer()
print('3:', len)  # 10, global -> built-in

del len
print('4:', len)  # len地址, built-in

# 四种作用域
# Built-in:内置作用域,所有文件所有函数
# Global:全局作用域,当前文件所有函数
# Enclosing:嵌套作用域,当前函数与当前函数的内部函数
# Local:局部作用域,当前函数


闭包

# closure:被包裹的函数,称之为闭包,闭包就是函数嵌套(格式稍作改良)
# 闭包函数提供了一种为函数体传值的解决方案
# 完整的闭包结构:1.将函数进行闭包处理;2.提升函数名的作用域,将内部函数对象作为外部函数的返回值
# 将内部函数对象作为外部函数的返回值:1.可以使用局部变量; 2.不改变函数的调用位置
def outer():
    num = 10
    def inner():  # 闭包:定义在函数内部的函数称之为闭包
        print(num)
    return inner
fn = outer()  # fn = inner
num = 20
fn()  # 10

# 案例一:外部函数可以为闭包传递参数 (了解)
import time
def download():
    print('开始下载')
    time.sleep(2)
    print('下载完成')
    data = "下载得到的数据"
    outer(data)
# 为闭包传参
def outer(data):
    def inner():
        # 保存,播放,删除等操作
        print("闭包打印:", data)
    inner()
download()

# 案例二:延迟执行
import requests
'''
# get_html一执行就获取页面
def get_html(url):
    html = requests.get(url)
    print(html.text)
get_html('https://www.baidu.com')
get_html('https://www.python.org')
get_html('https://www.sina.com.cn')
get_html('https://www.baidu.com')
'''

def outer(url):
    def get_html():
        html = requests.get(url)
        print(html.text)
    return get_html
# 先预定义多个爬虫方法,爬页面操作并未执行
baidu = outer('https://www.baidu.com')
python = outer('https://www.python.org')
sina = outer('https://www.sina.com.cn')
# 什么时候想爬什么页面就调用指定页面的爬虫方法
baidu()
sina()
baidu()

开放封闭原则:不改变调用方式与源代码上增加功能

'''
1.不能修改被装饰对象(函数)的源代码(封闭)
2.不能更改被修饰对象(函数)的调用方式,且能达到增加功能的效果(开放)
'''
# 1.0版本
def fn():
    print('fn run')
fn()

# 2.0版本
def fn():
    print('fn run0')
    print('fn run')
    print('fn run2')
fn()

# 目的:将2.0版本中的功能增加到1.0版本中
# 修改了源代码,没有更改调用方式,对外调用方式还是原来的,但功能要有所增加(开放)
def fn():
    print('fn run0')
    print('fn run')
    print('fn run2')
fn()

# 更改了调用方式,没有修改原功能代码(封闭)
def wrap(fn):
    print('fn run0')
    fn()
    print('fn run2')
wrap(fn)

装饰器初识



# 以 花瓶 为案例
def vase():
    print('插花')
vase()

# 增加一个绘画后观赏功能:不满足开放封闭原则,修改了源代码
def vase():
    print('插花')
    print('绘画:进行观赏')
vase()

# 增加一个绘画后观赏功能:不满足开放封闭原则,修改了调用方式
def wrap():
    vase()
    print('绘画:进行观赏')
wrap()

# 虽然满足了开放封闭原则,但是出现了函数调用的死循环
def fn():
    vase()  #vase已经指向了fn,所以会一直循环
    print('绘画:进行观赏')
vase = fn
vase()

# 了解:满足开放封闭原则,且可以达到装饰器的作用:拓展功能
def vase():
    print('插花')
tag = vase  # 暴露在全局:很容易被修改掉
def fn():
    tag()
    print('绘画:进行观赏')
vase = fn
vase()

# 下方的函数嵌套结构就是装饰器
def vase():
    print('插花')
def wrap(tag):
    def fn():
        tag()  # 原有的vase
        print('绘画:进行观赏')
    return fn  # 拓展功能后的vase
vase = wrap(vase)  # 将拓展功能后的功能函数重新赋值给vase
vase()  # 功能拓展了,且调用方式不变

装饰器简化语法

# 把要被装饰的函数作为外层函数的参数通过闭包操作后返回一个替代版函数
# 被装饰的函数:fn
# 外层函数:outer(func)  outer(fn) => func = fn
# 替代版函数: return inner: 原功能+新功能

def fn():
    print("原有功能")
# 装饰器
def outer(tag):
    def inner():
    	tag()
        print("新增功能")
    return inner
fn = outer(fn)                      
fn()

@语法糖:@外层函数

def outer(f):
    def inner():
        f()
        print("新增功能1")
    return inner

def wrap(f):
    def inner():
        f()
        print("新增功能2")
    return inner
#被装饰的顺序决定了新增功能的执行顺序
@wrap  #后被执行  fn = wrap(fn)
@outer  # 先被执行  fn = outer(fn)
def fn():  # 原有功能函数必须要放在@语法糖后面
    print("原有功能")
fn()
    
# 总结:一个函数可以被任意一个相关装饰器装饰,也可以被任意几个装饰器装饰
# 注:装饰的顺序会影响新增功能的执行顺序

有参有返的函数被装饰

# 增加一个账号处理功能:3位及以上英文字符或汉字
def check_usr(fn):  # fn, login, inner:不同状态下的login,所以参数是统一的
    def inner(usr, pwd):
         # 在原功能上添加新功能
        if not (len(usr) >= 3 and usr.isalpha()):
            print('账号验证失败')
            return False
        
        # 原有功能
        result = fn(usr, pwd)  # login
        
        # 在原功能下添加新功能
        # ...
        
        return result
    return inner

# 增加一个密码处理功能:6位及以上英文和数字
def check_pwd(fn):
    def inner(usr, pwd):
        if not (len(pwd) >= 6 and pwd.isalnum()):
            print('密码验证失败')
            return False
        return fn(usr, pwd)
    return inner

# 登录功能
@check_usr  # login = check_usr(login) = inner
@check_pwd
def login(usr, pwd):
    if usr == 'abc' and pwd =='123qwe':
        print('登录成功')
        return True
    print('登录失败')
    return False

res = login('abc', '123qwe')  # inner
print(res)

# 总结:
# 1.login有参数,所以in与fn都有相同参数
# 2.login有返回值,所以inner与fn都有返回值

装饰器结合可变长参数

def wrap(fn):
    def inner(*args,**kwargs):
        # print(args)  #(1,2,3)
        # print(kwargs)  #{'x':10,'y':20,'z':30}
        fn(*args,**kwargs)
        print("新增功能")
    return inner
@wrap
def func(a,b,c,*,x,y,z):
    print(a,b,c,x,y,z)
    print("原有功能")
func(1,2,3,x=10,y=20,z=30)

迭代器

一、迭代器概念
# 器:包含了多个值的器
# 迭代:循环反馈(一次从容器中取出一个值)
# 迭代器:从装有多个值的容器中一次取出一个值给外界
# ls = 'abcdef'
ls = [1, 2, 3, 4, 5]
# 遍历:被遍历的对象必须是有序容器
i = 0
while i < len(ls):
    print(ls[i])
    i += 1
    
 # 单纯的重复不是迭代
 i=0
 while True:
    print(i)

 # 迭代:重复+每次重复都是基于上一次的结果而进行
 l=['a','b','c']
 i=0
 while i < len(l):
     print(l[i])
     i+=1
        
# 通过迭代器取值优缺点:
# 优点:
#     1. 提供一种通用的且不依赖于索引的迭代取值方式
#     2. 同一时刻在内存中只存在一个值,更节省内存

# 缺点:
#     1. 取值不如按照索引的方式灵活,(不能取指定的某一个值,而且只能往后取)
#     2. 无法预测迭代器的长度

二、可迭代对象
# 对象:python中的一个对象(装有地址的变量)
# 可迭代对象:有__iter__()方法的对象,调用该方法返回迭代器对象
# [].__iter__()
# ().__iter__()
# {}.__iter__()
# {1,}.__iter__()
#有哪些:str | list | tuple | dict | set | range() | file | 迭代器对象 | enumerate() | 生成器

for v in 'abc'.__iter__():
    print(v)
    
for k, v in {'a': 1, 'b': 2}.items():  # keys() | values() | items()
    print(k)
    
r_obj = range(10)
for v in r_obj:
    print(v)
    
with open('abc.txt', 'r', encoding='utf-8') as f:
#    for line in f:
#        print(line)
	print(f.__next__())
	print(f.__next__())
	print(f.__next__())

三、迭代器对象
# 迭代器对象:既有__iter__方法,又有__next__()方法的对象,也就是用该方法一次从迭代器对象中获取一个值,从前往后一次一个,取出一个少一个
# 迭代器对象可以做到不依赖索引取值(一次从容器中取出一个值)

# ps:
# 1.迭代器对象一定是可迭代的对象,而可迭代的对象却不一定是迭代器对象
# 2.文件对象本身就是一个迭代器对象

# 可迭代对象
st1 = {3, 5, 7, 1, 9}
# 迭代器对象
iter_obj = st1.__iter__()
print(iter_obj)  # <set_iterator object at 0x0000026E0BF3B510>
# print([1, 2, 3].__iter__())  # <list_iterator object at 0x0000026E0BFF8320>

# 迭代器对象取一个值就少一个值
print(iter_obj.__next__())  # 1
print(iter_obj.__next__())  # 3
print(iter_obj.__next__())  # 5
print(iter_obj.__next__())  # 7
print(iter_obj.__next__())  # 9
# print(iter_obj.__next__())  # 抛异常 StopIteration, 可以通过try对异常进行捕获并处理
print('===============================================')
iter_obj = st1.__iter__()  # 上一个迭代器对象迭代取值完毕后,就取空了,如果要再次取值,要重新生成迭代器对象
# 迭代器对象不能求长度(内部值的个数)
while True:
    try:
        ele = iter_obj.__next__()
        print(ele)
    except StopIteration:
        # print("取完了")
        break
#重点:
1.从迭代器对象中取元素,取一个少一个,如果要从头开始取,需要重新获得拥有所有元素的迭代器对象
2.迭代器对象也有__iter__()方法,调用后得到的是自己本身(当前含有几个元素,得到的就只有几个元素的迭代器对象)  

四、for循环迭代器
#工作原理
#1. 先调用in后面那个对象的__iter__方法,将其变成一个迭代器对象
#2. 调用next(迭代器),将得到的返回值赋值给变量名k
#3. 循环往复直到next(迭代器)抛出异常,for会自动捕捉异常然后结束循环
# ps:从for角度,可以分辨出但凡可以被for循环循环取值的对象都是可迭代的对象
iter_obj = st1.__iter__()
for ele in iter_obj:
    print(ele)
    
for ele in st1:  # 1.自动完成 for ele in st1.__iter__():  2.自动完成异常处理
    print(ele)
    
五、总结
# 可迭代对象:有__iter__()方法的对象,调用该方法返回迭代器对象
# 迭代器对象:有__next__()方法的对象,也就是用该方法一次从迭代器对象中获取一个值,取出一个少一个
# for循环迭代器:
# 1.自动获取被迭代对象的迭代器对象;
# 2.在内部一次一次调用__next__()方法取值;
# 3.自动完成异常处理

obj = [1, 2, 3].__iter__()
for v in obj:
    print(v)
    if v == 2:
        break  # 1  2
print(obj.__iter__().__iter__().__iter__().__next__())  # 3
#print(obj.__iter__().__iter__().__iter__() is obj)  # True
# 可迭代对象.__iter__() 得到的是该对象的迭代器对象
# 迭代器对象.__iter__().__iter__()得到的就是迭代器对象本身

生成器

# 生成器不是一个函数
# 生成器:就是一个自定义的迭代器,本质就是迭代器
# 包含yield关键字的函数就是生成器
# 该函数名() 得到的是生成器对象,且不会执行函数体

def my_generator():
    yield 1
    yield 2
    yield 3
g_obj = my_generator()
# my_generator()并不会执行函数体,得到的返回值就是生成器对象
# *****但凡函数内包含yield关键字,调用函数不会执行函数体代码,会得到一个返回值,该返回值就是生成器对象
# 生成器对象就是迭代器对象
r1 = g_obj.__next__() # 1
for v in g_obj:
    print(v)  # 2 | 3

def g_fn():
    print(111111111111111)
    yield '结果1'
    print(222222222222222)
    yield '结果2'
    print(333333333333333)
    yield '结果3'
    print(444444444444444)
    yield '结果4'
    print(555555555555555)
    yield '结果5'
g_obj = g_fn()

# 在函数内部执行一次,在遇到下一个yield时停止,且可以拿到yield的返回值
r1 = g_obj.__next__()
print(r1)
# 从上一次停止的位置接着往下走,在遇到下一个yield时停止,且可以拿到yield的返回值
r2 = g_obj.__next__()
print(r2)

# 生成器可以被for循环迭代
for v in g_obj:
    print(v)
    
# 生成器的应用案例
# 当访问的数据资源过大,可以将数据用生成器处理,一次只获取所有内容的一条资源
# Owen版
def my_range(min,max = 0,step = 1):
    if max == 0:
        min,max = max,min
    tag = min
    while True:
        if tag >= max:
            break
        yield tag
        tag += step

rang_obj = my_range(5,10,2)
for i in rang_obj:
    print(i)

# egon版
def my_range(start,stop,step = 1):
    while start < stop:
        yield start
        start += step
obj = my_range(1,5)
print(next(obj))
print(next(obj))

for i in my_range(1,20,2):
    print(i)
    
# 生成器总结:
obj = [3,1,5].__iter__()
print(obj.__next__())  #取一个
print(obj.__next__())  #取一个

for v in [3,1,5]:  #循环一个个取完
    print(v)

#生成器:生成器不是函数,fn()也不是函数调用,就是产生生成器对象
def fn():
    yield 3
    yield 1
    yield 5
obj = fn()  #得到的就是生成器:[3,1,5]
print(obj.__next__())  #取一个
print(obj.__next__())  #取一个
for v in fn():  #循环一个个取完
    print(v)

枚举对象

ls = [1, 3, 5, 7, 9]
# 通过for迭代器 循环遍历 可迭代对象,需要知道迭代的索引
count = 0
for v in ls:
    print(count, v)
    count += 1

for i, v in enumerate(ls):
    print(i, v)

for i, v in enumerate('abc'): # 生成迭代器对象:[(0, 'a'),(1, 'b'), (2, 'c')]
    print(i, v)

递归调用

'''
1 什么是函数递归
    函数的递归调用是函数嵌套调用的一种特殊形式,在调用一个函数的过程中又直接或者间接地调用该函数本身,称之为函数的递归调用

    递归调用必须有两个明确的阶段:
        1. 回溯: 一次次递归调用下去,说白了就一个重复的过程,但需要注意的是每一次重复问题的规模都应该有所减少,直到逼近一个最终的结果,即回溯阶段一定要有一个明确的结束条件
        2. 递推: 往回一层一层推算出结果

'''
import sys
print(sys.getrecursionlimit())
sys.setrecursionlimit(2000)
# 自己调用自己形成递归
def foo(n):
    print('from foo',n)
    foo(n+1)
foo(0)
# 两个函数相互之间调用形成递归
def bar():
    print('from bar')
    foo()
def foo():
    print('from foo')
    bar()
foo()

age(5)=age(4)+2
age(4)=age(3)+2
age(3)=age(2)+2
age(2)=age(1)+2
age(1)=18
age(n)=age(n-1)+2 #n>1
age(n)=18         #n=1
# 递归调用就是一个重复的过程,但是每一次重复问题的规模都应该有所减少,并且应该在满足某种条件的情况下结束重复,开始进入递推阶段

def age(n):
    if n == 1:
        return 18
    return age(n-1) + 2
print(age(5))

l=[1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,]]]]]]]]]]]
def search(l):
    for item in l:
        if type(item) is not list:
            # 不是列表直接打印
            print(item)
        else:
            # 判断是列表则继续循环,判断...
            search(item)
search(l)

# 算法:是如何高效率地解决某一个问题的方法/套路
# 二分法
nums=[13,15,17,23,31,53,74,81,93,102,103,201,303,403,503,777]
find_num=503

def binary_search(nums,find_num):
    print(nums)
    if len(nums) == 0:
        print('not exists')
        return
    mid_index=len(nums) // 2
    if find_num > nums[mid_index]:
        # in the right
        nums=nums[mid_index+1:]
        # 重新执行二分的逻辑
        binary_search(nums,find_num)
    elif find_num < nums[mid_index]:
        #in the left
        nums=nums[0:mid_index]
        # 重新执行二分的逻辑
        binary_search(nums,find_num)
    else:
        print('find it')
# binary_search(nums,find_num)
binary_search(nums,94)

三元表达式

def max2(x, y):
    # if x > y:
    #     return x
    # else:
    #     return y
    return x if x > y else y
# 三元表达式实现的效果就是:条件成立的情况下返回一个值,不成立的情况下返回另外一种值

# res=条件成立情况下返回的值  if 条件 else 条件不成立情况下返回的值
name=input('your name: ').strip()
res="SB" if name == 'lqz' else "NB"
print(res)

列表生成式,字典生成式

# 列表生成式
names=['alex','lqz','yyh','fm']
l=[]
for name in names:
    res=name + '_DSB'
    l.append(res)
print(l)

l=[name + '_DSB' for name in names] # 列表生成式
print(l)

names=['alex_sb','lqz_sb','yyh_sb','fm_sb','egon']
l=[]
for name in names:
    if name.endswith('sb'):
        l.append(name)
print(l)
l=[name for name in names if name.endswith('sb')] # 列表生成式
print(l)
# 也可以用filter实现
res = filter(lambda name:name.endswith('_sb'),names)
print(list(res))

# 字典生成式
items=[
    ('name','egon'),
    ('age',18),
    ('sex','male'),
]
dic=dict(items)
print(dic)

# 补充
l=['a','b','c','d']
for i,v in enumerate(l):
    print(i,v)

keys=['name','age','sex']
vals=['egon',18,'male']
dic={}
for i,k in enumerate(keys):
    # print(i,k)
    dic[k]=vals[i]
print(dic)

dic={k:vals[i] for i,k in enumerate(keys)}
print(dic)

dic={k:vals[i] for i,k in enumerate(keys) if i > 0}
print(dic)

print({i:i for i in range(10)}) # 字典
print({i for i in range(10)})  # 集合
print({i for i in 'hello'}) #{'h','e','l','o'}

匿名函数

'''
1 匿名函数:就是没有名字的函数
2 为何要用:
    用于仅仅临时使用一次的场景,没有重复使用的需求
'''
def sum2(x,y):
    return x+y
print(lambda x,y:x+y)
print((lambda x,y:x+y)(1,2))
# 匿名函数的精髓就是没有名字,为其绑定名字是没有意义的
f=lambda x,y:x+y
print(f)
print(f(1,2))
# 匿名函数与内置函数结合使用
max,min,sorted,map,filter,reduce

salaries={
    'egon':300000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
}
# 求薪资最高的那个人名:即比较的是value,但取结果是key
res=max(salaries)
print(res)

# 可以通过max函数的key参数来改变max函数的比较依据,运行原理:
# max函数会“for循环”出一个值,然后将该值传给key指定的函数
# 调用key指定的函数,将拿到的返回值当作比较依据
def func(name):
    # 返回一个人的薪资
    return salaries[name]
res=max(salaries,key=func)  #'egon'
print(res)
# 求最大值
res=max(salaries,key=lambda name:salaries[name])  #'egon'
print(res)
# 求最小值
res=min(salaries,key=lambda name:salaries[name])  #'egon'
print(res)

# sorted排序
nums=[11,33,22,9,31]
res=sorted(nums,reverse=True)
print(nums)
print(res)
salaries={
    'egon':300000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
}
for v in salaries.values():
    print(v)
res=sorted(salaries.values())
print(res)

res=sorted(salaries,key=lambda name:salaries[name],reverse=True)
print(res)

# map:把一个列表按照我们自定义的映射规则映射成一个新的列表
names=['alex','lxx','wxx','yxx']
res=map(lambda name: name + "dSB", names)
print(list(res)) #['alexdSB','lxxdSB','wxxdSB','yxxdSB']

# filter: 从一个列表中过滤出符合我们过滤规则的值
# 运行原理:相当于for循环取出每一个人名,然后传给匿名函数,将调用匿名函数返回值为True的那个人名给留下来
names=['alex_sb','lxx_sb','wxx_sb','egon','yxx']
res=filter(lambda name:name.endswith('sb'),names)
print(list(res))
print([name for name in names if name.endswith('sb')])

# reduce: 把多个值合并成一个结果
from functools import reduce
l=['a','b','c','d']
res=reduce(lambda x,y:x+y,l,'A')
'A','a' => 'Aa'
'Aa','b'=>'Aab'
'Aab','c'=>'Aabc'
'Aabc','d'=>'Aabcd'
print(res)
# 输出结果:abcd
res=reduce(lambda x,y:x+y,l)
'a','b'=>'ab'
print(res)

# 1-100的和
res=reduce(lambda x,y:x+y,range(1,101))
1,2=>3
3,3=>6
print(res)

posted @ 2019-05-29 22:39  Mrchenwang  阅读(204)  评论(0编辑  收藏  举报