Python基础globlal nonlocal和闭包函数装饰器语法糖

Python
286
0
0
2023-06-11
标签   Python基础
目录
  • 一、global与nonlocal
  • 1、global
  • 2、nonlocal
  • 二、函数名的多种用法
  • 三、闭包函数
  • 1、什么是闭包函数
  • 2、闭包函数需满足的条件
  • 3、闭包函数的作用
  • 4、闭包函数的实际应用
  • 四、装饰器
  • 1、装饰器推导流程
  • 2、装饰器语法糖
  • 3、装饰器模板

一、global与nonlocal

1、global

  • 在py文件中,一般无法调用函数体内变量名,而global可使函数体代码内的变量名直接在函数体外部调用,条件是在需要调用的代码体中使用global 调用需要的变量名

未使用global情况:

# 在外部绑定一个变量名
name = 'kangkng'
# 定义一个函数体代码
def func():
    # 函数体内部重新绑定一个变量名
    name = 'zhangzhang'
# 调用函数func
func()
# 这时无法打印函数体内部的变量名
print(name)
------------------------------------------------------------------------ kangkang   

使用global情况:

# 在函数体内部使用global时,可在外部直接调用函数内部变量名
name = 'kangkng'
# 定义一个函数体代码
def func():
    # 使用global调用变量名
    global name
    # 函数体内部重新绑定一个变量名
    name = 'zhangzhang'
# 调用函数func
func()
# 外py中打印name
print(name)
------------------------------------------------------------------------
zhangzhang

2、nonlocal

  • 在函数嵌套使用时,通常在父代码体中无法调用子代码中变量名,

而nonlocal的作用是,可以在父代码中直接调用子代码中的变量名,条件是需要在子代码中使用nonlocal 调用需要的变量名

未使用nonlocal情况:

# 定义一个函数体代码
def outer():
    # 绑定一个变量名
    name = 'kangkang'
    # 代码体内部再次定义一段函数体
    def subcoat():
        # 内层中绑定变量名
        name = 'zhangzhang'
    # 在函数外层打印变量名
    print(name)
# 调用外层函数体代码
outer()
----------------------------------------------------------------------- 
kangkang

使用nonlocal情况:

# 在函数体内部使用global时,可在外部直接调用函数内部变量名
def outer():
    # 函数外层绑定一个变量名
    name = 'kangkang'
    # 代码体内部再次定义一段函数体
    def subcoat():
        # 在函数体内层使用nonlocal,调用变量名
        nonlocal name
        # 内层中绑定变量名
        name = 'zhangzhang'
    # 调用内层函数
    subcoat()
    # 在函数外层打印变量名
    print(name)
# 调用外层函数体代码
outer()   
----------------------------------------------------------------------
zhangzhang

二、函数名的多种用法

引言:

​ 函数名就相当于变量名,只不过函数名绑定的是一段函数体代码,在我们使用这个函数名加括号时就可以调用这段代码体,具体由以下几种用法:

1、当做变量名赋值

def func():
    print('我是func函数体代码')
res = func
print(res())
------------------------------------------------------------------------
我是func函数体代码
None

2、当作函数的参数

def func():
    print('我是func函数体代码')
def func(a):
    print('我是func函数体代码', a)
    a()
func(func)
------------------------------------------------------------------------
我是func函数体代码 <function func at 0x000001D0C14D6310>
我是func函数体代码

3、当作函数的返回值

def func():
    print('我是func函数体代码')
def func():
    print('我是func函数体代码')
    return func
res = func()
print(res)
res()
------------------------------------------------------------------------
我是func函数体代码
<function func atx00000218F95B6310>
我是func函数体代码

4、当作容器类型的数据

def spring():
    print('我是春季,生机盎然')
def summer():
    print('我是夏季,活力四射')
def autumn():
    print('我是秋季,翩翩起舞')
def winter():
    print('我是冬季,大雪纷飞')
