第4天,函数进阶

[toc]

一、函数对象

在python中,函数是一等对象,“一等对象”满足下述条件的程序实体:

  • 可以被引用
  • 可以当作参数传递给函数
  • 能做为函数的返回结果
  • 可以当作容器数据类型的元素,例如:可以当作字典中某个key的value
  • 在运行时创建

1.可以被引用

def foo():
    print('from foo')

func=foo

print(foo)
print(func)
func()

# 输出:
<function foo at 0x0000000000BAE1E0>
<function foo at 0x0000000000BAE1E0>
from foo

2. 可以当作参数传递给另一个函数

def foo():
    print('from foo')

def bar(func):
    print(func)
    func()         # 此时func() == foo()

bar(foo)           #将函数foo内存对象当做参数传给bar()

# 输出:
<function foo at 0x000000000114E1E0>
from foo

3.可以当作一个函数的返回结果

def foo():
    print('from foo')

def bar(func):
    return func

f=bar(foo)       # 此时f = foo

print(f)
f()         # 此时执行f() 相当于执行foo()

# 输出:
<function foo at 0x0000000000B8E1E0>
from foo

4. 当作其他容器类型数据的元素

def foo():
    print('from foo')
    
dic={'func':foo}   # 定义一个字典dic,将函数foo的内存对象做为value

print(dic['func'])  # 相当于打印foo的内存对象
dic['func']()       # 相当于执行foo()函数

# 输出:
<function foo at 0x0000000000A4E1E0>
from foo

应用:

def select(sql):
    print('========>select')

def insert(sql):
    print('========>add')

def delete(sql):
    print('=======>delete')

def update(sql):
    print('=======>update')


func_dic={
    'select':select,
    'update':update,
    'insert':insert,
    'delete':delete
}    # 定义一个字典,将上面定义的函数名做为value

def main():
    while True:
        sql = input('>>: ').strip()
        if not sql:continue
        l = sql.split()       
        cmd=l[0]
        if cmd in func_dic:
            func_dic[cmd](l)

main()

二、函数嵌套

1. 函数的嵌套定义

简单理解就是在定义一个函数时,函数体内又定义一个子函数,示例如下:

def f1():
    def f2():
        print('from f2')
        def f3():
            print('from f3')
        f3()
    f2()
f1()

# 输出:
from f2
from f3

2. 嵌套函数的调用

下面示例是一个求4个数中最大值的小程序:

# 嵌套函数
def mymax(x,y):
    return x if x > y else y

def four(a,b,c,d):
    res1=mymax(a,b)  # 调用mymax()函数先比较a和b,较大的值给res1    
    res2=mymax(c,res1)
    res3=mymax(d,res2)
    return res3

print(four(8,45,9,34))

# 输出:
45

三、名称空间与作用域

1.定义名字的方法

  • 导入的模块名称time,如:import time
  • 定义变量的名称name,如:name = 'caigy'
  • 定义函数的名称func,如:
def func():
    pass
  • 定义类的名称foo,如:
class foo:
    pass

2. 三种名称空间

2.1 内置名称空间

随着Python解释器的启动而产生,比如python内置的一些函数:sun()、max()、min()等,这些函数随着python的启动就定义好了,所以在定义名称时不要与这些关键字重名
可以用以下方法查看python的内置函数:

import builtins
for i in dir(builtins):
    print(i)
2.2 全局名称空间

py文件的执行会产生全局名称空间,指的是文件级别定义的名字都会放入该空间

2.3 局部名称空间

调用函数时会产生局部名称空间,只在函数调用时临时绑定,函数调用结束后解除绑定。

如下示例:

# !/user/bin/env python
# -*- coding:utf-8 -*-

name = 'caigy'   # 1

def func():
    x = 1        # 2

代码中:

  1. 变量name是全局名称空间
  2. 变量x在函数func()中,是局部名称空间

3. 作用域

  • 全局作用域:内置名称空间、全局名称空间
  • 局部作用域:局部名称空间

python中名字的查找顺序:
局部名称空间 ---> 全局名称空间 ---> 内置名称空间

  • 查看全局作用域内的名字: globals()
  • 查看局部作用域内的名字: locals()

示例代码:

name = 'caigy'
def foo():
    name = 'egon'
    print(name)
    print(locals())
    print(globals())
foo()

以上代码输出结果:

egon
{'name': 'egon'}
{'__file__': 'D:/PycharmProjects/s17/day04/test.py', 'foo': <function foo at 0x013B5660>, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01755A90>, 'name': 'caigy', '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__cached__': None}

