第二模块:函数编程 第1章·函数进阶

名称空间

 又叫命名空间,名字空间,英文名叫 name space,干嘛用的?

 假如有 x = 1 , 1是存放到内存的,那 x 存哪里的呢?名称空间正式存放名字x与1绑定关系的地方。

 

名称空间分3种:

  • locals:是函数内的名称空间,包括局部变量和形参。通过locals()可以打印当前名称空间的局部变量,如果在函数里就是函数里的局部变量。
  • globals:全局变量,函数定义所在模块的名字空间。通过globals()可以打印所有的全局变量,无论是在函数内还是函数外。
  • builtins:内置模块的名字空间。可通过dir(__builtins__)打印所有的内置方法

不同变量的作用域不同就是由这个变量所在的命名空间来决定的。

作用域即范围:

  • 全局范围:全局存活,全局有效
  • 局部范围:临时存活,局部有效。

作用域查找顺序:

LEGB代表名字查找顺序:locals --> enclosing function --> globals --> __builtins__

  • locals:是函数内的名字空间
  • enclosing:外部嵌套函数的名字空间
  • globals:全局变量,函数定义所在模块的名字空间
  • builtins:内置模块的名字空间

 

闭包

首先看例子:

def func():
    name = "Alex"

    def inner():
        print("在inner里打印name:", name)

    return inner

f = func()
f()

输出:

在inner里打印name: Alex

 

以上例子可以看出,嵌套函数中,直接执行func函数,里面的inner函数并没有执行,而是func函数将inner函数对象返回给了外部,给到了f ,这时候 f 实际上就是inner函数,f 加上括号就相当于直接执行inner函数。这样使得嵌套在函数内的子函数在外部也可被调用执行,调用的时候依然能够获取inner函数所在作用域的变量以及参数。

 

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

 

闭包的意义:

返回的函数对象,不仅仅是一个函数对象,在改函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

 

装饰器

 内容太多了,还是直接看大王的故事:

http://www.cnblogs.com/alex3714/articles/5765046.html

 

装饰器就是在不修改原来程序的情况下给程序添加新的功能。

 

软件开发中的一个原则:开放-封闭 原则
开放:已实现的功能代码块不应该被修改
封闭:对现有功能的扩展开放

 

装饰器练习题

'''
一:编写3个函数,每个函数执行的时间是不一样的,
提示:可以使用time.sleep(2),让程序sleep 2s或更多,

二:编写装饰器,为每个函数加上统计运行时间的功能
提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间

三:编写装饰器,为函数加上认证的功能,即要求认证成功后才能执行函数

四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
提示:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式
'''

import time


