第二模块:03python学习之函数进阶

Posted gbq-dog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第二模块:03python学习之函数进阶相关的知识,希望对你有一定的参考价值。

1.名称空间

定义:相比上一节的作用域,名称空间更能解释。名称空间又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方。

名称空间分以下三种:

  • locals:是函数内的名称空间,包括局部变量和形参,当前函数空间内
  • globals:全局变量,函数定义所在模块的空间
  • bulildings:内置模块的名称空间

针对作用域的查找顺序,遵循LEGB的规则

  • L:LOCALS 本地的
  • E:ENCLOSING 相邻的
  • G:GLOBLS 全局的
  • B:BUILTINGS 内置函数的

函数在调用变量时,遵循从上到下,逐层向下获取变量,LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__

看以下代码

技术分享图片
level = L0
n = 22


def func():
    level = L1
    n = 33
    print(locals())

    def outer():
        n = 44
        level = L2
        print(locals(),n)

        def inner():
            level = L3
            print(locals(),n) #此外打印的n是多少?
        inner()
    outer()


func()
View Code

2.闭包

关于闭包可以这么定义:即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

是不是觉得很麻烦,绕来绕去,先看代码

def outer():
    name = alex

    def inner():
        print("在inner里打印外层函数的变量",name)

    return inner


f = outer() 

f()

你会发现在与普通的嵌套函数不同的是子函数返回的是函数自己,而不是其他变量什么的。这个时候可以这么定义,在外部可以调用内部函数,同时使用内部的值这个就可以称之为闭包。虽然函数内部的子函数返回子函数自身,在返回的同时,不止是一个函数对象,同时在外面包裹了一层作用域,使得函数在调用的时候优先使用外部的作用域。

3.装饰器***

对于程序开发来说,我们一般需要遵守‘开放封闭原则’,所谓开放就是说程序有较好的拓展性以及延展性,因为对于一个程序的功能不可能永远不变,会各种各样的拓展功能,例如说我们登录一个程序,以前只有账户密码验证,后面铸件出现了QQ验证,微信认证,微博验证,这就要求我们的程序可以较好的拓展。而所谓的封闭就是说已经完成的程序代码最好不动,有可能的是这个操作会发生巨大的工作量,涉及到不同的部门或者不同的流程这样就会有巨大的变更动作,也有可能是代码时间已经很久远,不好重新构建。下面用几个例子来说明这个事情。

你所在的部门属于基础支撑部门,产生核心代码,其他部门代码从你这边调取

你的核心代码如下

############### 基础平台提供的功能如下 ###############
 
def f1():
    print f1
 
def f2():
    print f2
 
def f3():
    print f3
 
def f4():
    print f4
 
############### 业务部门A 调用基础平台提供的功能 ###############
 
f1()
f2()
f3()
f4()
 
############### 业务部门B 调用基础平台提供的功能 ###############
 
f1()
f2()
f3()
f4()

这个时候老板要求,核心代码随意调用有可能泄密,要求调用的时候需要账户密码验证。对于初学者我们有可能会想到这样,对每一个被调用的函数进行重编

代码如下

############### 基础平台提供的功能如下 ############### 

def f1():
    # 验证1
    # 验证2
    # 验证3
    print f1

def f2():
    # 验证1
    # 验证2
    # 验证3
    print f2

def f3():
    # 验证1
    # 验证2
    # 验证3
    print f3

def f4():
    # 验证1
    # 验证2
    # 验证3
    print f4

############### 业务部门不变 ############### 
### 业务部门A 调用基础平台提供的功能### 

f1()
f2()
f3()
f4()

### 业务部门B 调用基础平台提供的功能 ### 

f1()
f2()
f3()
f4()

但是这种情况,我们就发现重复会很多很多,这只是三四个模块,如果多呢?这样就太多重复代码了,有可能说我这样功能实现了呀,但是作为一个程序员应该是想着用最简洁的语句完成最多的事情,不过我们有可能在学了一点,想到以下的这种方式。

############### 基础平台提供的功能如下 ############### 

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass


def f1():
    
    check_login()

    print f1

def f2():
    
    check_login()

    print f2

def f3():
    
    check_login()

    print f3

def f4():
    
    check_login()
    
    print f4

专门做一个认证函数,每个基础模块做修改调用一次认证函数,认证通过就可以继续下去,但是我们这样还是违背了开放封闭原则,对源代码做了修改,最好的办法就是认证一次后不需要在此认证。比较高级的代码就是下面这个,后面会对这个代码说明。

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        return func()
    return inner
 
@w1
def f1():
    print f1
@w1
def f2():
    print f2
@w1
def f3():
    print f3
@w1
def f4():
    print f4

解释上面的代码,对于python程序是由上至下执行,首先返回俩个值

return fi()直接执行f1函数

return inner返回inner函数,并没有执行

没错,从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。

从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章,@函数名 是python的一种语法糖。

