Fork me on GitHub

第三章| 3.3 函数进阶

 4、高阶函数

命名空间

 

又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方

名称空间共3种,分别如下

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

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

作用域即范围

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

查看作用域方法 globals(),locals()

作用域查找顺序

 LEGB:

L:locals  ; E:enclosing相邻的 ; G:globls  ; B:builtins

n = 10
def func():
    n = 20
    print('func:' ,n)  #func:20
    def func2():
        n = 30
        print(' func2' ,n) #func2:30

        def func3():
            print('func3:' , n) #func3:30
        func3()
    func2()
func() 

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

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

闭包

  在函数里边又套了一层子函数,在子函数被返回了,就是当外层函数执行的时候子函数被返回了返回了内存地址;然后在外边执行这个子函数的时候它又引用了外边函数的这个变量,相当于这个子函数跟外层函数有某种扯不清的关系,这种关系就叫闭包。

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

 

 装饰器( 闭包+函数的重新赋值)

软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块不应该被修改
  • 开放:对现有功能的扩展开放

实现装饰器知识储备:
1. 函数即“变量”
2. 高阶函数;  a、把一个函数名当做实参传入另一个函数 ;b、返回值中包含函数名(不修改函数的调用方式) 
3. 嵌套函数 
高阶函数+嵌套函数=》装饰器

在不改变原代码前提下给它加一个装饰的功能:搞一个高阶函数,把要修饰的当做参数传进去,然后在里边返回。

定义:本质是函数(装饰其他函数)就是为其他函数添加附加功能
原则:1.不能修改被装饰的函数的源代码;2.不能修改被装饰的函数的调用方式;
装饰器对它被装饰的函数是完全透明的,就是别人不知道你改了它的,没影响。

如:

编写装饰器,为每个函数加上统计运行时间的功能

提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间

import time
def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print('the func run time is %s'%(stop_time - start_time))
def test1():
    time.sleep(2)
    print('in the test1')

deco(test1)     #test1()加括号就是把它的运行结果传进去了; 这种调用方式改变了函数的调用方式。改变了test1的调用方式。test1=deco(test1) ---> test1();前提是把它修改成第二种形式的高阶函数加个return返回它的内存地址即它的返回值。
  



import time
def timer(func):  #timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()  #运行test1()
        stop_time = time.time()
        print('the func run time is %s'%(stop_time - start_time))
    return deco  #第二种形式的高阶函数
def test1():
    time.sleep(2)
    print('in the test1')
#print(timer(test1))  #返回的是deco的一个内存地址
test1 = timer(test1) #执行timer(test1)这个函数,把test1当做参数传进去;拿到一个返回结果deco; 代码走到这一步的时候会执行timer(test1),不会往下走了
test1()  ## 实际上运行执行的是deco    #既没有改变调用方式也没有改变原代码



#######初级装饰器 import time def timer(func): #timer(test1) func=test1 def deco(): start_time = time.time() func() #运行test1() stop_time = time.time() print('the func run time is %s'%(stop_time - start_time)) return deco @timer #--->> test1 = timer(test1) def test1(): time.sleep(2) print('in the test1') @timer #--->> test2 = timer(test2) def test2(): time.sleep(4) print('in the test2') test1() test2()

 

#####中级带参数装饰器
import time
def timer(func):  #timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)  #运行test1()
        stop_time = time.time()
        print('the func run time is %s'%(stop_time - start_time))
        return func  #把函数的结果要给返回了
    return deco
@timer #--->>  test1 = timer(test1)
def test1():
    time.sleep(2)
    print('in the test1')
    return 'from test1'
@timer #--->> test2 = timer(test2)  = deco  test2(name,age)=deco(name,age) ==deco(arg1,arg2)-->func(arg1,arg2)
def test2(name,age):
    time.sleep(4)
    print('test2',name,age)

test1()
test2('alex',22)
打印:
in the test1
the func run time is 2.0001144409179688
test2 alex 22
the func run time is 4.0002288818359375

 

