首页 > 代码库 > Python自动化运维之函数进阶

Python自动化运维之函数进阶

1、函数嵌套
函数的嵌套定义:在一个函数的内部,又定义了另外一个函数
函数的嵌套调用:在调用一个函数的过程中,又调用了其他函数
代码:  

>>> def f1():
...     def f2():
...         print(‘from f2‘)
...         def f3():
...             print(‘from f3‘)
...         f3()
...     f2()
...

执行结果:

>>> f1()
from f2
from f3

2、命名空间与作用域:存放名字的地方,确切的是存放的名字与变量值的绑定的关系的空间
名称空间分类:内置名称空间、全局名称空间和局部名称空间
    内置名称空间:Python解释器启动时产生一些内置名字
    全局名称空间:在执行文件时产生的,存放在文件级别(流程控制语句定义、未缩进定义的)定义的名字
    局部名称函数:
    加载顺序:内置---->全局---->局部
    名称查找顺序:局部---->全局---->内置
作用域:作用的范围
    作用域分类:全局作用域和局部作用域
    全局作用域:全局存活,全局有效。查看函数globals(),显示字典类型
    局部作用域:临时存活,局部生效。查看函数locals(),显示字典类型
关键字:global nonlocal
局部修改全局,对于不可变类型需要使用global,可变类型无需使用global
局部修改局部,对于不可变类型需要使用nonlocal,可变类型无需使用nonlocal
注意:尽量避免使用局部修改全局
重点:作用域关系,在函数定义时,函数中名称查找就已经固定和调用位置无关。在调用函数时,必须回到函数原来定义的位置去找作用域关系

x = 1
def f1():
    def f2():
        print(x) # x 为全局作用域
    return f2()
def foo():
    x = 100
    f1()
foo()
x = 10
foo()

3、闭包函数
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
闭包可以形象的把它理解为一个封闭的包裹,这个包裹就是一个函数,当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量,自由变量可以在随着包裹到处游荡。当然还得有个前提,这个包裹是被创建出来的。
在Python中可以这样理解,一个闭包就是我调用了一个函数A,这个函数A返回了一个函数B给我。这个返回的函数B就叫做闭包。我在调用函数A的时候传递的参数就是自由变量。

1. 定义
python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).这个定义是相对直白的,好理解的,不像其他定义那样学究味道十足(那些学究味道重的解释,在对一个名词的解释过程中又充满了一堆让人抓狂的其他陌生名词,不适合初学者)。下面举一个简单的例子来说明。

>>> def addx(x):
...     def adder(y): return x + y
...     return adder
...
>>> c = addx(8)
>>> type(c)
<class ‘function‘> 
>>> c.__name__
‘adder‘ 
>>> c(10)
18

结合这段简单的代码和定义来说明闭包:如果在一个内部函数里:adder(y)就是这个内部函数,对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,则这个内部函数adder就是一个闭包。
闭包=函数块+定义函数时的环境,adder就是函数块,x就是环境,当然这个环境可以有很多,不止一个简单的x。所以,如果要用一句话说明白闭包函数,那就是:函数内在包含子函数,并最终return子函数。
2. 使用闭包注意事项
2.1 闭包中是不能修改外部作用域的局部变量的

>>> def foo():
...     m = 0
...     def foo1():
...         m = 1
...         print(m)
...     print(m)
...     foo1()
...     print(m)
...
>>> foo()
0
1
0

从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。

2.2 以下这段代码是在python中使用闭包时一段经典的错误代码

>>> def foo():
...     a = 1
...     def bar():
...         a = a + 1
...         return a
...     return bar
...

这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时

>>> c=foo()
>>> print(c())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in bar
UnboundLocalError: local variable ‘a‘ referenced before assignment

这是因为在执行代码c=foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单

def foo():  
    a = [1]  
    def bar():  
        a[0] = a[0] + 1  
        return a[0]  
    return bar

只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,所以在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。


4、装饰器
1. 装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。让我们从简单的开始,直到能写出实用的装饰器。
封闭原则:对扩展是开放的,对修改是封闭的。

>>> def outer(some_func):
...     def inner():
...         print("hello world!")
...         ret = some_func() # 1
...         return ret + 1
...     return inner
...
>>> def foo():
...     return 1
...
>>> decorated = outer(foo) #2
>>> decorated()
hello world!

请仔细看这个装饰器示例。首先,定义了一个带单个参数some_func的名为outer的函数。然后在outer内部定义了一个内嵌函数inner.inner 函数将打印一行字符串然后调用some_func,并在 #1 处获取其返回值。在每次 outer 被调用时,some_func 的值可能都会不同,但不论 some_func 是什么函数,都将调用它。最后,inner 返回 some_func() 的返回值加 1。在 #2 处可以看到,当调用赋值给 decorated 的返回函数时,得到的是一行文本输出和返回值 2,而非期望的调用 foo 的返回值 1。

我们可以说变量decorated是foo的装饰版——即foo加上一些东西。事实上,如果写了一个实用的装饰器,可能会想用装饰版来代替foo,这样就总能得到“附带其他东西”的foo版本。用不着学习任何新的语法,通过将包含函数的变量重新赋值就能轻松做到这一点:

