day13-14 函数对象与装饰器

Posted Dream-Z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了day13-14 函数对象与装饰器相关的知识,希望对你有一定的参考价值。

【三】名称空间与闭包

【1】名称空间

  • 名称空间即存放名字与对象映射/绑定关系的地方。
    • 对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系。
  • 在程序执行期间最多会存在三种名称空间
(1)内建名称空间
  • 伴随python解释器的启动/关闭而产生/回收
    • 因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内建函数名
>>> max
<built-in function max> #built-in内建
(2)全局名称空间
  • 伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中,如下名字
import sys #模块名sys

x=1 #变量名x

if x == 1:
    y=2 #变量名y

def foo(x): #函数名foo
    y=1
    def bar():
        pass

Class Bar: #类名Bar
	pass
(3)局部名称空间
  • 伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中
def foo(x):
    y=3 #调用函数时,才会执行函数代码,名字x和y都存放于该函数的局部名称空间中

名称空间的加载顺序是:

​ 内置名称空间->全局名称空间->局部名称空间,

而查找一个名字,必须从三个名称空间之一找到,查找顺序为:

​ 局部名称空间->全局名称空间->内置名称空间。

【2】作用域

(1)全局作用域与局部作用域

按照名字作用范围的不同可以将三个名称空间划分为两个区域:

  1. 全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围,该范围内的名字全局存活(除非被删除,否则在整个文件执行过程中存活)、全局有效(在任意位置都可以使用);
  2. 局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)。
(2)作用域与名字查找的优先级
  • 在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,没有找到
    • 再去全局作用域查找:先查找全局名称空间,没有找到
      • 再查找内置名称空间,最后都没有找到就会抛出异常
x=100 #全局作用域的名字x
def foo():
    x=300 #局部作用域的名字x
    print(x) #在局部找x
foo()#结果为300
  • 在全局作用域查找名字时,起始位置便是全局作用域
    • 所以先查找全局名称空间,没有找到
      • 再查找内置名称空间,最后都没有找到就会抛出异常
x=100
def foo():
    x=300 #在函数调用时产生局部作用域的名字x
foo()
print(x) #在全局找x,结果为100

提示:

​ 可以调用内建函数locals()和globals()来分别查看局部作用域和全局作用域的名字,查看的结果都是字典格式。

​ 在全局作用域查看到的locals()的结果等于globals()

  • Python支持函数的嵌套定义,在内嵌的函数内查找名字时,会优先查找自己局部作用域的名字
    • 然后由内而外一层层查找外部嵌套函数定义的作用域
      • 没有找到,则查找全局作用域