如上例@w1内部会执行一下操作:

    • 执行w1函数,并将 @w1 下面的 函数 作为w1函数的参数,即:@w1 等价于 w1(f1)
      所以,内部就会去执行:
          def inner:
              #验证
              return f1()   # func是参数,此时 func 等于 f1
          return inner     # 返回的 inner,inner代表的是函数,非执行函数
      其实就是将原来的 f1 函数塞进另外一个函数中
    • 将执行完的 w1 函数返回值赋值给@w1下面的函数的函数名
      w1函数的返回值是:
         def inner:
              #验证
              return 原来f1()  # 此处的 f1 表示原来的f1函数
      然后,将此返回值再重新赋值给 f1,即:
      新f1 = def inner:
                  #验证
                  return 原来f1() 
      所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在 新f1 函数内部先执行验证,再执行原来的f1函数,然后将 原来f1 函数的返回值 返回给了业务调用者。
      如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着

所以我们可以这么理解,装饰器只是作为一个语法糖包含在函数外面,将函数作为参数传递给认证函数执行,对于调用者而言其实是执行,调用的f1(),其实是调用的inner函数,然后做认证,执行真正f1()函数

对于装饰器还有其他

带参数

#单参数
def w1(func):
    def inner(arg):
        # 验证1
        # 验证2
        # 验证3
        return func(arg)
    return inner

@w1
def f1(arg):
    print f1

一个参数
#俩个参数
def w1(func):
    def inner(arg1,arg2):
        # 验证1
        # 验证2
        # 验证3
        return func(arg1,arg2)
    return inner

@w1
def f1(arg1,arg2):
    print f1

两个参数

#多个参数
def w1(func):
    def inner(*args,**kwargs):
        # 验证1
        # 验证2
        # 验证3
        return func(*args,**kwargs)
    return inner
 
@w1
def f1(arg1,arg2,arg3):
    print f1

双装饰器

def w1(func):
    def inner(*args,**kwargs):
        # 验证1
        # 验证2
        # 验证3
        return func(*args,**kwargs)
    return inner
 
def w2(func):
    def inner(*args,**kwargs):
        # 验证1
        # 验证2
        # 验证3
        return func(*args,**kwargs)
    return inner
 
 
@w1
@w2
def f1(arg1,arg2,arg3):
    print f1

其他类型的需要后面再添加

4.列表生成器

现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式

第一种

a = [1,3,4,6,7,7,8,9,11]
b = []
for i in a:b.append(i+1)
    a = b

第二种

b = []
>>> for i in a:b.append(i+1)
>>> a = b

稍微装逼一点的

a =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = map(lambda x:x+1, a)
for i in a:print(i)

最装逼的就是下面这种了

a = [i+1 for i in range(10)]

直接一行代码搞定,减少代码量,这种就叫做列表生成式

5.生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。

第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

第二种方法,yield  把函数变成生成器

5.1看以下代码

list1 = [i for i in range(100)]
list1 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

我们很容易就用列表生成式生成一个100元素的列表,那么如果要生成一个一个出现的额生成式,只需要将[]变为()就可以了,看以下代码

>>> li = (i for i in range(100))
>>> li
<generator object <genexpr> at 0x0220C6E8>

对于一个生成式获取他下一个元素就有俩种方式next(li) 和 li.__next__(),就可以轻松获取到下一个元素

不过需要注意的是,在调用next()的函数的时候,在最后一个位置会抛出一个错误,必须记住生成器只能往前,不能后退,也不可以切片,走到最后直接报错

>>> next(li)
8
>>> next(li)
9
>>> next(li)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

#每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

range对于python2与3的区别

技术分享图片

在python3中,range和文件的打开也是生成器

 5.2用yield语法生成一个生成器

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b
        a,b = b,a+b

        n += 1

    return done



>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

yield与return的区别

return返回并中止function

yield返回数据,并冻结当前进程

next 唤醒冻结的函数执行过程,继续执行直到下一个yield

return在生成器中,代表生成器的中止,会直接报错

5.3生成器send

例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)

def fib(max):
    a, b, n = 0, 1, 0
    while n < max:
        send_msg = yield b
        print(send_msg)
        a, b = b, a+b
        n += 1

f = fib(10)
print(next(f))
f.send(寄个快递)
print(next(f))

技术分享图片

f.send(None)    #第一次相当于f.__next__()

总结:

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:

  1. 节约内存
  2. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的

6.迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如listtupledictsetstr等;
  • 一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

可以使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance(abc, Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用isinstance()判断一个对象是否是Iterator对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance(abc, Iterator)
False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter(abc), Iterator)
True

技术分享图片

技术分享图片

总结

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break


















以上是关于第二模块:03python学习之函数进阶的主要内容,如果未能解决你的问题,请参考以下文章

Python学习之函数进阶

python学习之函数学习进阶

python学习之函数学习进阶

python学习之函数学习进阶

Python学习之装饰器进阶

Python学习之模块