Python基础第七天——global与nonlocal、闭包函数、装饰器 一、拾遗  二、global与nonlocal的补充 三、 闭包函数  四、装饰器

鸡汤:

突破Python基础第五、六天,算是正式上路了!路漫漫其修远兮 吾将上下而求索!持之以恒才是胜利的关键,加油,奔跑吧小白!

1、函数的返回值

语法:

  在函数内部后面加上:“return 值”

特点:

  (1)一次return可以返回一个或多个值,且返回值没有类型限制,返回多个值时以逗号作为分隔符隔开

  (2)返回的一个值是字符串的形式,返回多个值是以元组的形式存储

  (3)如果不写return,默认返回:None

  (4)一个函数中若有多个rentun,只能返回第一个return,函数就终止

  

2、函数参数的使用

函数的参数分为两大类:形参实参

(1)形参特点和种类:

  <1>特点:

        形参在定义阶段定义,调用时需要接收实参传过来的值

  <2>种类:

      I、位置形参特点: 必须被传值,不能多传也不能少传

      II、默认参数特点: 在定义阶段已为形参赋值,定义阶段有值,调用阶段可以不传值。    例:def (x,y=1):中的"y=1"就是默认形参,通常情况下把固定不变或很少变的参数作为默认参数。

      III、*args:      特点:用来接收实参传过来的可变长的位置实参*用来接收实参传过来的可变长按位置定义的参数,然后赋值给变量名args

      IV、**kwargs特点: 用来接收实参传过来的可变长的关键字实参**

      V命名关键字参数

             特点:*后面的参数,这些参数必须要被传值,且实参必须以关键字的形式传值

                在 *args  后面的参数也是命名关键字参数,只是 *args 可以接收从实参传过来的任意长度的的位置实参。

  <3>形参种类在定义时的位置排序:

      (位置形参,默认参数,*args,命名关键字参数,**kwargs)

      ------------------------------------------------------------->

              从左至右排序排序

 

(2)实参特点和种类:

  <1>特点:

        实参在调用阶段时定义,调用时要传入的值。简单地说,实参就是给形参传值的

  <2>种类:

        I、位置实参:    特点:位置传值,必须与形参一 一对应

      II、关键字实参特点:按关键字以“key=value”的形式传值

  <3>实参种类在定义时的位置排序:

        (位置实参,关键字实参)

        ------------------------>

        从左至右排序排序

(3)形参与实参的关系:

形参相当于变量名,实参相当于变量的值,在python中变量的定义都没有储值的功能,所以它们两是一种绑定的关系。这种绑定关系在函数调用时生效,函数调用结束后失效

(4)*args和**kwargs的作用:

<1>在定义阶段:

两者一起连用表示能接收从实参传过来的任意长度、任意形式的参数。

*接收任意长度的参数后以元组的形式保存并赋值给变量名:args

**接收任意形式的参数后以字典的形式保存并赋值给变量名:kwargs

<2>在调用阶段:

*args表示将传进去的这些参数全都拆开,拆成按位置的形式,如:(1,2,3,4,5)

**kwargs表示将传进去的这些参数全都拆开,拆成按关键字的形式,如(a=1,b=2,c=3)

3、函数的嵌套

分为两种:

(1)函数的嵌套调用

定义:在函数内部嵌套调用其它函数

(2)函数的嵌套定义

定义:在函数内部使用def关键字再定义一个函数

4、函数对象

定义:函数可以被当作数据去处理。

(1)可以被引用

(2)可以当作参数传入

(3)可以当作函数的返回值

(4)可以当作容器类的元素

5、名称空间与作用域(global和nonlocal)

(1)名称空间

定义:存放名字和值的绑定关系

<1>种类:

内置名称空间:python解释器一启动就会产生。

全局名称空间:python解释器开始执行文件的时候就会产生。

局部名称空间:执行文件的过程中可能会调用到函数,调用函数则会产生局部名称空间,它是临时产生的,调用结束则失效。

 <2>三种名称空间按产生顺序排序

内置名称空间——>全局名称空间——>局部名称空间

 <3>三种名称空间查找名字的顺序排序

局部名称空间——>全局名称空间——>内置名称空间

