python 之 函数进阶

命名空间

概念

:就是存放名字的地方,存什么名字呢?

举例说明:若x=1,1存放于内存中,那名字x存放在哪呢?

                     名称空间正是存放名字x 于 1 绑定关系的地方

命名空间分为三种

全局命名空间:Locals:是函数内的名称空间,包括局部变量和形参,打印当前所在的名称空间的所有变量

局部命名空间:Globals:全局变量,打印程序所有的变量。

内置命名空间:Builtins:内置模块的名字空间,dir(__builtins__)

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

作用域的作用范围

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

作用域查找顺序

LEGB

L:locals

E:enclosing 相邻的上一级

G:globls

B:buitlins ..

作用域链

函数名的本质

函数名的本质就是函数的内存地址

    1. 可以被引用
    2. 可以被当做容器类型的元素
    3. 可以当做函数的参数和返回值

闭包

概念:

这样理解:我们在函数里有子函数,子函数名被返回,在外部拿到了里边的函数,执行内部函数,利用内部作用域里所有的值,

或者这样:在外层函数被执行的时候,子函数名被返回了(返回的是内存地址),在外面执行内部函数的时候,他又引用了外层函数的变量。


格式如下,最为直接

def func():
    name = 'eva'
    def inner():
        print(name)
    return inner

f = func()  # 在此时,函数并没有执行
f()

装饰器

  装饰器的本质:一个闭包函数

  装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展

装饰器——简单版1

import time

def func1():
    print('in func1')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

func1 = timer(func1)
func1()

装饰器语法糖,这样就不用每次调用了,直接放在需要的函数“头部”

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()

我们可以给装饰器传递参数,并且可以是多个参数

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))

函数有返回值,一定要加上return,带返回值的装饰器

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func2('aaaaaa')
print(func2('aaaaaa'))

刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效

def index():
    '''这是一个主页信息'''
    print('from index')

print(index.__doc__)    #查看函数注释的方法
print(index.__name__)   #查看函数名的方法

为了不让他们失效,我们还要在装饰器上加上一点来完善它:调用工具箱的wraps

from functools import wraps

def deco(func):
    @wraps(func) #加在最内层函数正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@deco
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)

装饰器的固定格式

def timer(func):
    def inner(*args,**kwargs):
        '''执行函数之前要做的'''
        re = func(*args,**kwargs)
        '''执行函数之后要做的'''
        return re
    return inner

装饰器固定格式----带wraps

from functools import wraps

def deco(func):
    @wraps(func) #加在最内层函数正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

带参数的 装饰器,在不删除装饰器的情况下(因为可能有成千上万个啊)可以随时设置是否使用装饰器,加标志位

def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args,**kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()

多个装饰器装饰同一个函数,

def wrapper1(func):
    def inner():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner

def wrapper2(func):
    def inner():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner

@wrapper2
@wrapper1
def f():
    print('in f')

f()
思考一下?两个装饰器的执行顺序

可迭代对象、迭代器,生成器

要了解生成器,我们先搞明白可迭代对象吧

迭代:和循环相似,基本可以画等号
迭代对象:可用于for循环的对象
有哪些可迭代对象:list,tuple,dict,set,str和generator
迭代器:可以被next()函数调用并返回下一个值得对象称为 迭代器:Iterator

生成器:是一种生成的迭代器,显著特征是yield



列表生成式


我们之前已经了解了,并用过了这个工具
    

>>> a_list = [i for i in range(5)]
>>> a_list
[0, 1, 2, 3, 4]
>>> type(a_list)
<class 'list'>

生成器


只需要把[] 换成()。我们来看一下区别
    

>>> a_generator = (i for i in range(5))
>>> a_generator
<generator object <genexpr> at 0x000001C7E8820200>
>>> type(a_generator)
<class 'generator'>
>>> next(a_generator)
0
>>> next(a_generator)
1

我们看见,它并不会生产一个类似列表的东西 ,而是一个“generator object”,叫做生成器对象
通过查看类型,我们发现是一个“generator ”
通过使用next()函数,可以依次取到里面的值,直到取完,然后报错
    
>>> next(a_generator)
4
>>> next(a_generator)
Traceback (most recent call last):
         File "<pyshell#18>", line 1, in <module>
         next(a_generator)
     StopIteration

现在总结一下生成器的特点


1、不会主动产生,只有在需要的时候,可以通过next()获取,并且是随时取,随时停
2、随时取,随时停,有记忆功能,紧跟在上次取得值得后面
3、无法通过索引取值,不能切片,他已经不是一个列表或者元组对象了
4、只能往前走,也就是,取一个,生成器里可取的值就少一个,无法返回
5、取完了所有的值,就会报错


生成器的优点

节省内存空间,因为只在需要的时候生产


想想,我们刚才只是range(5),当然运行起来很流畅
但是,如果是range(100000000),用列表生成式的话,一不小心电脑就会卡死的
而生成器不会
你get到了吗?

顺便提一下在python2里面的的xrange和range


在python3中是这样的
   

>>> range(10)
range(0, 10)
>>> m = range(10)
>>> type(m)
<class 'range'>


