day11-函数对象,函数嵌套,名称空间与作用域,闭包函数,以及装饰器的前言

一.函数对象

在python中:函数是第一类对象,函数是第一等公民
本质:函数可以当作变量用

1.可以赋值

def func():
    print('from func')


f = func
print(f)  # <function func at 0x0000017E65C12288>
f()  # from fun

2.可以当作函数传给另外一个函数

def func():
    print('from func')
    
def foo(x):
    print(x)  # <function func at 0x000002D284BB7EE8>
    x()  # from func

foo(func)

3.可以当作函数的返回值

def func():
    print('from func')

def foo(x):
    return x


res = foo(func)
print(res)  # <function func at 0x000001E64BDAC048>

4.可以当作容器类型的元素

def func():
    print('from func')

l = [func, ]
print(l)  # [<function func at 0x000001E64BDAC048>]
l[0]()  # from func

函数名当作容器类型的元素之字典的存放 案例:

def withdraw():
    print('取款')


def transfer():
    print('转账')


def check_balance():
    print('查询余额')


def deposit():
    print('存款')
    

func_dic = {
    "0": ['退出', exit],
    "1": ["提款", withdraw],
    "2": ["转账", transfer],
    "3": ["查询余额", check_balance],
    "4": ["存款", deposit]
}

while True:
    for k, v in func_dic.items():
        print(k, v[0])

    choice = input(">>>: ").strip()
    if choice in func_dic:
        func_dic[choice][1]()
    else:
        print("输入错误")    

函数名当作容器类型的元素之列表的存放 案例:

def withdraw():
    print('取款')


def transfer():
    print('转账')


def check_balance():
    print('查询余额')


def deposit():
    print('存款')
    
  
func_list = [["退出", exit], ["取款", withdraw], ["转账", transfer], ["查询余额", check_balance], ["存款", deposit]]

while True:
    for index1, func1 in enumerate(func_list):
        print(f"{index1} 的功能为{func1[0]}")
    chios = input(">>>>:").strip()
    if not chios.isdigit(): continue
    chios = int(chios)
    for index, func in enumerate(func_list):
        if chios == index:
            func_list[chios][1]()
    else:
        print('暂不支持')

二.函数的嵌套

函数的嵌套定义: 在一个函数中定义了另外一个函数

例子:比较4个数的大小

def max2(a,b):
    if a > b:
        return a
    else:
        return b


def max4(a,b,c,d):
    res = max2(a, b)
    res1 = max2(res,c)
    res2 = max2(res1,d)
    return res2

print(max4(15,523,523,535))

例子:函数内的变量名在外部不能使用

def f1():
    x = 111
    def f2():
        print('from f2')
    print(x)
    print(f2)

f1()
# print(x)  # NameError: name 'x' is not defined
# print(f2)  # NameError: name 'f2' is not defined

# 总结,在函数体内部定义的"变量"只能在自己的函数体内部,被引用,在函数体外则不能被引用

三.名称空间与作用域

名称空间

概念:namespaces名称空间,存放名字的地方,具体来说是名字和内存地址绑定关系的地方

内置名称空间: 存放内置的名字

​ 生命周期:python解释器启动则删除,关闭则销毁

全局名称空间:除了在函数内的名称的名字

​ 生命周期:运行python文件时则产生,python文件运行完毕则销毁

  • x = 1
    if 1 > 0:
        x1 = 2
    while True:
        x2 = 3
    以上的xn都是全局名称 也都是顶级的
    

局部名称空间:存放的是函数内的名字

​ 生命周期:执行函数内的代码则产生,执行完就销毁

核心:名字的访问优先级

​ 基于当前所在的位置向外查找
​ 函数内---> 外层函数 ---> 全局 ---> 内置

案例1:len不同的名称空间的使用

len = 10

def func():
    len = 20
    print(len)

func()  # 20
print(len)  # 10

案例2:执行f1函数,x的值会怎么变化(将函数体代码x的注释打开,从里到外)