x=1
def outer():
    x=2
    def inner(): # 函数名inner属于outer这一层作用域的名字
        x=3
        print(\'inner x:%s\' %x)

    inner()
    print(\'outer x:%s\' %x)

outer() 
#结果为
inner x:3
outer x:2
  • 在函数内,无论嵌套多少层,都可以查看到全局作用域的名字
    • 若要在函数内修改全局名称空间中名字的值
      • 当值为不可变类型时,则需要用到global关键字
x=1
def foo():
    global x #声明x为全局名称空间的名字
    x=2
foo()
print(x) #结果为2
  • 当实参的值为可变类型时
    • 函数体内对该值的修改将直接反应到原值,
num_list=[1,2,3]
def foo(nums):
    nums.append(5)

foo(num_list)
print(num_list)
#结果为
[1, 2, 3, 5]
  • 对于嵌套多层的函数
    • 使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局)
def  f1():
    x=2
    def f2():
        nonlocal x
        x=3
    f2() #调用f2(),修改f1作用域中名字x的值
    print(x) #在f1作用域查看x

f1()

#结果
3
  • nonlocal x 会从当前函数的外层函数开始一层层去查找名字x
    • 若是一直到最外层函数都找不到,则会抛出异常。

【四】函数对象与闭包

【1】函数对象

  • 函数对象指的是函数可以被当做 数据 来处理,具体可以分为四个方面的使用
(1)函数可以被引用
>>> def add(x,y):
...     return x+y
... 
>>> func=add
>>> func(1,2)
3
(2)函数可以作为容器类型的元素
>>> dic=\'add\':add,\'max\':max
>>> dic
\'add\': <function add at 0x100661e18>, \'max\': <built-in function max>
>>> dic[\'add\'](1,2)
3
(3)函数可以作为参数传入另外一个参数
>>> def foo(x,y,func):
...     return func(x,y)
...
>>> foo(1,2,add)
3
(4)函数的返回值可以是一个函数
>>> def bar():
...     return add
...
>>> func=bar()
>>> func(1,2)
3

【2】闭包函数

(1)闭与包
  • 基于函数对象的概念,可以将函数返回到任意位置去调用
    • 但作用域的关系是在定义完函数时就已经被确定了的,与函数的调用位置无关。
x=1

def f1():
    def f2():
        print(x)

    return f2

def f3():
    x=3
    f2=f1() #调用f1()返回函数f2
    f2() #需要按照函数定义时的作用关系去执行,与调用位置无关

f3() #结果为1
  • 也就是说函数被当做数据处理时,始终以自带的作用域为准。
    • 若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用
      • 那么该’内嵌函数’就是闭包函数,简称闭包(Closures)
x=1
def outer():
    x=2
    def inner():
        print(x)
    return inner

func=outer()
func() # 结果为2
  • 也就是说函数被当做数据处理时,始终以自带的作用域为准。
    • 若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该’内嵌函数’就是闭包函数,简称闭包(Closures)
x=1
def outer():
    x=2
    def inner():
        print(x)
    return inner

func=outer()
func() # 结果为2
  • 可以通过函数的closure属性,查看到闭包函数所包裹的外部变量
>>> func.__closure__
(<cell at 0x10212af78: int object at 0x10028cca0>,)
>>> func.__closure__[0].cell_contents
2
  • “闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。
    • 因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。
(2)闭包函数的用途
  • 目前为止,我们得到了两种为函数体传值的方式
    • 一种是直接将值以参数的形式传入
    • 另外一种就是将值包给函数
import requests

# 方式一:
def get(url):
    return requests.get(url).text

# 方式二:
def page(url):
    def get():
        return requests.get(url).text
    return get

提示:requests模块是用来模拟浏览器向网站发送请求并将页面内容下载到本地,需要事先安装:pip3 install requests

  • 对比两种方式
    • 方式一在下载同一页面时需要重复传入url
    • 方式二只需要传一次值,就会得到一个包含指定url的闭包函数,以后调用该闭包函数无需再传url
# 方式一下载同一页面
get(\'https://www.python.org\')
get(\'https://www.python.org\')
get(\'https://www.python.org\')
……

# 方式二下载同一页面
python=page(\'https://www.python.org\')
python()
python()
python()
……
  • 闭包函数的这种特性有时又称为惰性计算。
    • 使用将值包给函数的方式,在接下来的装饰器中也将大有用处

【五】装饰器

【1】装饰器介绍

(1)装饰器的用途
  • 软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。

    • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    • 对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
  • 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃

    • 而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
(2)装饰器的介绍
  • 装饰 代指为被装饰对象添加新的功能, 代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。
  • 概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
  • 装饰器经常用于有切面需求的场景
    • 比如:
      • 插入日志、性能测试、事务处理、缓存、权限校验等应用场景
      • 装饰器是解决这类问题的绝佳设计
      • 有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。

【2】装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。

(1)无参装饰器的实现
  • 如果想为下述函数添加统计其执行时间的功能
import time

def index():
    time.sleep(3)
    print(\'Welcome to the index page’)
    return 200

index() #函数执行
  • 遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样
start_time=time.time()
index() #函数执行
stop_time=time.time()
print(\'run time is %s\' %(stop_time-start_time))
  • 考虑到还有可能要统计其他函数的执行时间
    • 于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用
    • 我们可以使用参数的形式传入
def wrapper(func): # 通过参数接收外部的值
    start_time=time.time()
    res=func()
    stop_time=time.time()
    print(\'run time is %s\' %(stop_time-start_time))
    return res
  • 但之后函数的调用方式都需要统一改成
wrapper(index)
wrapper(其他函数)
  • 这便违反了不能修改被装饰对象调用方式的原则
    • 于是我们换一种为函数体传值的方式,即将值包给函数
def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print(\'run time is %s\' %(stop_time-start_time))
  
return res
    return wrapper
  • 这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能
    • 只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index
index=timer(index)  #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
  • 至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。
    • 但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常
def home(name):
    time.sleep(5)
    print(\'Welcome to the home page\',name)

home=timer(home)
home(\'egon\')

#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given
  • 之所以会抛出异常,是因为home(egon)调用的其实是wrapper(egon),而函数wrapper没有参数。
    • wrapper函数接收的参数其实是给最原始的func用的
    • 为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合
    • 于是修正装饰器timer如下
def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print(\'run time is %s\' %(stop_time-start_time))
        return res
    return wrapper
  • 此时我们就可以用timer来装饰带参数或不带参数的函数了
    • 但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式
    • 需要在被装饰对象的正上方单独一行添加 @timer
    • 当解释器解释到 @timer 时就会调用timer函数
    • 且把它正下方的函数名当做实参传入
    • 然后将返回的结果重新赋值给原函数名
@timer # index=timer(index)
def index():
    time.sleep(3)
    print(\'Welcome to the index page\')
    return 200
@timer # index=timer(home)
          def home(name):
    time.sleep(5)
    print(\'Welcome to the home page’,name)
  • 如果我们有多个装饰器,可以叠加多个
@deco3
@deco2
@deco1
def index():
    pass
  • 叠加多个装饰器也无特殊之处,上述代码语义如下:
index=deco3(deco2(deco1(index)))
(2)有参装饰器的实现
  • 了解无参装饰器的实现原理后
    • 我们可以再实现一个用来为被装饰对象添加认证功能的装饰器
    • 实现的基本形式如下
def deco(func):
    def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
    return wrapper
  • 如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下
  def deco(func):
        def wrapper(*args,**kwargs):
            if driver == \'file\':
                编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
            elif driver == \'mysql\':
                编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
        return wrapper
  • 函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能
    • 不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数
    • 这样便保证了在auth函数内无论多少层都可以引用到
def auth(driver):
    def deco(func):
        ……
    return deco
  • 此时我们就实现了一个有参装饰器,使用方式如下
# 先调用auth_type(driver=\'file\'),得到@deco,deco是一个闭包函数,包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
@auth(driver=\'file\')
def index():
   pass

@auth(driver=\'mysql\')
def home():
   pass
  • 可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性
    • 但对于被装饰之后的函数,查看文档注释
@timer
def home(name):
    \'\'\'
    home page function
    :param name: str
    :return: None
    \'\'\'
    time.sleep(5)
    print(\'Welcome to the home page\',name)

print(help(home))
\'\'\'
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None
\'\'\'
  • 在被装饰之后home=wrapper
    • 查看home.name也可以发现home的函数名确实是wrapper
    • 想要保留原函数的文档和函数名属性,需要修正装饰器
def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print(\'run time is %s\' %(stop_time-start_time))
        return res
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper
  • 按照上述方式来实现保留原函数属性过于麻烦
    • functools模块下提供一个装饰器wraps专门用来帮我们实现这件事
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print(\'run time is %s\' %(stop_time-start_time))
        return res
    return wrapper

Python高阶函数与函数装饰器-day4

  • 上节回顾
  • 高阶函数
  • 闭包函数
  • 函数装饰器
  • 模块导入

 

一、上节回顾

  Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误。我在这里简单归纳Python3和Python2各自的区别。

  首先是Python3-->代码文件都是用utf-8来解释的。将代码和文件读到内存中就变成了Unicode,这也就是为什么Python只有encode没有decode了,因为内存中都将字符编码变成了Unicode,而Unicode是万国码,可以“翻译”所以格式编码的格式。Python3中str和bytes是两种格式,bytes可以当做二进制的表现形式。
  Python2使用系统默认的字符编码解释代码,所以要用utf-8解释代码,就必须在头部申明;并且Python2中有解码和编码,但是解码动作是必须的而编码动作可以忽略,因为Python代码加载到内存中就是Unicode,这一点和python3一样;Python2中还需要注意的就是str和bytes是一个意思。Python2 里面的str就是Python3中的bytes格式,而Python3中的str其实就是Unicode.

    

  函数基础(这里我就是用递归函数中的二分查找)

  为什么使用函数:将将程序进行模块设计

  定义函数有三种形式:

    - 无参函数

    - 有参函数

    - 空函数

  PS:如果函数有多个返回值,那么返回的来的数据格式是元组
  - 如何在函数传入参数时限定参数数据格式。

  def leon(x:int,y:int)->int:

    pass

  其中这里指定了x,y都必须是int类型 " -> "的意思是函数返回值也必须是int类型

     print(yan.__annotations__):显示形参的限定数据格式以及返回值的格式

a = [1,2,3,4,5,7,9,10,11,12,14,15,16,17,19,21]  #形参中的num
def calc(num,find_num):
    print(num)
    mid = int(len(num) / 2)                       #中间数的下标
    if mid == 0:        #递归函数非常重要的判断条件
        if num[mid] == find_num:
            print("find it %s"%find_num)
        else:
            print("cannt find num")
    if num[mid] == find_num:     #直接找到不用递归,结束函数
        print("find_num %s"%find_num)
    elif num[mid] > find_num:     #find_num应该在左边,向下递归
        calc(num[0:mid],find_num)

    elif num[mid] <  find_num:     #find_num应该在右边,向下递归
        calc(num[mid+1:],find_num)
calc(a,12)

      匿名函数

c = lambda x:x+1   #x就是形参,c就是这个匿名函数的对象
print(c(22))   

  高阶函数-特性

  1. 把一个函数的内存地址传给另外一个函数,当做参数

  2.一个函数把另外一个函数的当做返回值返回

def calc(a,b,c):
    print(c(a) + c(b))

calc(-5,10,abs) #引用上一节的实例,将-5和10绝对值相加

 

 

 

二、高阶函数(补充) 

  函数是第一类对象

  • 函数可以被赋值
  • 可以被当做参数
  • 可以当做返回值
  • 可以作为容器类型的元素
#函数可以被赋值
def leon():
    print("in the leon")

l = leon
l()

#函数可以被当做参数
def yan(x):  #这里x形参,其实就是我们调用实参的函数名
    x()        #运行函数

y = yan(leon)


#函数当做返回值
def jian(x):   和上面一样这这也必须传入一个函数
    return x
j = jian(leon) #这里需要注意一点就是这里的意思是运行jian这个函数而这个函数返回的是x 也就是leon这个函数的内存地址,也就是说这时候leon这个函数并没有被执行
j()  #运行 leon函数

#可以做为容器类型的元素
leon_dict = {"leon":leon}

leon_dict["leon"]() #这样也可以运行leon这个函数

三、闭包函数

1.什么是闭包?我来看一下,比较官网的概念(这不是我在官网上面找的,不过没有关系,反正你们也看不懂):

  闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。

  懵逼了?不存在的。下面我用简洁的说一下,但是有一点很重要,闭包是装饰器中的重点,如果没有把闭包正真理解,那么学完装饰器之后会很快忘记。我们通过一个列子来说明下

import requests   #首先导入一个模块,这个可以不用记

def get(url):    #定义一个get函数里面需要传一个url的位置参数
    def wapper():  #在定义一个wapper函数
        res = requests.get(url) #这一步就是打开一个网页
        return res.text   #将网页以文字的形式返回
    return wapper   #返回最里层的wapper函数

g = get("http://www.baidu.com")  #调用:首先因为作用域的原因,我们无法访问到里层的wapper函数,所以我们直接调用get函数这里返回了一个wapper函数
print(g())  # 然后我在调用g(get函数)的对象,这样是不是就访问到里层的wapper函数呢

技术分享

PS:这里我们可以把函数当做一个特殊的变量,当代码从上向下执行的时候,如果函数不被调用话,函数内的代码是不会被执行的。就拿上面的上面的举例,当我们执行get函数的时候,这时候会返回一个wapper函数的内存地址,但是这个时候wapper函数并没有被执行也就是说g()这时候返回的状态其实就是wapper,这是我们只需要将g运行,就等于运行了wapper内的代码。

 

四、函数的嵌套调用

  嵌套调用其实很好理解,就是在一个函数中调用另一个函数的结果,也就是return的东西,同样的我们看一段非常简单的代码来看一下。

#嵌套调用,在一个函数中调用另一个函数的功能
#calc这个函数就是在对比两个数字的大小
def calc2(x,y):
    if x >y :
        return x
    else:
        return y

#我靠老板非常变态,然你直接计算四个数字的大小,擦。
def calc4(a,b,c,d):
    res1 = calc2(a,b)   #res1的值,这里不就是calc2这个函数比较时最大的哪一个吗。
    res2 = calc2(res1,c)
    res3 = calc2(res2,d)
    return res3

  通过上面的代码我们做一记忆。什么时候会用到嵌套调用呢?很显然,就是我们这个函数(calc4)需要另外一个函数的实行结果(return的y或者x)。

 

五、装饰器(高级的闭包函数)

  就拿下面的这段代码来说。如何在不改源代码的情况下实现计算代码的运行时间

def geturl(url):
    response = requests.get(url)
    print(response.status_code)

geturl("http://www.baidu.com")
def timer(func):
    def wapper(url):
        start_time = time.time()
        func(url)
        stop_time = time.time()
        so_time_is = stop_time - start_time
        print("运行时间%s"%so_time_is)
    return wapper


@timer
def geturl(url):
    response = requests.get(url)
    print(response.status_code)

python = geturl("http://www.baidu.com")

   图解代码

技术分享

装饰器必备:

  1. @timer就是装饰器,意思是装饰它下面的函数,而装饰器和被装饰的都是一个函数。
  2. timer(装饰器函数),首先它会有一个位置参数(func)名字随意,但是必须并且只能是一个位置参数
  3. func参数就是被装饰的geturl这个函数
  4. 为什么func是geturl这个函数呢-->上面写了一个装饰器功能:geturl=timer(geturl),我们看到这里的timer中传入的其实就是func函数所以func = geturl(被装饰的函数)
  5. 分析geturl=timer(geturl),首先我们可以得知timer这是一个闭包函数,当我们执行这个闭包函数,会把里层的函数(wapper)返回,也就是说timer(geturl)其实就是返回的wapper,所以就可以这样理解了geturl==wapper,所以当我们运行geturl的时候就相当于在执行wapper()这样的一个操作;如果这里实在记不住,就这样。咱上面不是有一个闭包函数吗?你就把geturl=timer(geturl)中的geturl(执行函数的返回结果)当做上面g(函数调用的返回结果),然后在分别再执行了下"g"或者"geturl”这个对象。
  6. 如果被装饰者有位置参数的话,我们需要在wapper函数中加上对应的位置参数用来接收,如果长度是不固定的话还可以用*args和**kwargs

六、有参装饰器

  听着名字顾名思义,就是在装饰器中还有位置参数。

#一个low得不能再low得验证脚本,如果是显示环境中所有数据必须是由数据库或者一个静态文件提供,并且登录成功时,需要保存用户的一个状态

def auth(auth_type):   #有参装饰器名称
    def auth_deco(func):   #定义第二层函数名称
        def wrapper(*args,**kwargs):  #最里层函数,主要实现认证功能
            if auth_type == "file":
                username = input("username>>:").strip()
                password = input("username>>").strip()
                if username == "leon" and password == "loveleon":
                    res = func(*args,**kwargs)
                    return res
            elif auth_type == "mysql_auth":
                print("mysql_auth...")
                return func(*args,**kwargs)
        return wrapper  #第二层返回的是wrapper函数,其实就是home
    return auth_deco    #第一层返回的结果等于第二层函数的名称

@auth(file)
def home():
    print("welcome")

home() #执行home-->wrapper

有参函数必备知识:

  1. 套路,通过上面无参装饰器,我们得出了geturl=timer(geturl)这个等式。回到有参装饰器,我们又会有什么样子的等式呢?首先@auth("file")是一个装饰器也就是一个函数,所以我们定义了一个auth(auth_type)这个函数,而这个函数返回的是什么呢?没有错就是第二层函数;到了这里我们就会发现@auth("file")其实就是@auth_deco,现在我们知道了现在装饰器其实就是auth_deco,那剩下的还不知道怎么写吗?
  2. 整理公式,auth(‘file‘)-----------(return)> auth_deco----->@auth_deco ->home=auth_deco(home)
  3. 如果记不住?如果实在是记不住,其实就可以这样理解,有参装饰器无非就是在无参装饰器上面加了一层(三层),然后在第一层返回了第二层的函数,而到了第二层就和我们普通用的装饰器是一毛一样了

 七、模块导入

  import ,创建一个leonyan.py的模块文件,等待被导入

a = 10
b = 20
c = 30

def read1():
    print("in the read1")

def read2():
    print("in the read2")

  导入leonyan.py文件(调用模块文件和模块文件在同一目录下)

import leonyan #Python IDE这行会爆红,但是不用管

leonyan.read1() #执行leonyan这个包中的read1函数

leonyan.read2() #执行leonyan这个包中read2函数

print(leonyan.a + leonyan.b + leonyan.c ) #输出60

 总结:在Python中包的导入(import ***)会干三个事情:1:创建新的作用域;2:执行该作用域的顶级代码,比如你导入的那个包中有print执行后就会直接在屏幕中输出print的内容;3:得到一个模块名,绑定到该模块内的代码

  

  在模块导入的时候给模块起别名

import leonyan as ly
import pandas as pd #这是一个第三方模块,以后的博客中会写到,这是一个用于做统计的

 给模块起别名还是挺多的,在有些模块的官方文档中,还是比较推荐这种方法的,比如pandas的官方文档中就是起了一个pd别名,总之as就是一个模块起别名

 

  from *** import ***

from leonyan import read1 #引入直接调用
read1() 

如果在调用模块的函数作用域中有相同的同名的,会将调用过来的覆盖。

 

在form ** import ** 中控制需要引用的变量(函数其实在未被执行的时候也是一个存放在内存中的变量)

from leonyan import read1,read2 在同一行中可以引用多个,只需要用逗号隔开就行了

print(read1)
print(read2)
#这里打印的就是read1和read2的内存地址

#需求我现在只需要导入read2

这时候我们就可以在leonyan这个函数中加上这么一行:

__all__ = ["read2"]  #这里的意思就是别的文件调用为的时候用from ** import ** 只能拿到read2 这个函数的内存地址,也就是只有read2可以被调用

 

  把模块当做一个脚本执行

   我们可以通过模块的全局变量__name__来查看模块名:
  当做脚本运行:
  __name__ 等于‘__main__‘

  作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
  if __name__ == ‘__main__‘:

#fib.py

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end= )
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

代码执行 Python flb.py 100

 

  只需要简单了解的Python模块导入搜索路径

  内建(build-in)  --> sys.path(sys.path是一个列表,而且第一个位置就是当前文件夹)

 

  模块导入的重点-->包的导入

  在实际的开发环境中,你不可能一个文件的代码写到底,当然你也有可能会引用同文件夹中的其他模块,但是你有没有想过这一个项目不可能是你一个写的,都是很多人协作开发。这样就存在这样的一个问题了;不同的人不可能用一台电脑,也不可能在一个文件夹下面写写功能。他们也有自己的代码文件夹,然后大家把功能通过接口的方式,提供调用。这时候就面临这不同文件夹的调用问题。而这种问题也需要通过from ** import ** 调用。

  技术分享

 

上图中我运行“模块导入.py”这个文件夹,首先我from Pythonscript.leonyan.command import config,因为我们得运行脚本和需要导入的包都在Pythonscript的目录下面所以我直接通过绝对路径导入,然后一层一层“.”下去,知道最后import了这个config文件,这里我们需要注意一点:当脚本在最外层运行的时候sys.path 列表中的第一个参数就是运行脚本的目录,这是什么意思,这代表着你在别的包中有调用了其他的东西比如说我的config.py是调用了bing.py文件,这时候就必须写绝对路径,因为这在sys.path文件夹中已经找不到了,也就是导入不进来。

总结:包的导入其实都是很简单的,你需要记住一点:当你导入Python内建或者下载的第三方模块直接用import 导入,如果是自己写的就用from ** import ** 使用绝对目录导入就行了,也就是从调用脚本的上级目录开始导入。这样可以保证不会报模块导入的错误了。



以上是关于day13-14 函数对象与装饰器的主要内容,如果未能解决你的问题,请参考以下文章

Python高阶函数与函数装饰器-day4

python学习 day4

Python入门学习-DAY11-装饰器

面向对象之绑定方法与非绑定方法(day7)

python学习day07 高阶函数 装饰器 语法糖

Day4 装饰器——迭代器——生成器