在python2是这样的
   

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)
>>> m = range(10)
>>> type(m)
<type 'list'>


仅此而已

迭代器的next()方法


    
可能你已经发现了,我们每次都要用next()函数去手动调用,这是很累的,如果有100000000个....
这种脏活应该交给电脑去做啊
用for循环
    

>>> for i in a_generator:
             print(i)

    
     0
     1
     2
     3
     4
     5
     6
     7
     8
     9


用while 循环
    

>>> while True:
             next(a_generator)

    
     0
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11


运行到最后,会发现(我真的等到最后了...
用while到结束后会报错,所以我们一般用for
你会发现挺管用的是不是,但是它好像一旦开始,就停----不----下----来

这时,该拿出神器了(如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:)


用法提示:

1、yield 必须放在函数中

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

3、generator在每次调用next(i)或者__next()__的时候执行,遇到yield语句返回,再次被next()调用时从上次返回的yield语句处继续执行。

4、yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
      

def fib(max):
n, a, b = 0, 0, 1
while n < max: # 一定要有停止条件,要不就会无限循环下去
yield b # 使用yield 返回结果
a, b = b, a + b
n += 1

data = fib(100)
print(data)
print(next(data))
print(data.__next__())
print('去干点其他事情')
print(data.__next__())
print(next(data))
print('下面用for循环自动获取')
# 因为有记忆功能,所以会接着上次的继续
for i in data:
print(i)


还是上面的例子,先看效果

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


data = fib(5)
print(data)
print(next(data))
print(next(data))
print(data.__next__())
print(data.__next__())
print('去干点其他事情')
print(data.__next__())
print(data.__next__())
print(next(data))
print(next(data))
print('下面用for循环自动获取')


    
如果你运行了这段代码,会发现报错,那就对了,而且错误是StopIteration:+函数的返回值

尝试用for循环
    

def fib(max):
         n, a, b = 0, 0, 1
         while n < max:
             yield b
             a, b = b, a+b
             n += 1
         return 'Done'
         
data = fib(5)
for i in data:
    print(i)

这样好像就不报错了,可是,我们的返回值呢,该怎么获取呢?
返回值包含在StopIteration的value中,关于如何捕获错误,后面的错误处理继续学习吧。以后一定补上

总结一下:


         函数里有yield 之后,意味着函数func()就变成了生成器.生成器函数。
         yield 和 return 的作用很相似(但是return表示终止),把结果返回到函数外边,交给调用者。
         启动生成器有三种方法
             1、next(i)或者i.__next__(),这是很原始的方法,一般不会用
             2、while循环,其实也是基于next(),结束会报异常
             3、for循环,拿不到返回值,结束后不报异常,要通过其他方法获取retrun的返回值
         如果有retrun,代表终止,返回值会发给StopIteration
    

生成器的send方法


怎么才能让生成器随时立即停止呢?用send方法
        

def fib(max_num):
    n, a, b = 0, 0, 1
    while n < max_num:
        sign = yield b
        if sign == 'stop':
            return 'KO'
            # 或者使用break,只是返回值是Done
            # break
        a, b = b, a + b
        n += 1
    return 'Done'


data_fib = fib(10)
next(data_fib)
# data_fib.send(None)  # 或者使用开始第一个,send的参数一定要是None
data_fib.send(None)  # seed方法 发送消息给生成器内部
next(data_fib)
fib_1 = next(data_fib)
print(fib_1)
data_fib.send('stop')

        

总结一下:


1、send:获取下一个值的效果和next基本一致,即,唤醒并继续执行;
2、可以发送一个信息到生成器内部,yield就是一个默认发送None的send消息;
3、只是在获取下一个值的时候,给上一yield的位置传递一个数据,我们可以利用这个获得的数据做点什么。


使用send的注意事项


1、第一次使用生成器的时候 是用next获取下一个值,也可以用send(None)而send(其他内容)不会有效;
2、最后一个yield不能接受外部的值


                 

可迭代对象(iterable),迭代器(iterator),生成器(generator)的关系

可迭代对象iterable(1)


定义:可以直接作用于for 循环的数据统称为  可迭代对象
         一类是集合类型,一类是generator(包括生成器和带yield的generator function)

可以被next()函数调用并返回下一个值得对象称为 迭代器:Iterator   
可以通过iter()把iterable 变成iterator
        
用isinstance()判断一个对象是否是iterable 对象
Isinstance(‘abc’,iterable)会返回布尔值以判断

为什么list dict 等不是Iterator
          因为iterator是一个数据流,可以无限大,不知道它什么时候结束,可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能通过next()函数实现按需计算下一个数据,它是有惰性的,只有在需要返回时,才会计算

迭代器一定是迭代对象,
         迭代对象不一定是迭代器,可以对迭代对象使用iter()方法来生成迭代器。
生成器一定是迭代器,
         迭代器不一定是生成器,生成器更高级,有两种类型(生成器函数,和生成器表达式)

posted @ 2018-05-26 21:36  游小刀  阅读(131)  评论(0编辑  收藏  举报
levels of contents