作用域的有效范围

  • 全局作用域的名字:全局有效,在任何位置都能被访问到,除非del删掉,否则会一直存活到文件执行完毕
  • 局部作用域的名字:局部有效,只能在局部范围内调用,只在函数调用时才有效,调用结束就失效

注:当全局变量与局部变量同名时,在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用

四、闭包函数

闭包函数的特性:

  • 定义在一个函数的内部的函数
  • 包含对外部作用域而非全局作用域的引用

该内部函数就称为闭包函数
如下示例:

def func():
    name = 'caigy'
    def foo():
        print(name)
    return foo

f = func()  # func()返回结果是foo ,所以f = foo
print(f)    # 打印的实际上是foo的内存对象地址
f()

# 输出结果:
<function func.<locals>.foo at 0x01342150>
caigy

如上述代码中,foo()就是一个闭包函数

闭包函数的应用:随性计算
爬取一个网页

from urllib.request import urlopen

def index(url):
    def get():
        return urlopen(url).read()
    return get

f= index('http://www.360duohui.com')
res = f().decode('utf-8')
print(res)

上述代码中get()函数就是一个闭包函数

总结:闭包函数实现了内部函数在外部可以被调用

获取闭包函数所引用的外部变量(非全局变量)的值

def func():
    name='caigy'
    def foo():
        print(name)
    return foo
f = func()
print(f.__closure__[0].cell_contents)

# 输出:
caigy

五、装饰器(decorator)

装饰器,顾名思义,就是用来装饰用的,具体点说就是给其它程序添加功能的。其本质就是函数,功能是为其他函数添加新功能。

装饰器本身可以是任何可调用对象,被装饰的对象也可以是任意可调用的对象。

1. 为什么要用装饰器呢?

源于程序开的开放封装原则:

  • 对修改是封闭的,对扩展是开放的。
  • 装饰器就是为了在不修改被装饰对象的源代码以及调用方式的前提下,为其添加新功能

如下示例:

def run():
    time.sleep(3)
    print('already test 3s')
run()

为上述代码添加一个打印日志的功能:
无参装饰器

import time
def timer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        func()     #3
        stop_time = time.time()
        print('run is %ss'%(stop_time-start_time))
    return wrapper

timer       #1
def run():
    time.sleep(3)
    print('already test 3s')
run()       #2

#1 @timer 实际上就是做了run = timer(run) 的操作;
#2 因为timer()函数的返回值是wrapper,所以此时的run = wrapper , 所run()实际就相当于调用了wrapper()
#3 run 做为参数传给timer()函数,所以此时func=run,此时的func()相当于调用了run()

以上代码的输出结果如下:

already test 3s
run is 3.0033679008483887s

2. 附加多个装饰器

有参装饰器

import time
def timer(func_1):
    def wrapper_1(*args,**kwargs):
        start_time = time.time()
        func_1(*args,**kwargs)      #3
        stop_time = time.time()
        print('run is %s秒 '%(stop_time-start_time))
    return wrapper_1

user_stat = {'name':None,'stats':False}
def auth(func):
    def wrapper(*args,**kwargs):
        if user_stat['name'] and user_stat['stats']:
            print('login successful')
            func(*args, **kwargs)
        else:
            name = input('name: ')
            pwd = input('password: ')
            if name == 'egon' and pwd == '123':
                # 登录成功后记录用户名和登录状态,下次调用时就不用再输入用户名登录了
                user_stat['name'] = 'egon'
                user_stat['stats'] = True
                print('login successful')
                func(*args,**kwargs)
                # time.sleep(3)
            else:
                print('login error')
    return wrapper

@timer   #2
@auth    #1  
def index():
    print('Welcome to Oldboyedu.com')

@auth
def home(name):
    print('Welcome to %s homepage'%name)

index()   #4
home('egon')

输出结果:

name: egon
password: 123
login successful
Welcome to Oldboyedu.com
run is 3.2893900871276855秒 
login successful
Welcome to egon homepage

#1 @auth 实际上就是做了index = auth(index)的操作,因为auth()函数的返回值是wrapper,所以index此时等于wrapper,在下方加括号()就可以直接调用wrapper()函数了
#2 由于此时@auth 为wrapper, 所以@timer 就是wrapper = timer(wrapper) , timer()的返回值是wrapper_1,所以此时wrapper = wrapper_1
#3 由于wrapper被当作参数传给timer(),所以此时func_1 = wrapper
#4 在执行index()时,相于当于执行wrapper_1() ;当程序执行到func_1(args,*kwargs)时,此时的func_1 就是 wrapper,所以就会调用wrapper(args,*kwargs);当wrapper()函数内的程序执行到func(args,*kwargs)时,此时的func = index ,所以用调用真实定义的index()函数;然后返回wrapper_1(),继续执行func_1()后的代码。