def runtime(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        run_time = end_time - start_time
        print("run time:", run_time)

    return inner


def get_account_from_file():
    f = open("account2.txt", mode="r", encoding="utf-8")
    data = eval(f.read())
    f.close()
    return data


login_status = False


def login(func):
    def inner(*args, **kwargs):
        global login_status
        if login_status == False:
            username = input("username:>")
            password = input("password:>")
            account_dict = get_account_from_file()
            if username == account_dict["name"] and password == account_dict["password"]:
                print("登录成功")
                login_status = True
            else:
                print("用户名或密码错误.")

        if login_status:
            print("已通过验证..")
            func(*args, **kwargs)

    return inner


@login
@runtime
def func1():
    print("func1 running...")
    time.sleep(2)


@login
@runtime
def func2():
    print("func2 running...")
    time.sleep(3)


@login
@runtime
def func3():
    print("func3 running...")
    time.sleep(1)


func1()
func2()
func3()

 

 列表生成式

有一个需求是 有一个列表,要对列表里的每个元素加1,如何做,有以下几种:

# 版本一:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = []
for i in a:
    b.append(i + 1)
print(b)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本二:
c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for index, i in enumerate(c):
    c[index] = i + 1
print(c)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本三:
d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
d = list(map(lambda x: x + 1, d))
print(d)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 版本四:列表生成式
e = [i + 1 for i in range(10)]
print(e)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

f = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
f = [i if i < 5 else i * i for i in f]    # 列表生成式
print(f)  # 输出:[0, 1, 2, 3, 4, 25, 36, 49, 64, 81]

 

 生成器

通过前面的列表生成式,我们可以很容易的创建一个列表,但是假如我要创建100万的数据,那么这100万的数据就会全部在内存中,而内存是有限的,不可能让你无限制的存,而且我只需要访问前面的一部分数据,后面的其实根本不会使用,还以这样的方式创建就对资源消耗太大了。

如果列表里的元素可以按照某种算法推算出来,那我们就可以循环的推算出后面的元素的值,就可以不用完整的创建整个列表从而大大节省资源消耗,这种一边循环一边计算的机制,就称为生成器(generator)

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

>>> L=[i*i for i in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>
>>>
>>> g=(i*i for i in range(10))
>>> g
<generator object <genexpr> at 0x0000022BD464E150>
>>>

 

如何获取generator里的元素呢?通过next()方法来获取。

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

 

说明:

生成器生成的只是方法,并不执行。

next()只能往前走,不能往回退。意思就是已经通过next()获取的元素不能够再次获取。 

 

generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素的时候,抛出StopIteration异常。

一步一步的nexg(g)很麻烦,我们可以通过for循环来获取元素值,还不会报StopIteration异常。

g = (i * i for i in range(10))
for j in g:
    print(j)

输出:

0
1
4
9
16
25
36
49
64
81

 

 如果瑞算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都是由前两个数相加得到,用列表生成式写不出来,但是用函数打印出来很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

fib(10)

输出:

1
1
2
3
5
8
13
21
34
55

注意:

a, b = b, a + b

相当于:

t = a + b

a = b

b = t

 

说到这里我们还没有用到生成器,回头想一下生成器的实现逻辑,再来看看这个斐波那契数列的程序,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这个逻辑和生成器的逻辑是非常相似的。

上面的函数里生成器 generator就是一步之遥,要把fib函数变成generator,只需要把print(b)n 改为 yield b 就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        yield b  # 改为 yield 就变成了generator,遇到yield程序就停在这里,等到下一次next()时,程序继续从这里开始运行,然后又等待下一次next(),循环往复,知道报出StopIteration异常
        a, b = b, a + b
        n = n + 1
    return 'done'


g = fib(10)
print(g)  # 输出 <generator object fib at 0x000001F14DE340F8>  说明函数变成了generator
print(next(g))  # next(g)  出generator里的元素
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print("其他程序在这个地方执行一下")  # 在next的过程中依然可以执行其他的程序
print(next(g))  # 还可以继续执行之前的next
print(next(g))
print(next(g))
print(next(g))
print(next(g))

输出:

<generator object fib at 0x000001F14DE340F8>
1
1
2
3
5
其他程序在这个地方执行一下
8
13
21
34
55

 

关于执行流程:

函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。

变成generator的函数,在每次调用 next() 的时候执行,遇到yield语句返回,再次被next()调用时,从上次返回的yield语句处继续执行。

yield后面加上a,就表示返回a,类似于 return a ,但yield不会退出,return会退出。

 

关于如何拿到generator里的return返回值:

如果我们再加一个next(g) 这时候就要报StopIteration异常了,因为已经没有值可以拿了,但是函数最后有一个   return  "done"  的返回值,我们是可以看到的。

Traceback (most recent call last):
<generator object fib at 0x000001D4FA4540F8>
  File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器.py", line 41, in <module>
1
1
2
3
5
其他程序在这个地方执行一下
8
13
21
34
55
    print(next(g))
StopIteration: done

  

如果使用 for 循环则拿不到generator里面的 return 返回值。如果要拿到返回值,必须要捕获StopIteration异常,返回值在StopIteration的value中。

g = fib(10)
while True:
    try:
        x = next(g)
        print("g:", x)
    except StopIteration as e:
        print("Genrator return value:", e.value)
        break

输出:

g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
g: 13
g: 21
g: 34
g: 55
Genrator return value: done

 关于捕获异常,后面还会详细讲解。

 

再来看看生成器的调用方法:

g = (i for i in range(10))
print(next(g))
print(next(g))
print(next(g))

print("---------------")
for i in g:
    print(i)

 输出:

0
1
2
---------------
3
4
5
6
7
8
9

 next()之后,之前的值就没有了,所以看到for循环的时候是从3开始的

for循环其实就是每循环一次就next一次

range其实也是一个生成器

 

关于range:

在python2里:
range(100000000000000) 会直接生成这么多个数据

在python3里:
range(100000000000000) 直接生成一个公式,根本就没有创建,在python2里也有同样的 不过叫 xrange(100000000000000) 和py3的range是一样的。

 

python2:
  range = list
  xrange = 生成器
python3
  range = 生成器
  xrange 没有

 

函数写生成器:

生成器的创建方式:

  1. 列表生成式

  2. 函数

区别:

  列表生成式最后只能写一个三元运算,更复杂的情况没办法办到。

  所以要用到函数

 

def range2(n):
    count = 0
    while count < n:
        print("count", count)
        count += 1
        yield count


new_range = range2(10)

r1 = next(new_range)
print(r1)
r2 = next(new_range)
print(r2)
r3 = next(new_range)
print(r3)

 输出:

count 0    # 由print打印的
1          # 由yield返回的
count 1
2
count 2
3

 

 yield vs return:

return:返回并终止function

yield:返回数据,并冻结当前的执行过程。

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

 

 生成器send方法:

在函数里已经有了yield后,再写return是不会返回return值的,并且会报StopIteration异常。

 

函数有了yield之后:

1. 调用时函数名加 ()  就便得到了一个生成器 

2. return 在生成器里,代表生成器的中止,直接报错。

 

send作用:

1. 唤醒并继续执行

2. 发送一个信息到生成器内部

def range2(n):
    count = 0
    while count < n:
        print("count", count)
        count += 1
        sign = yield count
        print("收到来自send的消息::", sign)
        if sign == "stop":
            print("我要停止程序运行了,bye bye")
            break  # break后就执行return了,return后会报错,需要自己捕获
    return 333


new_range = range2(3)
n1 = next(new_range)
new_range.send("stop")

 输出:

count 0
Traceback (most recent call last):
收到来自send的消息:: stop
我要停止程序运行了,bye bye
  File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器send.py", line 16, in <module>
    new_range.send("stop")
StopIteration: 333

 

send()里面不写值的话,默认是发送一个None的。

next()默认也是发了一个None的。  

next(iterator, default=None)   如果后面的参数给了值的话,但next完所有元素的时候也不会报StopIteration异常。

 

通过yield实现在单线程的情况下实现并发运算的效果:

import time


def consumer(name):
    print("%s 准备吃包子啦!" % name)
    while True:
        baozi = yield
        print("包子[%s]来了,被[%s]吃了!" % (baozi, name))


def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("老子开始准备做包子啦!")
    for i in range(10):
        time.sleep(1)
        print("做了%s个包子!" % (i))
        c.send(i)
        c2.send(i)


producer("alex")

 输出:

A 准备吃包子啦!
B 准备吃包子啦!
老子开始准备做包子啦!
做了0个包子!
包子[0]来了,被[A]吃了!
包子[0]来了,被[B]吃了!
做了1个包子!
包子[1]来了,被[A]吃了!
包子[1]来了,被[B]吃了!
做了2个包子!
包子[2]来了,被[A]吃了!
包子[2]来了,被[B]吃了!
做了3个包子!
包子[3]来了,被[A]吃了!
包子[3]来了,被[B]吃了!
做了4个包子!
包子[4]来了,被[A]吃了!
包子[4]来了,被[B]吃了!
做了5个包子!
包子[5]来了,被[A]吃了!
包子[5]来了,被[B]吃了!
做了6个包子!
包子[6]来了,被[A]吃了!
包子[6]来了,被[B]吃了!
做了7个包子!
包子[7]来了,被[A]吃了!
包子[7]来了,被[B]吃了!
做了8个包子!
包子[8]来了,被[A]吃了!
包子[8]来了,被[B]吃了!
做了9个包子!
包子[9]来了,被[A]吃了!
包子[9]来了,被[B]吃了!

 

迭代器

可以理解为 迭代器=循环 ,迭代一次,循环一次。

可以直接作用于 for 循环的数据类型有以下几种:

  • 一类是集合数据类型,如 list、tuple、dict、set、str等;
  • 一类是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()函数调用并不断返回下一个值的对象称为迭代器:Itetator

同样可以使用 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
>>>

 

生成器都是 Itrator 对象。

list 、dict、str 是 Iterable,但不是 Iterator。

可以把 list、dict、str 等 Iterable 变成 Iterator ,使用 iter() 函数:

>>> li = [1,2,3,4,5,7]
>>> isinstance(li,Iterable)
True
>>> isinstance(li,Iterator)
False
>>> li2=iter(li)
>>> isinstance(li2,Iterator)
True

 

为什么 list、dict、str 等数据类型不是 Iterator ?

因为Python的 Iterator对象表示的是一个数据流,Iterator对象可以被 next() 函数调用并不断返回下一个数据,知道没有数据时抛出 Stopitration 异常。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next() 函数实现按需计算下一个数据,所以 Iterator的计算是惰性的,只有在需要返回下一个数据时他才会计算。

Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而 list 是永远不可能存储全体自然数的。

 

小结:

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

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

集合数据类型如 list、dict、str等时Iterable但不是Iterator,不过可以通过iter() 函数变成 Itrator对象。

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

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

 完全等价于:

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

 

posted @ 2018-05-28 16:01 alexchenx 阅读(...) 评论(...) 编辑 收藏