>>> foo = outer(foo)
>>> foo
<function outer.<locals>.inner at 0x000000F47F5CBA60>

现在任意调用foo()都不会得到原来的foo,而是新的装饰器版!明白了吗?来写一个更实用的装饰器。

2. 函数装饰器@符号的应用

import time
def timemer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        RETURN=func()
        stop = time.time()
        print("run time is: %s" % (stop-start))
        return RETURN
    return wrapper
如果定义了一个函数,可以用 @ 符号来装饰函数,如下:
@timemer
def index():
    time.sleep(2)
    print("welcome to index")
index()

值得注意的是,这种方式和简单的使用index函数的返回值来替换原始变量的做法没有什么不同——Python只是添加了一些语法来使之看起来更加明确。
使用装饰器很简单!虽说写类似staticmethod或者classmethod的实用装饰器比较难,但用起来仅仅需要在函数前添加@装饰器名 即可!
3. *args和*kwargs
上面我们写了一个实用的装饰器,但它是硬编码的,只适用于特定类型的函数——带有两个参数的函数。内部函数checker接收两个参数,然后继续将参数传给闭包中的函数。如果我们想要一个能适用任何函数的装饰器呢?让我们来实现一个为每次被装饰函数的调用添加一个计数器的装饰器,但不改变被装饰函数。这意味着这个装饰器必须接收它所装饰的任何函数的调用信息,并且在调用这些函数时将传递给该装饰器的任何参数都传递给它们。

import time
def timemer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        RETURN=func(*args,**kwargs)
        stop = time.time()
        print("run time is: %s" % (stop-start))
        return RETURN
    return wrapper

如果定义了一个函数,可以用 @ 符号来装饰函数,如下:

@timemer
def index(name):
    time.sleep(2)
    print("welcome %s to index" %name)
index("name")

同时使用*args和*kwargs使得装饰器更加的通用

4. 无参装饰器

current_usr={"user":None}
def auth(func):
    info = {
        "xuanwei": {"password": "123123", "number": 0},
        "linglong": {"password": "123123", "number": 0}
    }
    def wrapper(*args,**kwargs):
        if current_usr["user"]:
            return func(*args, **kwargs)
        else:
            while True:
                username = input("请输入用户名: ")
                if username in info:
                    password = input("请输入密码: ")
                    if info[username]["number"] > 1:
                        print("%s 用户已收锁定" % username)
                        exit()
                    if password == info[username]["password"]:
                        current_usr["user"] = username
                        RETURN = func(*args, **kwargs)
                        return RETURN
                    else:
                        print("用户密码错误")
                        info[username]["number"] += 1
                else:
                    print("%s 用户不存在" % username)
    return wrapper
@auth
def index1():
    print("welcome to index1")
    return 123
def index2():
    print("welcome to index1")
    return 123
index1()
index2()

5. 有参装饰器

def auth(auth_type = "dict"):
    def deco(func):
        current_usr = {"user": None}
        def wrapper(*args,**kwargs):
            if current_usr["user"]:
                return func(*args, **kwargs)
            else:
                if auth_type == "file":
                    with open("db.txt", "r", encoding="utf-8") as read_f:
                        info = eval(read_f.read())
                else :
                    info = {
                        "xuanwei": {"password": "123123", "number": 0},
                    }
                while True:
                    username = input("请输入用户名: ")
                    if username in info:
                        password = input("请输入密码: ")
                        if info[username]["number"] > 1:
                            print("%s 用户已收锁定" % username)
                            exit()
                        if password == info[username]["password"]:
                            current_usr["user"] = username
                            RETURN = func(*args, **kwargs)
                            return RETURN
                        else:
                            print("用户密码错误")
                            info[username]["number"] += 1
                    else:
                        print("%s 用户不存在" % username)
        return wrapper
    return deco
@auth()
def index():
    print("welcome to index")
    return 123
index()

5、迭代器
    迭代:是一个重复的过程,每一次重复,都是基于上一次的结果而来
    什么是迭代器对象
        有__iter__和__next__方法,并且执行__iter__得到仍是迭代器本身
    迭代器对象的优点
        1. 提供了一种统一(不依赖索引)的迭代方式
        2. 迭代器本身,比起其他数据类型更少内存,next()在内存中只有1个值
    迭代器对象的缺点
        1. 取值不灵活。只能往后走,不能回退,没有索引取值灵活
        2. 无法预知取值结束,即无法预知长度
    判断可迭代器对象与迭代器对象
        from collections import Iterator
        判断是否是可迭代器对象
        print(isinstance(对象,Iterator))

6、生成器:在函数内存包含yield关键字,那么该函数执行的结果是生成器,并且生成器就是迭代器
关键字yield的功能:
(1) 包函数的结果做出迭代器,以一种优雅的方式封装好__iter__,__next__方法
(2) 函数暂停与再继续运行的状态是有yield保存的
return 和 yield的区别:
(1) 功能相同:都能返回值
(2) 不同:return只能执行一次

本文出自 “炫维” 博客,请务必保留此出处http://xuanwei.blog.51cto.com/11489734/1951593

Python自动化运维之函数进阶