六、迭代器(iterator)

1. 迭代的概念

重复的过程称为迭代,每次重复即为一次迭代,并且每次迭代的结果作为下一次迭代的初始值;
不是迭代:

while True: #只满足重复,因而不是迭代
    print('====>')

下面才为迭代

l = [1, 2, 3]
count = 0
while count < len(l):  
    print('====>', l[count])
    count += 1

# 输出:
====> 1
====> 2
====> 3

注:以上这种方式迭代的是列表,而列表是有序的对象,那像无序的对象,比如:字典,集合,文件等如何迭代呢?

2. 为什么要有迭代器

对于没有索引的数据类型,必须提供一种不依赖索引的迭代方式
可迭代的对象:内置__iter__方法的对象,都是可迭代的对象

[1,2].__iter__()        # 列表
'hello'.__iter__()      # 字符串
(1,2,).__iter__()       # 元组
{'a':1,'b':2}.__iter__()    # 字典
{1,2,3}.__iter__()      # 集合

迭代器: 执行__iter__方法,得到的结果就是迭代器,迭代对象都具体__next__方法

i = [1,2,3].__iter__()
print(i)    # 打印可迭代对象的内存地址

print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())   
    # 超出迭代的对象个数后会抛出异常:StopIteration

# 输出:
<list_iterator object at 0x011B5AB0>
1
2
3
Traceback (most recent call last):
  File "D:/PycharmProjects/s17/day04/test.py", line 209, in <module>
    print(i.__next__())
StopIteration

迭代字典

i={'a':1,'b':2,'c':3}.__iter__()

print(i.__next__())     # 迭代字典中的key
print(i.__next__())
print(i.__next__())
print(i.__next__())  
    # 超出字典i中的键值对的个数,会抛出异常:StopIteration

换一种方法:

i={'a':1,'b':2,'c':3}
I = iter(i)

print(next(I))
print(next(I))
print(next(I))

注:__iter__() = iter(),__next__() = next(),两种方法得到的结果是一样的。

利用while循环迭代字典

dic={'a':1,'b':2,'c':3}
i=dic.__iter__()   # 生成迭代器对象
while True:
    try:
        key=i.__next__()
        print(dic[key])
    except StopIteration:  # 遇到指定异常后,执行子代码
        break

3. 如何判断一个对象是可迭代对象,还是迭代器对象

  • 可迭代对象:只有__iter__方法,执行该方法得到的是迭代器对象
  • 迭代器对象:有__next__方法和__iter__方法,迭代器对象执行__iter__方法,得到的结果仍然是它本身。

判断可迭代的对象需要导入Iterable模块

from collections import Iterable,Iterator

f = open('a.txt','w')
f.__iter__()

# 下列数据类型都是可迭代的对象
print(isinstance('abc',Iterable))       # 字符串
print(isinstance([1,2,3],Iterable))     # 列表
print(isinstance({'a':1,},Iterable))    # 字典
print(isinstance({1,2,3},Iterable))     # 集合
print(isinstance((1,2,),Iterable))      # 元组
print(isinstance(f,Iterable))           # 文件    

# 输出:
True
True
True
True
True
True

判断迭代器对象需要导入Iterator模块

from collections import Iterable,Iterator

f = open('a.txt','w')
f.__iter__()