while True:
    season_dict = { '': spring,
               '': summer,
               '': autumn,
               '': winter
                   }
    season_select = input('根据编号,选择您喜欢的季节>>>:').strip()
    if season_select in season_dict:
        season_dict.get(season_select)()
    else:
        print('你选择的编号不存在')
------------------------------------------------------------------------

三、闭包函数

1、什么是闭包函数

 一个函数的返回值是另外一个函数,返回的函数调用父函数内部的变量,如果返回的函数在外部被执行,就产生了闭包

2、闭包函数需满足的条件

满足以下两个条件的就是闭包函数:

条件一:定义在函数内部的函数

条件二:用到了外部函数空间名称中的名子

3、闭包函数的作用

作用:使函数外部能够调用函数内部放入属性和方法

缺点:闭包操作导致整个函数的内部环境被长久保存,占用大量内存

4、闭包函数的实际应用

1.函数内部变量名在外部被访问

def fun():
    name = 'python'
    def inner():
        print(name)
    return inner
result = fun()
result()
------------------------------------------------------------------------
python

2.函数体内部函数体代码可以通过外部访问

def fun():
    def inner():
        print("执行了内部函数inner")
    def all():
        return inner
    return all
result = fun()
result()()
------------------------------------------------------------------------
执行了内部函数inner

四、装饰器

​ 当我们需要将一段函数体代码在不改变调用方式和源代码的情况下,需要给这个段代码添加新的功能时,这时候我们就需要给这段代码安装一个装饰器,装饰器是指将这段代码封装在闭包函数内,来达到既能满足上述条件,又能增加新的功能的条件

概念

  • 在不修改被装饰对象源代码和调用方式的情况下给被装饰的对象添加新的功能

本质

  • 并不是一门新的技术,而是由函数参数、名称空间、函数名多种用法、闭包函数组合到一起的效果

口诀

  • 对修改封闭,对扩展开放

1、装饰器推导流程

1、首先定义一段函数体代码,当我们给这段函数传入指定的参数时,他就会暂停一秒,然后运行,使它在运行结束后,能够统计它的运行时间

import time
def index(a, b):
    time.sleep()
    print(index,a, b)

2、通常,我们只需要在这段代码运行前打印一个时间戳,运行后再次打印一个时间戳,在这段代码运行结束后通过前后时间的插值就能统计出这段代码的运行时间,但这种办法使用起来比较麻烦且只能使用一次

方法一:
    import time
    def index(a, b):
        start = time.time()
        time.sleep()
        print(index, a, b)
        end = time.time()
        print(end - start)
    index(,2)
方式二:
    import time
    def index(a, b):
        time.sleep()
        print(index, a, b)
    start = time.time()
    index(,2)
    end = time.time()
    print(end - start)

3、通过上述方法的方式二,我们可以得出将函数名包裹在统计时间功能代码内,这样在调用时相对便捷,进一步思考,若将这段代码使用函数封包,那样在调用时就可以更为便捷,在以后统计该代码时,只需要调用封包这段代码的函数名就可以直接统计这段代码的运行时间

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
def time_():
    start = time.time()
    index()
    end = time.time()
    print(end - start)
time_()
------------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/pytcharm项目文件路径//11.py", line 297, in <module>
    time_()
  File "D:/pytcharm项目文件路径//11.py", line 293, in time_
    index()
TypeError: index() missing required positional arguments: 'a' and 'b'

4、虽然这种方式可以行得通,但只能针对没有参数的函数体代码,若这段代码需要传参者无法运行,并直接报错。再次进一步思考,只需要将封包的这段函数设置为有参函数就可解决这个问题

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
def core(a,b):
    start = time.time()
    index(a, b)
    end = time.time()
    print(end - start)
core(, 2)
------------------------------------------------------------------------
<function index atx000001F4A0026310> 1 2
.0047826766967773