def f1():
    # x = 555
    def f2():
        # x = 666
        print(x)

    f2()


x = 444
f1()  # 在执行f1之前全局名称空间中就有x这个名称了

f1()  # 在执行f1的时候,名称空间中还没有x这个名称,报错:NameError: name 'x' is not defined
x = 444

专业术语:LEGB : L ---> 内层函数; E-----> 外部函数; G-----> 全局名称,B---->内置名称

名称空间与作用域的关系是在函数定义阶段(扫描语法)就确立的,与什么时候调用以及调用位置无关

案例1:判断下列函数中x的值打印出来是什么?

x = 111
def f1():
    print(x)  # x只管在哪被定义的
    x = 222

f1()

案例2:当在局部名称空间中改变全局名称空间的可变数据类型时

l = []
def func():
    l.append(1111)  # l表示的是全局名称空间的l

func()
print(l)  # 打印出的全局名称空间的l为[1111]

上面在函数内即局部名称空间改变全局名称空间用的是.append功能,改变的是全局的,但你在局部给他重新赋值看看

l = []
def func():
    l = [11,22,33,44]

func()
print(l)  # []

你会发现并没有 给 全局的l重新附上 对应的 值,原因就是 局部的l=[11,22,33,44]是一个新的l,即是局部的l.

现在有一个需求.就是将全局名称空间的值是一个不可变的类型,我能怎么更改呢???

你可能会试着用 x = xxx 给他重新赋值 或者 字符串的操作,但是你会发现它都不能更改,因为是一个不可变的数据类型,所以python给我们提供了一个global关键字,表示其后面的名称是全局的

案例:将x这个整数111改为222

x = 111

def func():
    global x  # 声明这里用的x是一个全局变量
    x = 222

func()
print(x)  # 222

而对应的也有一个关键字可以声明变量名是来自于外层函数的,不是全局的,这个关键字就是nonlocal

案例:

# nonlocal  # 声明变量名是来自与外层函数的,不是全局
x = 111
def f1():
    x = 222
    def f2():
        # global x # 声明变量名是来自于全局
        nonlocal x  # 声明变量名是来自于外层函数的,不是全局
        x = 333
    f2()
    print(x)
    
f1()  # 333

四.闭包函数=函数对象+函数嵌套+名称空间与作用域

闭包函数就是一个对上面学习的一个汇总.

闭:指的该函数是定义在函数内的函数

包:指的就是该函数引用了一个外层函数作用域的名字

例子1:执行了outter函数就是相当于执行了wrapper(),即在全局名称空间执行到了局部名称空间内的代码

def outter():
    x = 111  # x表示的就是包
    def wrapper():  # wrapper表示就是闭
        print(x)
    return wrapper 

f = outter()
print(f)  # 111

闭包函数其实本质就是为函数体代码传参的一种方式

我们之前都学过,为函数体代码传参最直接的方式就是用参数传

即例子:

def wrapper(x):
    print(x)

wrapper(111)
wrapper(222)
wrapper(333)

那我们还能不能通过别的方式为函数体代码传参呢??

闭包函数就是这样的一种方式

即:案例

def outer(x):
    # x = xxx
    def wrapper():
        print(x)

    return wrapper

f1 = outer(111)
f1()  # 111

f2 = outer(222)
f2()  # 222

装饰器:

1.什么是装饰器

​ 装饰器就是一个用来为被装饰对象添加新功能的工具

2.为何要用装饰器

​ 开放封闭原则:一旦软件上线运行之后,应该对修改源代码封闭,对扩展功能开放

原则:
1、不修改函数内的源代码
2、不修改函数的调用方式
装饰器就是在遵循原则1和2的前提下,为被装饰对象添加上新功能

3、如何实现装饰器

我们先不说如何实现,我现在有一个需求:为函数index添加统计运行时间的功能

import time