# 只有文件是迭代器对象
print(isinstance('abc',Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({'a':1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))

# 输出:
False
False
False
False
False
True

验证迭代器对执行__iter__方法得到的结果仍然是其本身

f = open('a.txt','w')
f1 = f.__iter__()
print(f)
print(f1)
print(f1 is f)      # 输出`True`表示结论正确 

# 输出:
<_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>
<_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>
True

4. 迭代器的优点和缺点

其特点:

  • 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
  • 不能随机访问集合中的某个值 ,只能从头到尾依次访问
  • 访问到一半时不能往回退
  • 便于循环比较大的数据集合,节省内存

  • 优点:

    • 提供了一种不依赖于下标的迭代方式
    • 就迭代器本身来说,更节省内存
  • 缺点:

    • 无法获取迭代器对象的长度
    • 不如序列类型的数据取值灵活,而且是一次性的,只能往后取值,不能往前退

      验证:只能往后取值,不能往前退

        l=[10000,2,3,4,5]
        i=iter(l)
        for item in i:
            print(item)
        print('=====================')
        
        for item in i:
            print(item)
        
        # 输出:
        10000
        2
        3
        4
        5
        ====================
        # 通过上述代码 的结果可以看出,通过列表l生成的迭代器i ,通过for循环迭代后,再次通过for循环迭代,就取不到值了

扩展:enumerate()方法生成的也是迭代器对象

l=[2,3,4]

i=enumerate(l)

print(next(i))
print(next(i))
print(next(i))

# 输出:
(0, 2)
(1, 3)
(2, 4)

总结:python中for循环就是通过迭代器的方式来实现的,而while只是普通的循环,for迭代 == while + try ... except(异常处理)

七、生成器(generator)

只要函数体内包含yield关键字,该函数就是生成器函数

1. 生成器简单示例

生成器就是迭代器

def index():
    print('first')
    yield 1
    print('second')
    yield 2
    print('third')
    yield 3

g = index()
for i in g:
    print(i)

上述代码输出:

first
1
second
2
third
3

通过next()方法:

def index():
    print('first')
    yield 1
    print('second')
    yield 2
    print('third')
    yield 3

g = index()
print(next(g))  # next()方法触发迭代器g的执行,进而触发函数的执行
print(next(g))
print(next(g))

# 输出:
first
1
second
2
third
3

生成器应用 :

def counter(n):
    print('start...')
    i=0
    while i < n:
        yield i
        i+=1
    print('end...')

g=counter(5)
print(g)    # 这一步不会执行counter()函数,如果是普通函数,就会被执行
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

# 输出:
<generator object counter at 0x0196DB10>
start...
0
1
2
3
4

注:上述代码中,迭代器对象g,只有通过next()方法(调用时,才会触发函数counter()的执行

总结:
yield的功能:

  • 相当于为函数封装好__iter____next__
  • return只返回一次值,函数就终止了,而yield能返回多次值,每次返回都会将函数暂停,下一次next()会从上一次暂停的位置继续执行

2. 生成器执行流程

def foo():
    print('in the foo ...')
    food = yield '您好'
    print('food >>>',food)
    print('我在后面')
    food1= yield '你坏'
    print('food1 >>> ',food1)

g= foo()
res = next(g)
print(res)
res1 = g.send('x')
print(res1)
##res2= g.send('xx')

'''
生成器执行流程:
1.g=foo(),只是将foo()生成器对象赋值给g,然后继续往下执行;

2.遇到next()或g.send('x')就开始执行foo()内部的代码,\
  执行遇到第一个yield时,就暂停(我也理解为进入休眠状态),\
  并将yield后面的值返回给next(g),并跳出到foo()外面next(g)所在的那一行,\
  将yield返回的值赋值给res

3.res接收yield返回给next(g)的值,然后往下执行代码,打印res的值;

4.当再次遇到next()或g.send('x')时,唤醒foo()继续从上次 \
  暂停的位置开始执行, 同时将g.send(‘x’)中的'x'发送 \
  给第一个yield,并赋值给food,然后继续往下执行;

5.当遇到第二个yield时,进入暂停(休眠),\
  同时将yield后面的值返回给g.send('x'),\
  跳出到g.send('x')所在的那一行,并将yield返回的值赋值给res1,\
  然后继续执行至结束。
    
注意:
    print(res1)后面没有代码了,此时foo()中的food1是空,\
    如果print(res1)后面再出现g.send('xx')代码,\
    才会将'xx'发送给第二个yield,并赋值给food1;
    但是,foo()内部会从第二个yield那一行继续往下执行,\
    如果后面没有yield关键字了,程序就会抛出一个StopIteration异常。
'''

生成器的应用:
实现Linux命令tail -f a.txt | grep 'python'的功能

import time
def tail(filepath):
    with open(filepath,encoding='utf-8') as f:
        f.seek(0,2)     # 跳到文件末尾
        while True:
            line=f.readline().strip()
            if line:
                yield line      #3
            else:
                time.sleep(0.2)

def grep(pattern,lines):        #1
    for line in lines:          #2
        if pattern in line:     #4
            yield line          #5

g=grep('python',tail('a.txt'))
print(g)

for i in g:                     #6
    print(i)

程序解析:
#1 pattern = 'python' , lines = tail('a.txt')
#2 此时的lines是一个迭代器,经过for循环会触发tail('a.txt')函数的执行,这时运行tail()函数内的程序,会通过while循环监控文件末尾是否有内容
#3 如果有新内容,则通过yield返回
#4 第#3通过yield返回的line传给#4行的line
#5 这里将line通过yield返回给#6
#6 这时i = line

上一篇:Memcached工作原理


下一篇:PyCharm使用技巧