5、由上推导可看出,虽然此功能可以更为便捷的统计代码执行时间,但若是源代码的参数需要修改则封包它的参数也需要修改,这时我们可联想到将参数修改为可变长参数,就不会出现这个问题

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
def core(*args,**kwargs):
    start = time.time()
    index(*args, **kwargs)
    end = time.time()
    print(end - start)
core(,2)
------------------------------------------------------------------------
<function index atx000002ECDD4E6310> 1 2
.004744529724121

6、这样无论源代码参数如何修改,我们都可以进行传参,虽然这个问题解决了,但考虑使用的广泛性,若有其他函数体也需要用到这个功能时,还需要重新修改封包内代码,这时,我们可以使用闭包的方式来满足这个条件

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
def func(x, y, z):
    time.sleep()
    print(func, x, y, z)
def outer(index):
    def core(*args, **kwargs):
        start = time.time()
        index(*args, **kwargs)
        end = time.time()
        print(end - start)
    return core
res = outer(func)
res(, 2, 3)
------------------------------------------------------------------------
<function func atx0000018C23686670> 1 2 3
.00856614112854

7、通过将源代码函数名放至闭包函数参数内,就可以达到可以调动任何函数体代码都可以执行此功能的方法,但并未满足闭包函数的条件,源代码的调用方式改变了,这时我们可以通过将原函数体代码赋值的方式来达到调用方式和源代码都未改变的情况下来增加此功能

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
def func(x, y, z):
    time.sleep()
    print(func, x, y, z)
def outer(index):
    def core(*args, **kwargs):
        start = time.time()
        index(*args, **kwargs)
        end = time.time()
        print(end - start)
    return core
index = outer(index)
index(,2)
func = outer(func)
func(, 2, 3)
------------------------------------------------------------------------
<function outer.<locals>.core atx0000026C17F58280> 1 2
.004807710647583
<function outer.<locals>.core atx0000026C17F58940> 1 2 3
.0077626705169678

8、虽然上述推导过程都已满足装饰器条件,但是考虑到源代码有返回值的情况,我们没有并没有获取,这时在进一步推导,可在装饰器函数内部调用源代码函数名的位置设置一个变量名用于接收返回值,传给装饰器底层return用于接收即可解决这个问题

import time
def index(a, b):
    time.sleep()
    print(index, a, b)
    return 'index'
def func(x, y, z):
    time.sleep()
    print(func, x, y, z)
    return 'func'
def outer(index):
    def core(*args, **kwargs):
        start = time.time()
        res = index(*args, **kwargs)
        end = time.time()
        print(end - start)
        return res
    return core
index = outer(index)
res = index(,2)
print(res)
func = outer(func)
res = func(, 2, 3)
print(res)
------------------------------------------------------------------------
<function outer.<locals>.core atx0000020C50A78280> 1 2
.0050580501556396
index
<function outer.<locals>.core atx0000020C50A78940> 1 2 3
.0094454288482666
func

2、装饰器语法糖

什么是装饰器语法糖

当我们使用装饰器调用被装饰的函数体代码时,总是需要在调用前通过赋值的方式来调用,这样的方式相对比较麻烦,这时我们就可以用到装饰器语法糖来节省时间和代码

语法糖的使用方法和条件

用法:在源代码函数体上方使用@加装饰器函数名

条件:源代码需在装饰器下方

具体用法

import time
def outer(index):
    def core(*args, **kwargs):
        start = time.time()
        res = index(*args, **kwargs)
        end = time.time()
        print(end - start)
        return res
    return core
@outer
def index(a, b):
    time.sleep()
    print(index, a, b)
    return 'index'
index(,2)

3、装饰器模板

def outer(func):
    def inner(*args, **kwargs):
        # 执行被装饰对象之前可以做的额外操作
        res = func(*args, **kwargs)
        # 执行被装饰对象之后可以做的额外操作
        return res
    return inner