(2)作用域

定义:生效范围

分类按照每个名称空间的作用范围不同分成了两个区域:

<1>全局作用域:

  定义:定义在全局作用域名字叫全局变量。包含内置名称空间和全局名称空间

  特点:在文件的任何位置都可以被引用

  失效:文件结束完了或del直接干掉了

<2>局部作用域:

  定义:定义在局部作用域的名字叫局部变量,局部名称空间

  特点:只能在局部使用,不能从全局作用域看局部作用域的名字,但是能在局部作用域看全局作用域的名字

<3>全局作用域和局部作用域按查找顺序排序

  局部作用域——>全局作用域

  补充:

    即使当前位置是全局作用域,也要按照以上的顺序来,因为全局作用域的局部作用域仍然是全局作用域

   查看局部作用域的名字:locals

   查看全局作用域的名字:globals

 

 

 二、global与nonlocal的补充

1、global

作用:局部作用域修改名字,会在全局作用域的名字中找

函数内加上global关键字它会一下从最里层跳到最外一层找全局的了

例1:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

例2:

要求:在局部作用域中修改全局作用域的名字 (不推荐

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

  上述编程风格在实际工作中一定要尽可能要避免,因为在局部进行更改会导致全局也跟着改变,这样很容易改乱。

  例如,抢票软件。假设一张车票,有几十万人同时在线,大家都发现了这张票,(读一个全局的变量,这几十万人都能读到),于是这几十万人便开始抢,都把这张票加到购物车后付钱,(此时相于每个人的后台都要减去这一张票,这就相当于大家都在改一个值)最快速度付完款的人则能抢到票,但是在最快抢到票的那段时间内,同一时间这几十万人当中可能同时不止一个人抢到了票,可能会多个人同时抢到这张票,这时就会出现一张票被多人抢到手的问题了。(相当于同一时间内有多人改1个值

  为了避免以上问题,解决的方法是:不要使用全局的变量,要使用局部变量

 

2、nonlocal

作用:局部作用域内修改名字,只在函数内部找

函数内加上nonlocal关键字它会找当前层的上一层。

例1:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

例2:若将以上f2函数中“x”注释掉,nonlocal则会继续去再上一层的函数中找变量"x",

本例得到的结果仍然是"from f2 function",那是因为f1函数体中的“x”值只是定义而没有加打印,而且调用了f2函数体中的print('from f2 function',x)这段代码,所以才会得到“from f2 function,xiaobai“

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 例3:若将函数中所有的变量x都注释掉,则会报错,提示变量"x"未绑定

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

三、 闭包函数

1、定义:

函数内部定义函数,称为内部函数

该内部函数对外部作用域,而不是对全局作用域名字的引用

闭包函数的格式:

def 外部函数名():

  内部函数需要的变量

  def 内部函数():

    引用外部变量

  return 内部函数

例1:

本例中的闭包函数就是返回的foo函数名以及附带的x=2的值,把调用func函数的结果赋值给变量名f,此时变量“f”又是一个全局的变量,也就是此时的func函数能在任意位置被调用了

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 例2:

补充:即使以下闭包函数中的foo函数不加return也算是闭包函数,只要这个函数是内部函数,且对外部函数的引用就是闭包函数。但是为了调用时不受层级限制,通常函数函数与return一起使用。

将例1中的闭包函数进行调用

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

2、闭包函数的特点

(1)特性:

闭包函数永远自带着自己的状态,可以被赋值给变量名,调用时不受函数定义时的层级限制

(2)验证函数是否为装饰函数:__closure__

验证一个函数是否为闭包函数则用“__closure__”来验证。(closure的中文意思就是闭包)

闭包函数中不一定要有return返回值,但是为了调用时不受层级限制,通常要与return一起使用,只需要将这个函数名后加上.__closure__查看结果就可判断是否为闭包函数,如果打印的结果有值,则为闭包函数

(3)取值:

通过“cell_contents”得到闭包中的值。

 例1:闭包中附带1个值

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

例2:闭包中附带多个值

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 例3:内部函数引用的全局变量则不称为闭包函数,此时用“__closure__”验证得到“None”

以下是非闭包函数

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 (4)补充:

补充1:凡是在函数内部看到有调用时,一定要看去找这个被调用函数定义时的位置。

例:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

补充2:在闭包函数中加上nonlocal关键字时,这个函数仍然是闭包函数,只是这可以在闭包函数内部对外面一层的函数里的值进行修改

例:包两层的情况

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 3、多层闭包函数

思路:想让一个函数保存一个状态,不断地包它就行了,包一层外面定义一个变量,把这个它包在一个函数内部,想要基于这个函数的基础之上再往里面包内容则再包一层,再把那个函数返回来

例:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

4、闭包的应用场景

闭包函数可应用于爬虫项目。

什么是爬虫?

答:爬虫是通过一个链接把网站的内容下载至本地,然后通过一些规则(如正则表达式)提取对自己有用的信息。

例:写一个爬虫的雏形项目

通过urlopen模块可以向网页发起请求将代码下载至本地

from urllib.request import urlopen

(1)爬虫的基本原理

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

(2)把输入的链接用函数做成一个功能,通过传参的方式统一定义。

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

(3)为了省去记网址的麻烦,则将程序改为闭包函数。将这种多次访问同一个链接地址称为一种状态。这种状态可以通过闭包的形式保存起来,此时调用函数时不必传参,当要用到时只需要加上括号即可。

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

(4)以上步骤中的url变量里的网址是写死的,如果要分别爬多个网站,则需要修改源代码,为了避免修改源代码,将以上代码修改为:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

 四、装饰器

1、定义:

依照目前所学的知识可以将装饰器理解为以下定义:

  装饰器本身就是函数且是可以任意调用的对象,被装饰的对象也是任意可调用对象。

  (可调用对象:加括号就能运行的函数)

通俗地说装饰器就是闭包函数的一种应用而已。

装饰器的目的是,给被装饰对象添加新功能,并且不让用户察觉感觉到装饰器的存在。

2、特性:

装饰器遵循开放封闭原则 (即不修改被装饰对象的源代码以及调用方式)为被装饰对象添加新功能

开放封闭原则:对扩展是开放的,对修改是封闭的(所有软件开发都要遵循这个原则)

以上这句话的详细意思是:

  例如写一个软件,不可能一次性地把功能都写全了再上线运行,一定会根据客户需要将软件进行功能扩展,这就是对扩展开放。

  对软件源代码是不能修改的,因为这些源代码在函数里时,定义阶段可能会被许多位置使用到,这就意味着一旦把某个函数中的源代码改了,影响的可能是全局,引起连锁反应。除非你能保证不会影响全局的情况下才能进行对源代码修改。

例:给一个函数的加上统计运行时间的功能

# 要求:给index函数加上统计index函数的运行时间
import time     # 时间模块
import random   # 随机模块

# 装饰器
def timmer(func):
    # func = index
    def wrapper():
        start_time = time.time()    # time.time()表示显示当前时间
        func()
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
    return wrapper

# 被装饰对象
def index():
    time.sleep(random.randrange(1,4))   # 表示随机取1-4中的任意一个数
    print('welcome to index page')



index = timmer(index)  # timmer(index)表示把被装饰对象传给timmer装饰器
index()

图示:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

3、装饰器的流程解析:

例:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

4、装饰器的语法:

在被装饰对象的正上方单独写一行@装饰器名

例:将上面的装饰器使用装饰器语法

# 要求:给index函数加上统计index函数的运行时间
import time
import random

# 装饰器
def timmer(func):
    # func = index
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
    return wrapper



# 被装饰对象1
@timmer # 装饰器语法,相当于 index = timmer(index)
def index():
    time.sleep(random.randrange(1,4))
    print('welcome to index page')

# 被装饰对象2
@timmer # 装饰器语法,相当于 home = timmer(home)
def home():
    time.sleep(random.randrange(1,3))
    print('welcome to home page')


index()
home()

输出结果:

welcome to index page
run time is 1.0005803108215332
welcome to home page
run time is 1.0001659393310547
View Code

5、我自已理解的装饰器原理。

def  装饰器名(形参名):             # 装饰器中的形参是用来接收值的,这个传过来的值是被装饰对象的函数名

  def  内部函数名():

    装饰器功能代码....

    被装饰对象名()              # 调用被装饰对象,被装饰对象的名字与装饰器括号内的形参名字是一致的。

    装饰器功能代码....

  return  内部函数名

@ 装饰器名          # 装饰器语法:相当于被装饰对象名 = 装饰器名(被装饰对象名)

def 被装饰对象名():

  函数体

  ......

被装饰对象名()            # 调用被装饰对象

6、多个装饰器同时被使用

执行程序的流程是从上往下依次执行的。

而多个装饰器同时使用时,则是从下往上执行的顺序依次计算的。按排序最上面的装饰器先执行最下面的装饰器先计算。对我们有用的就是执行!

例1:

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

例2:多个装饰器的执行流程

import time        # 1      执行顺序由数字说明
import random      # 2
# 装饰器
def timmer(func):  # 3
    def wrapper(): # 7
        start_time = time.time()
        func()
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
    return wrapper # 8

def auth(func):   # 4
    def deco():  # 9
        username = input("Please input your username:")    # 12
        password = input("Please input your password:")
        if username == 'xiaobai' and password == '123456':
            print('Login successful!')
            func()
        else:
            print('Login error!')
    return deco    # 10
# 被装饰对象
@auth           # 5   将加上wrapper闭包的index当作参数传入auth函数内,返回deco函数,此时得到deco的闭包,再赋值给index
@timmer         # 6  将index当作参数传入timmer函数,返回wrapper函数,此时得到wrapper的闭包,再赋值给index
def index():
    time.sleep(3)
    print('welcome to index page')
index()    # 11

 7、有参装饰器

(1)例:

# 有参装饰器
import time
import random
#装饰器
def timmer(func):
    def wrapper(name):
        start_time = time.time()
        func(name)
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
    return wrapper

# 装饰器语法
@timmer
# 被装饰对象
def home(name):
    time.sleep(random.randrange(1,3))
    print('Welcome to %s home page'%name)

# 调用阶段
home('xiaobai')

Python基础第七天——global与nonlocal、闭包函数、装饰器
一、拾遗
 二、global与nonlocal的补充
三、 闭包函数
 四、装饰器

(2)装饰器接收任意长度的参数

被装饰对象有多个,且有的是无参函数,有的是有参函数时:

在装饰器的接收位置加上*args,**kwargs可接收任意长度的参数,

在装饰器内调用被装饰对象时也要加上*args,**kwargs

 例2:

import time
import random
#装饰器
def timmer(func):
    def wrapper(*args,**kwargs):    # 不管被装饰对象是否有参数,用*args和**kwargs来接收传过来的任意长度的参数
        start_time = time.time()
        func(*args,**kwargs)        # 怎么接收的就要怎么给。
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
    return wrapper


# 装饰器语法
@timmer
# 被装饰对象1——有参函数
def home(name):
    time.sleep(random.randrange(1,3))
    print('Welcome to %s home page'%name)


# 装饰器语法
@timmer
# 被装饰对象2——无参函数
def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')


# 调用阶段
home('xiaobai')
index()

(3)当被装饰对象有返回值时

例:

import time
import random

#装饰器
def timmer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res = func(*args,**kwargs)       # 调用被装饰对象并赋值给res
        stop_time = time.time()
        print('run time is %s'%(stop_time - start_time))
        return res                      # 返回res,如果被装饰对象中有返回值,则原封不动地返回,如果没有则返回None
    return wrapper

# 装饰器语法
@timmer
# 被装饰对象2——无参函数
def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')


# 装饰器语法
@timmer
# 被装饰对象1——有参函数
def home(name):
    time.sleep(random.randrange(1,3))
    print('Welcome to %s home page'%name)
    return 'I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'



# 调用阶段
res1 = index()          # 此时的index不是原始的index,而是调的wrapper函数
print('index return %s'%res1)
res2 = home('xiaobai') # 此时的home不是原始的home,而是调的wrapper函数
print('home return %s'%res2)

输出结果:

welcome to index page
run time is 2.0005133152008057
index return None
Welcome to xiaobai home page
run time is 1.0001530647277832
home return I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
View Code