def index():
    time.sleep(1)  # 模拟功能在做一些io操作
    print('from index')

index()

是不是很简单,那我现在又接收到了一个需求:让我们给index函数在不动它函数体代码的情况下,为其添加计时功能?

这时候你可能会问???我为什么要不动函数体代码.是的,一般我们也正是如此,但有时候啊,你一个软件以及上线了,你如果要直接该源代码的话,你可能会因为你一改,改对了还好,该运行的还正常运行,当时当你改错了,你会发现,你有多个地方已经改了,你的程序有可能就直接崩了.而原代码还被你给改了,这就导致了你程序要进入维修状态了.

而装饰器呢就可以解决不改变函数体源代码的情况下,为其添加功能.

居然装饰器这么强大,那我们赶快学习吧.

不要着急.我们在学习的前提,我们看看我们对函数index加上计时功能的几种解决方案

方案一:在index函数的函数体代码内添加功能代码

# 代码示范
import time

def index():
    start = time.time()
    time.sleep(1)
    print('from index')
    stop = time.time()
    print('run time is %s' %(stop - start))

index()

你会发现,是的你已经添加了计时功能了,但是有一个弊端就是动了函数体代码.

方案二:在调用函数时,再为其加上计时功能

import time

def index():
    time.sleep(1)
    print('from index')

start = time.time()
index()
stop = time.time()
print('run time is %s' %(stop - start))

你会说这时候我没改函数体代码了吧,但是你这时候又犯了另外一个弊端,你要在每次执行index的时候带上对应的功能代码,重复代码可不是一个好的习惯.

此时你又可以想到,我可以将下面的也放到一个函数内啊,每次执行新的函数不就好啦!

也就是方案三:将index函数放到另外一个函数内,添加计时功能,再调另外一个函数保证添加计时功能

import time

def index():
    time.sleep(1)
    print('from index')

def wrapper():
    start = time.time()
    index()
    stop = time.time()
    print('run time is %s' %(stop - start))

wrapper()
wrapper()

但你有没有发现你的调用方式又变了,你又得去,将每个调用index的改成调用wrapper了.

我们根据上面的方案三,我又有一个需求,将计时添加到多个功能上面

所以就有了方案四:将计时功能添加到多个函数上面

import time

def index():
    time.sleep(1)
    print('from index')

def wrapper(func):
    start = time.time()
    func()
    stop = time.time()
    print('run time is %s' %(stop - start))

wrapper(index)

但是你还是没满足我的不改变调用方式的需求:

这时候你就会想到你学过的闭包了

案例5,将计时功能添加到别的功能上,并且不改变功能调用的形式

提示:案例5最好自己实现.

import time


def index():
    time.sleep(1)
    print('from index')


# wrapper 是 一个 闭包函数 , 闭的是 wrapper函数 即 timer函数内部定义的一个函数, 包指的是wrapper函数引用了外部函数timer的局部名称,即作用域名字==>func
def timer(func):
    """
    使wrapper函数变成即使是一个局部的名称空间,但是在全局也可以调用
    :param func: func 即要加上 该功能的 函数
    :return: 返回的就是wrapper的内存地址---> 加() 即可以执行内部代码
    """

    def wrapper():
        """
        作用是为func函数加上计时功能
        :return: None
        """
        start = time.time()
        func()
        stop = time.time()
        print('run time is %s' % (stop - start))

    return wrapper


index = timer(index)  # 即将index这个函数当作实参传给了timer, 拿到wrapper函数的内存地址.再将内存地址绑定了一个名称为index
index()  # 执行了index 内存地址对应的函数代码体  注意:此时这个index 对应的内存地址是timer返回的内存地址

这时候,你就已经满足了我的所以需求了.不仅加了功能还没改变调用方式

先给你留个悬念:其实在python中index = timer(index),还有一个简单的使用方式.明天再讲.

posted on 2020-12-29 17:09  Jkeykey  阅读(176)  评论(0编辑  收藏  举报