#####高级版
import time
user,passwd = 'kris' , 'abc123'
def auth(auth_type):  #auth_type : 'local'
    print("auth func:" , auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:" , *args,**kwargs)

            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[33;1m User has passed authentication \033[0m" )
                    res = func(*args,**kwargs) #from home
                    print("-------after authentication")
                    return res  #这样就把home的执行结果返回了
                else:
                    exit("\033[31;lmInvalid username or password\033[0m")
            elif auth_type == "ldap":
                print("ladp呀呀")
        return wrapper
    return outer_wrapper
def index():
    print("welcome to index page")
@auth (auth_type="local")   #本地的认证 #home = wrapper();最终返回的是wrapper;你现在加上括号了,执行了;
def home():
    print("welcome to home page")
    return "from home"   #home执行完之后返回的数据,不能改变执行结果;怎么获取home的返回结果呢
@auth(auth_type="ldap")   #远程的认证
def bbs():
    print("welcome to bbs page")
index()
print(home()) #相当于调用的是wrapper()
bbs()
##打印:
auth func: local
auth func: ldap
welcome to index page
wrapper func args:
Username:kris
Password:abc123
 User has passed authentication 
welcome to home page
-------after authentication
from home
wrapper func args:
ladp呀呀

 

 

#_*_coding:utf-8_*_

user_status = False #用户登录了就把这个改成True

def login(func): #把要执行的模块从这里传进来

    def inner(*args,**kwargs):#再定义一层函数
        _username = "alex" #假装这是DB里存的用户信息
        _password = "abc!23" #假装这是DB里存的用户信息
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func(*args,**kwargs) # 看这里看这里,只要验证通过了,就调用相应功能

    return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数


def home():
    print("---首页----")

@login
def america():
    #login() #执行前加上验证
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

# @login
def henan(style):
    '''
    :param style: 喜欢看什么类型的,就传进来
    :return:
    '''
    #login() #执行前加上验证
    print("----河南专区----")

home()
# america = login(america) #你在这里相当于把america这个函数替换了
henan = login(henan)

# #那用户调用时依然写
america()

henan("3p")

 

 带参数的装饰器

#_*_coding:utf-8_*_
user_status = False #用户登录了就把这个改成True

def login(auth_type): #把要执行的模块从这里传进来
    def auth(func):
        def inner(*args,**kwargs):#再定义一层函数
            if auth_type == "qq":
                _username = "alex" #假装这是DB里存的用户信息
                _password = "abc!23" #假装这是DB里存的用户信息
                global user_status

                if user_status == False:
                    username = input("user:")
                    password = input("pasword:")

                    if username == _username and password == _password:
                        print("welcome login....")
                        user_status = True
                    else:
                        print("wrong username or password!")

                if user_status == True:
                    return func(*args,**kwargs) # 看这里看这里,只要验证通过了,就调用相应功能
            else:
                print("only support qq ")
        return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数

    return auth

def home():
    print("---首页----")

@login('qq')
def america():
    #login() #执行前加上验证
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

@login('weibo')
def henan(style):
    '''
    :param style: 喜欢看什么类型的,就传进来
    :return:
    '''
    #login() #执行前加上验证
    print("----河南专区----")

home()
# america = login(america) #你在这里相当于把america这个函数替换了
#henan = login(henan)

# #那用户调用时依然写
america()

# henan("3p")

 

5、生成器&迭代器

列表生成器

直接对列表里边的值修改

>>>a = [ i*i for i in range(10)]
>>>a
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>>a = list(range(10))
>>>a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,]
>>>a = [ i if i < 5 else i*i for i in a]
>>>a
[0, 1, 2, 3, 4, 25, 36, 49, 64, 81 ]

生成器

range(0,10,2)代表指向了0,2,4,6,8这几个值,最后的2代表步长;

>>> m=range(10)
>>> m[2]
2
>>> m[-1]
9
>>> m[5]
5

yield 语句可以让普通函数变成一个生成器,并且相应的 __next__() 方法返回的是 yield 后面的值。

一种更直观的解释是:程序执行到 yield 会返回值并暂停,再次调用 next() 时会从上次暂停的地方继续开始执行。

生成器的创建方式:1.列表生成式()(通过list函数生成列表); 2.函数

>>>a2 = (i for i in range(1000))
>>>a2
<generator  object  <genexpr>  at 0x1014a73b8>  ##生成了一个生成器
>>>next(a2)
0
>>>next(a2)
1
>>>next(a2)
2

 上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81

 

def fib(max):
    n, a, b = 0, 0 ,1
    while n < max:
        print('before yield')
        yield b  #把函数的执行过程返回冻结在这一步了,并且把b的值返回给了外面的next()
        print(b)
        a, b = b, a+b
        n = n + 1
    return 'done'
f = fib(15) #turn function into a generator
next(f) #first time call next()
next(f) #first time call next()
next(f) #first time call next()
next(f)

打印出:
before yield
1
before yield
1
before yield
2
before yield

 

斐波那契

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

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

 

 主要就是yield后边那两句;里边有yield函数一加括号,代码根本不执行,只生成一个生成器。

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
done

 

fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

##用函数写生成器
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>

用next调用它结果是一样的:

1
1
2
3
5
8
13
21
34
55

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

 函数写生成器

def range2(n):
    count = 0
    while count < n:
        #print(count) #
        count +=1
        yield count  #return
print(range2(10))    #打印: <generator object range2 at 0x10159ac50>  #一个生成器

打印:
1
2
3
4
5
6
7
8
9
10

 

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

new_range = range2(10)
r1 = next(new_range)
print(r1)
for i in range2(10):
    print(i)

打印:
count 0
1
count 0
1
count 1
2
count 2
3
count 3
4
count 4
5
count 5
6
count 6
7
count 7
8
count 8
9
count 9
10

 

def range2(n):
    count = 0
    while count < n:
        print(‘count’ ,count)  
        count +=1
        yield count  #return

new_range = range2(10)
r1 = next(new_range)
print(r1) 
r2 = next(new_range)
print'干别的事'print(r2)
next(new_range)  # == new_range.__next__() #这两个是一样的
new_range.__next__()

打印:
count 0
1
count 1
干别的事
2
count 2
count 3

 

 

生成器调用方法

python2:  range = list           Python3: range = 生成器

               xrange = 生成器                     xrange | 没有

 

生成器send方法

只要函数里边有yield就是生成器了;

迭代器(循环)

范围比生成器大

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

一类是集合数据类型,如listtupledictsetstr等;

一类是generator,包括生成器和带yield的generator function。

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

凡是可作用于for循环的对象都是Iterable(可迭代)类型;凡是可作用于next()函数的对象都是Iterator(迭代器)类型,它们表示一个惰性计算的序列;集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

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

 

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

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

实际上完全等价于:

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

创建一个迭代器有3种方法,其中前两种分别是:

  1. 为容器对象添加 __iter__() 和 __next__() 方法(Python 2.7 中是 next());__iter__() 返回迭代器对象本身 self__next__() 则返回每次调用 next() 或迭代时的元素;
  2. 内置函数 iter() 将可迭代对象转化为迭代器
  3. 生成器,生成器通过 yield语句快速生成迭代器,省略了复杂的 __iter__() & __next__() 方式。

创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即可。

 

posted @ 2018-06-09 21:06  kris12  阅读(364)  评论(2编辑  收藏  举报
levels of contents