# python04---函数

一. 初识函数

"""
def 函数名(参数):
    函数体
  返回值
"""
# def: 定义函数关键字
# 函数名: 遵循变量命名规范
# 参数: 可有可无
# 返回值:非必须
# 函数体:逻辑语句

1.1 定义函数

s = '金老板小护士'

def my_len():    # 定义/声明函数
    i = 0
    for k in s:
        i+=1
    # print(i)
    return i  # 返回值

my_len()    # 函数调用

length = my_len()
print(length)

特点:
定义了之后,可以在任何需要它的地方调用(代码的复用)
没有返回长度,只是单纯的打印 (返回的重要性)

1.2 返回值

关键字,retuen

# 没有返回值  --- 默认返回 None
    不写 return
    只写 return   # 结束一个函数
    return None  # 不长用
  • 返回一个值
"""
 1. 可以返回任何数据类型
 2. 只要返回了,就可以接受到
 3. 如果在一个程序中有多个return,那么只执行第一个
"""
  • 返回多个值
def func():
    return 1,2

r1,r2 = func()
print(r1,r2)

"""
 1. 用多个变量进行接收:有多少返回值就用多少变量接收
 2. 返回多个值用一个变量接收,得到一个元组
"""

def func():
    return 1,2  # (1,2)

r = func()
print(r)

1.3 参数

image.png

形参,实参,默认参数,动态参数

def func(位置参数,*args,默认参数,**kwargs):
  pass
'''
# 没有参数
        - 定义函数和调用函数时括号里都不写内容
# 有一个参数
        - 传什么就是什么
# 有多个参数
        - 位置参数
'''

def my_sum(a,b):
    res = a+b
    return res

print(my_sum(1,2))

站在实参角度传参

def my_sum(a,b):
    print(a,b)
    res = a+b
    return res

ret = my_sum(2,3)
print(ret)
ret = my_sum(b =2,a=4)
# ret = my_sum(2,b=4)
print(ret)

ret = my_sum(3,b=2)
print(ret)

"""
站在实参的角度上,
    - 按照位置传参
    - 按照关键字传参
    - 混着用可以:
        - 但是,必须先按照位置传参,再按照关键字传参
            - 不能给同一个变量传多个值
"""

站在形参的角度上

"""
站在形参的角度上
    - 位置参数:必须传,且有几个参数,传几个值
  - 默认参数:可以不传,如果不传就使用默认的参数,如果传了,就用传的
"""

def classmate(name, sex='男'):
    print('姓名:{}\n性别:{}\n'.format(name,sex))

classmate('天才','男')
classmate('小天才','女')
classmate('default')

只有调用函数的时候(想怎么写,怎么写)

"""
只有调用函数的时候
    - 按照位置传:直接写参数的值
  - 按照关键字传参:关键字 = 值
"""

定义函数的时候(想怎么写,怎么写)

'''
定义函数的时候:
    - 位置参数:直接定义参数
  
  - 默认参数,关键字参数:参数名= 默认的值
  
  - 动态参数:可以接收任意多个参数,参数名之前加*,习惯参数名 args / 只能接收,位置参数,不能接收关键字参数传递的值
      1) *args    接收的是按照位置参数传递的值,组成一个元祖
      2) **kwargs 接收的是按照关键字参数传递的值,组成一个字典
  
顺序:
    - 默认参数,在位置参数之后;
  - 必须先定义位置参数, 动态参数,后定义默认参数
'''
2. 动态参数

*args

# 不知道有多少位置参数要传递
def sum(*args):
    he = 0
    for i in args:    # 元祖
        he += i
    return  he

print(sum(1,2,3,4,5,6))
print(sum(1,2,3,4,5))
print(sum(1,2,3,4))

**kwargs :不知道有多少关键字参数要传递

def func(**kwargs):  
    print(kwargs)     # 字典

func(a=1,b=2,c=3)
func(a=1,b=2)
func(a=1)
# func(1) # 报错

'''
{'a': 1, 'b': 2, 'c': 3}
{'a': 1, 'b': 2}
{'a': 1}
'''

动态参数的另一种传参方式

def func(*args):  # 站在形参的角度上,给变量加上*,就是组合所有传来的值
    print(args)

func(1,2,3,4,5)
l = [1,2,3,4,5]
func(l[0],l[1],l[2])

func(*l) # 站在实参的角度上,给一个序列加上*号,就是将这个序列,按照顺序传递

# 传递字典
def func(**kwargs):
    print(kwargs)

func(a=1,b=2)

d = {'a':1,'b':2}
func(**d)  ## 将字典,按序列传递

1.5 函数的注释

def func(**kwargs):
    '''
    
    :param kwargs: 
    :return: 
    '''
    print(kwargs)

1.6 小结

image.png

二、函数进阶

2.1 命名空间

函数可以使用外部的变量,外部不能使用函数内部的变量

def func():
    a = 1

func()
print(a)    # a 获取不到

命名空间义工有三种:

  • 分别是内置命名空间;
  • 全局命名空间;
  • 局部命名空间

内置命名空间中存放了 python 解释器为我们提供的名字:input,print,str,list...它们都是我们熟悉的,拿过来就可以用的方法
image.png
三种命名空间支间的加载与取值顺序:

  • 加载顺序:

内置命名空间(程序运行前加载) ->  全局命名空间(程序运行中:自上而下) ->  局部命名空间(程序运行中:调用时才加载)

  • 取值
    • 在局部调用:

局部命名空间  ->  全局命名空间  ->  局部命名空间

  • 在全局调用

全局命名空间  ->  内置命名空间

2.2 作用域

# 作用域两种
# 全局作用域 --- 作用在全局 --- 内置和全局名字空间中的名字都属于全局作用域 --- globals()
# 局部作用域 --- 作用在局部 --- 函数(局部名字空间中的名字属于局部作用域)--- locals()

作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域
全局作用域:包含内置名称空间,全局名称空间,在整个文件的任意位置都能被引用、全局有效
局部作用域:局部名称空间,只能在局部范围内生效

两个方法 locals(),globals()

a = 1
b = 2

def func():
    x ='aaa'
    y = 'bbb'
    print(locals())  # 查看所有局部命名空间中的变量
    print(globals()) # 查看所有全局/内置命名空间中的变量
 
func()

# globals  永远打印全局的名字
# locals   输出什么,根据 locals 所在的位置

global关键字

a = 1
def func():
  global a     # 尽量通过参数和返回值的形式,替代 global
  a = 2
func()
print(a)

# 对于不可变数据类型,在局部中可以查看全局作用域中的变量
# 但是不能直接修改
# 如果想要修改,需要在程序的一开始添加 global 声明
# 如果在一个局部(函数)内声明了一个 global 变量,那这个变量在局部的所有操作将对全局的变量有效

2.3 函数嵌套和作用域链

比较大小

def max(a,b):
    return a if a>b else b

# 函数的嵌套调用
def the_max(x,y,z):
    c = max(x,y)
    return max(c,z)

print(the_max(1,2,3))

函数的嵌套定义

# 内部函数可以使用外部函数的定义
def outer():
    a = 1
    def inner():
        print('inner',a)
    return inner()

outer()

关键字:golobal(),nonlocal()
golobal():只对全局有效
nonlocal():只对局部生效

a = 1
def outer():
    a =1
    def inner():
        b = 2
        print('inner',a)
        def inner2():
            global a     # 只继承/复用全局的变量,最外层
            a+=1
            print('inner2',a)
        inner2()
    inner()

outer()
print('全局',a)

2.4 函数名的本质

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

不明白,那就记住一句话,就当普通变量用

  • 第一类对象(first-class object)指
    • 可在运行期创建
    • 可用作函数参数或返回值
    • 可存入变量的实体
a = 1
def outer():
    a =1
    def inner():
        a = 2

        def inner2():
            nonlocal a   # 声明了一个上层局部变量,值继承上层
            a+=1         # 不可变数据类型的修改
            print('inner2() a = {}'.format(a))
        inner2()

        print('inner()  a = {}'.format(a))
    inner()
    print('outer()  a = {}'.format(a))
outer()

print('全局',a)
def func():
    print(123)

# func() # 函数名就是内存地址

func2 = func() # 函数名可以赋值
func2()

l = [func,func2]  # 函数名可以作为容易类型的元素
print(l)

for i in l:
    i()
def func():
    print(123)

def wahaha(f):
    f()
    return f   # 函数名可以作为函数的返回值

wahaha(func)  # 函数名可以作为函数的参数

qqx = wahaha(func)
qqx()

2.5 闭包

定义

'''
1. 嵌套函数,内部函数调用外部函数的变量
2. 通过 return,在一个函数的外部,使用内部的函数
'''
def outer():
    a = 1
    def inner():
        print(a)
    print(inner.__closure__)   # <cell at 0x10ca86978: int object at 0x10c60ac00>
outer()
print(outer.__closure__)  # None

外部调用

def outer():
    a = 1
    def inner():
        print(a)
    return inner()

inn = outer()  # 变量 a 的值,不会因为 outer 函数的结束而消失 inn(),永不消失

inn()
import urllib # 模块,关于 url 请求的
from urllib.request import urlopen   # 打开网页,借助

def get_url():
    url = 'http://slide.news.sina.com.cn/s/slide_1_2841_351657.html#p=1'
    def get():
        ret = urlopen(url).read()
        print(ret)
    return  get

get_func=get_url()
get_func()

练习

part1 

# 1. 写函数,接收n 个数字,求这些参数数字的和

# 2. 读代码,回答:代码中,打印出来的值,a,b,c分别是什么?为什么
a = 10
b = 20
def test5(a,b):
    print(a,b)
c = test5(b,a)
print(c)

# 3. 读代码,回答:代码中,打印出来的值 a,b,c分别是什么?为什么
a = 10
b = 20
def test5(a,b):
    a = 3
    b = 5
    print(a,b)
c = test5(b,a)
print(c)

# 4. 用面向函数的思想完成购物车作业
# 函数一:实现三次登录功能
# 函数二:实现新用户注册功能
# 函数三:购物功能
# 进阶任务:将购物车功能拆分成多个功能

part2:必做题

# 1. 整理函数相关知识点,画思维导图,写博客
# 2. 写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
# 3. 写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回个调用者
# 4. 写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5.
# 5. 写函数,计算传入字符串中数字,字母,空格以及其他的个数,并返回结果
# 6. 写函数,检查用户传入的对象(字符串,列表,元组)的每一个元素是否包含空格,并返回结果
# 7. 写函数,检查传入字典的每一个 value 的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者
    dic = {'k1':'v1v1','k2':[11,22,33,44]}
  字典中的 value 只能是字符串或列表
# 8. 写函数,接收两个数字参数,返回比较大的那个数字
# 9. 写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成整个文件的批量修改操作(进阶)
# 10. 写一个函数完成三次登录功能,再写一个函数完成注册功能
1. 略
2. 写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
def check(l):
    print(l[1::2])
    
check((1,2,3,4,5))
check([1,2,3,4,5])

# 字符串、列表和元祖有规律的取值可以用它们共有的方法,切片
3. 写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5.
# 方法一(不推荐)
def check(l):
    print('>5') if len(l) > 5 else print('<5')
check('asdfadfsdfs')

# 方法二
def func(x):
    return len(x) > 5
print(func('abcdfd'))

# 公共方法 + 三目运算符
# 根据用途判断,是打印还是return
4. 写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回个调用者
# 方法一:
def func(x):
    if len(x) > 2:
        return x[:2]   # x[0:2]
    else:return x

print(func([1,2,3,4,5]))
print(func([1]))

# 方法二:
def func(x):
    return x[:2] if len(x)>2 else  x

print(func([1,2,3,4,5]))
print(func([1]))

# 方法三:
# 推荐
def func(x):
    return x[:2] 
print(func([1,2,3,4,5]))
print(func([1]))
5. 写函数,计算传入字符串中数字,字母,空格以及其他的个数,并返回结果
# 方法一
def func(x):
    dic = {'num':0,'alpha':0,'space':0,'other':0}
    for i in x:
        if i.isalpha():
            dic['alpha'] += 1
        elif i.isalnum():
            dic['num'] +=1
        elif i.isspace():
            dic['space'] +=1
        else:dic['other'] +=1
    return dic


print(func('a3f ,4'))

# 方法二
def func(x):
    zm,sz,kg,qt=0,0,0,0
    for i in x:
        if i.isalpha():
            zm += 1
        elif i.isalnum():
            sz +=1
        elif i.isspace():
            kg +=1
        else:qt +=1
    print('字母:{}\n数字: {}\n空格: {}\n其他: {}'.format(zm,sz,kg,qt))

func('a3f ,4')
6. 写函数,检查用户传入的对象(字符串、列表、元祖)的每一个元素是否含有空内容,并返回结果
# 对于字符来讲,空格就是空内容
# 对于列表和元组 [],()

def func(x):
    if type(x) is str and x:    # 参数是字符串,切字符串不为空,判断是否包含' '空格
        for i in x:
            if i == ' ':
                return True
    elif type(x) is list or type(x) is tuple and x: # 参数是列表或元组,且不为空
        for i in x:
            if not i:     # i == Flase, i 不是一个内容的时候,进入条件
                return True
    elif not x:    # 如果本身为空,则返回 True
        return True

print(func([['','2'],(1,2,''),'']))
7. 写函数,检查传入字典的每一个 value 的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者
dic = {'k1':'v1v1','k2':[11,22,33,44]}  # 字典中的 value 只能是字符串或列表

def func(dic):

    for i,j in dic.items():
        dic[i]=j[:2]

    return  dic

print(func(dic))
8. 写函数,接收两个数字参数,返回比较大的那个数字
def func(x,y):

    return x if x > y else y

print(func(1,2))
9. 写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成整个文件的批量修改操作(进阶)
def func(filename,old,new):
    with open(filename,encoding='utf-8') as f,\
        open('%s.bak'%filename,'w',encoding='utf-8') as f2:
        for line in f:
            if old in line:
                line = line.replace(old,new)
            f2.write(line)

    import os
    os.remove(filename)
    os.rename('%s.bak'%filename,filename)

func('aa','111','222')

三、装饰器

装饰器的 形成过程,最简单的装饰器,有返回值的,有一个参数的,万能参数的

'''
    1. 装饰器的本质:一个闭包函数
    2. 装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展
'''

# 1. 把功能函数的函数名,当做参数传递给装饰器函数,
# 2. 把过程1,的过程,赋值给 功能函数,同名
# 3. 装饰器函数 有一个闭包,功能函数在装饰器函数的子函数内进行操作
# 4. 装饰器函数返回子函数的函数名称(入口)

3.1 初尝装饰器

'''
    装饰器: 版本1
'''
def func1():
    print('in func1')

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

func1 = timmer(func1)
func1()


# 不想修改函数的调用方式,但是还想修改原来的函数前后添加功能
# timmer 就是一个装饰器函数,只是对一个函数,有一些装饰作用

# 1. 把功能函数的函数名,当做参数传递给装饰器函数,
# 2. 把过程1,的过程,赋值给 功能函数,同名
# 3. 装饰器函数 有一个闭包,功能函数在装饰器函数的子函数内进行操作
# 4. 装饰器函数返回子函数的函数名称(入口)
'''
    装饰器:版本2
'''

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

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

func1()

装饰带参数函数的装饰器

'''
    装饰器:版本3
    参数:1个
'''

def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner
@timer     # 别漏了    func1 = timer(func1)
def func1(a):
    print(a)

func1(1)

'''
    装饰器:4
    参数:2个
'''

def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner
@timer
def func1(a,b):
    print('in func1')

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


func1(1,2)
print(func2(1))

返回值

'''
    装饰器:5
    返回值:1
'''
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        ret = func(*args,**kwargs)
        print(time.time() - start)
        return ret
    return inner

def func1(a):
    print('in func2 and get a %s'%(a))
    return 'func2 over'
func1('adfsdf')
print(func1('adfsdf11222222'))

3.1 装饰器的作用

# 不想修改函数的调用方式,但是还想修改原来的函数前后添加功能
# timmer 就是一个装饰器函数,只是对一个函数,有一些装饰作用

3.2 开放封闭原则

开放:对扩展是开放的,
封闭:对修改,是封闭的(开发过了的功能,是不会再修改了,封版)

装饰器完美的遵循了这个开放封闭原则。

1.对扩展是开放的
    为什么要对扩展开放呢?
  我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

2.对修改是封闭的
    为什么要对修改封闭呢?
  就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

3.3 语法糖

import time

def timmer(f):
    def inner():
        start = time.time()
        f()
        end = time.time()
        print(end - start)
    return inner

@timmer       # 语法糖  @装饰器函数名   ;func =timmer(func)
def func():   # 被装饰的函数
    time.sleep(0.01)
    print('大家哈')

func()

3.4 装饰器的固定格式

def wrapper(func):           # 装饰器函数,f: 被装饰的函数
    def inner(*args,**kwargs):
        '''在被装饰函数之前要做的事'''
        ret = func(*args,**kwargs)   # 被装饰的函数
        '''在被装饰函数之后要做的事'''
        return ret
    return inner

@wrapper                 # 语法糖
def func(a,b):          # 被装饰的函数
    pass

3.5 带参数的装饰器

假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?
一个一个的取消掉? 没日没夜忙活3天。。。
过两天你领导想通了,再让你加上。。。

import time
FLAG = False
def timer_out(flage):
    def timer(func):
        def inner(*args,**kwargs):
            if flage:
                start = time.time()
                ret = func(*args,**kwargs)
                print(time.time()-start)
                return ret
            else:
                ret = func(*args, **kwargs)
                return ret
        return inner
    return timer

@timer_out(FLAG)        # timer_out(flage) = timer
def funnc1():
    time.sleep(0.1)
    print('in func2')
@timer_out(FLAG)
def funnc2():
    time.sleep(0.2)
    print('in func2')

funnc1()
funnc2()

3.6 多个装饰器装饰一个函数

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

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

@wrapper1       # 比如登录
@wrapper2       # 比如记录日志
def f():
    print('in f')

f()

'''
    wrapper1,before func
    wrapper2,before func
    in f
    wrapper2,after func
    wrapper1,after func
'''

3.7 获取函数名

from functools import wraps

def wrapper(func):
    @wraps(func)      # 一个完美的装饰器
    def inner(*args):
        print('你最爱吃的水果')
        func(*args)
    return inner

@wrapper
def wahaha(a):
    '''
    这是一个房价通知
    :param a:
    :return:
    '''
    print('%s'%a)

wahaha('苹果')

print(wahaha.__name__,wahaha.__doc__)

练习

# 1. 编写装饰器,为多个函数加上认证功能(用户的账号密码来源与文件),要求登录成功一次,后续的函数都无需再输入用户名和密码

# 2. 编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件

'''
    进阶作业
  # 编写下载网页内容的函数,要求功能是
  # 1. 用户传入一个 url,函数返回下载页面的结果
  # 2. 为题目1编写装饰器,实现缓存网页内容的功能:
    具体:
        实现下载页面存放于文件中,如果文件内有值(文件大小不为0),就有限从文件中读取网页内容
      否则,就去下载
    
'''
1. 编写装饰器,为多个函数加上认证功能(用户的账号密码来源与文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
FLAG = False
# 装饰器函数
def login(func):
    def inner(*args,**kwargs):
        global FLAG
        '''登录程序'''
        if FLAG:
            ret = func(*args,**kwargs)
            return ret
        else:
            if auth():
                ret = func(*args,**kwargs)
                FLAG = True
                return ret
            else:print('登录失败')
    return inner

# 登录函数
def log_in():
    user =  input('用户:')
    pwd =  input('密码:')
    return {'name':user,'password':pwd}

# 认证函数
def auth():
    with open('register','r+',encoding='utf-8') as f:
        return (str(log_in()) == f.read())
@login
def shoplist_add():
    print('add')
@login
def shoplist_del():
    print('del')

shoplist_add()
shoplist_add()
shoplist_add()
2. 编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件
def wrapper(func):
    def inner(*args,**kwargs):
        with open('log','a',encoding='utf-8') as f:
            f.write(func.__name__,'\n')
        ret = func(*args,**kwargs)
        return ret
    return inner


@wrapper
def shoplist_add():
    print('增加一个')

@wrapper
def shoplist_del():
    print('减少一个')

shoplist_add()
shoplist_del()

四、迭代器

可被 for 循环的数据类型

list,dic,str,set,tuple,f = open(),range(),enumerate
'''
    假如我现在有一个列表l=['a','b','c','d','e'],我想取列表中的内容,有几种方式?
    首先,我可以通过索引取值l[0],其次我们是不是还可以用for循环来取值呀?
    你有没有仔细思考过,用索引取值和for循环取值是有着微妙区别的。
    如果用索引取值,你可以取到任意位置的值,前提是你要知道这个值在什么位置。
    如果用for循环来取值,我们把每一个值都取到,不需要关心每一个值的位置,因为只能顺序的取值,并不能跳过任何一个直接去取其他位置的值。
    但你有没有想过,我们为什么可以使用for循环来取值?
    for循环内部是怎么工作的呢?
'''

4.1 查看一个数据类型所支持的所有方法

l = [1,2,3]
print(dir(l))

# ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

4.2 迭代器协议和可迭代协议

# 迭代器协议:内部还有__next__和__iter__方法的就是迭代器
# 可迭代协议:只要含有__iter__方法的都是可迭代的
print('__iter__' in dir(str))

迭代器一定可迭代,可迭代的通过调用 iter()方法就能得到一个迭代器

4.3 拆解 for 循环

# for 循环是怎么一回事呢?

l = [1,2,3]
for i in l:
    pass

# iterator = l.__iter__()
# iterator.__next__()
# 报错的时候就停止了

for 循环的本质就是迭代器
只有是可迭代对象的时候,才能用 for
当我们遇到一个新的变量,不确定能不能 for 循环的时候,就判断它是否可迭代

4.4 迭代器的优点

'''
    # 迭代器的特点:
  # 很方便使用,且只能取所有的数据取一次
  # 节省内存空间
'''


'''
    # 1. 从容器类型中一个一个的取值,会把所有的值都取到
  # 2. 可以节省内存空间   range(),文件句柄
  #    迭代器并不会在内存中再占用一大块内存,而是随着循环,每次生成一个,每次 next 给一个
'''

print(range(10000))   # 可迭代对象, 并不直接生成数据 
print(list(range(10000)))

五、生成器函数 yield

生成器的本质就是迭代器

'''
    # 生成器的表现形式
  # 1. 生成器函数
  # 2. 生成器表达式
'''

'''
    # 生成器函数
  ## 含有 yield 关键字的函数就是生成器函数
  
  # 特点
  # 1. 调用函数的之后函数不执行,返回一个生成器
  # 2. 每次调用 next 方法的时候会取到一个值
  # 3. 直到取完最后一个,在执行 next 会报错
  
'''

生成器取值

'''
    # 生成器函数进阶:01
    # 可以打印789,但是会报错
'''

# def generator():
#     print(123)
#     yield 1
#     print(456)
#     yield 2
#     print(789)
#
# g = generator()
# ret = g.__next__()
# print(ret)
# ret = g.__next__()
# print(ret)
# ret = g.__next__()
# print(ret)

5.1 简单的生成器

# 只要含有 yield 关键字的函数都是生成器函数
# yield 只能写在函数里面,不能和 return 共用
# yield 不会结束函数
def generator():
    print(1)
    yield 'a'

# 生成器函数执行之后,会得到一个生成器作为返回值
ret = generator()

# 生成器是一个可迭代对象,= 迭代器
# ret.__next__()
# ret.__iter__()


print(ret)
print(ret.__next__())

 yield 和 return 的区别

def generator():  
    print(1)
    yield 'a'
    print(2)
    yield 'b'

a = generator()    # 生成器 = 生成器函数
# print(generator())
print(a.__next__())   # 1,a
print(a.__next__())   # 2,b

5.2 生成器的特点

# 制造两百万个娃哈哈

def wahaha():
    for i in range(2000000):
        yield '娃哈哈%s'%i

g = wahaha()

count = 0   # 调用指定次数
for i in g:
    count +=1
    print(i)
    if count > 50 :break

# 尝试继续取下一个
print('\n能否继续取?%s \n'%g.__next__())

for i in g:
    count +=1
    if count > 100: break
    print(i)

5.3 生成器实例

def tail(filename):
    f = open(filename,encoding='utf-8')

    while True:
        line = f.readline()
        if line.strip():
            yield line.strip() # 去掉回车


# 监听,过滤效果
g = tail('file')
for i in g:
    if 'python' in i:
        print(i)
        
# 2
# 写生成器实现:有一个文件,从文件里分段读取内容
# readline
# read(10)

# 在读出来的内容前面加一个'***',再返回给调用者

def generator():
    for i in range(2000000):
        yield '娃哈哈{}'.format(i)

g = generator()  # 调用生成器函数得到一个生成器
g.__next__()  # 每一次执行 g.__next__就是从生成器中取值,预示着生成器函数中的代码继续执行

num = 0
for i in g:
    num += 1
    if num > 50:
        break
    print(i)

5.4 从生成器中取值的几个方法

# 从生成器中取值的几个方法
    # next
    # for
    # 数据类型的强制转换:占内存
    # send

def generator():
    for i in range(20):
        yield '娃哈哈{}'.format(i)

g = generator()  # 调用生成器函数得到一个生成器

print(list(g))   # 可以打印,就说明已经存在了内存当中

5.5 生成器进阶 send

__send__方法

'''
    # 生成器函数进阶:01 
    # next 和 send 效果一样,在获取下一个值
    # send 在获取下一个值的时候,给上一个值的位置传递一个数据
    
    # 注意事项:
    # 1. 第一次用是生成器的时候,使用 next 获取下一个值
    # 2. 最后一个 yield 不能接受外部的值,已经结束了,值没地传
    # 3. 一定要朝最后一个位置传值,可以 yield 空
'''

## 01:取值

# def generator():
#     print(123)
#     yield 1
#     print(456)
#     yield 2
#
# g = generator()
# ret = g.__next__()     # 取第一个值,必须用 next ,
# print(ret)
# ret = g.send(None)
# print(ret)


## 02:传值

def generator():
    print(123)
    content = yield 1
    print('==========',content)
    print(456)
    yield 2

g = generator()
ret = g.__next__()
print(ret)
ret = g.send('hello')   
print(ret)

## 03:最后一个 yield,传值

def generator():
    print(123)
    content = yield 1
    print('==========',content)
    print(456)
    arg = yield 2
    print('最后一个',arg)
    yield

g = generator()
ret = g.__next__()
print(ret)
ret = g.send('hello')
print(ret)
ret = g.send('last')
print(ret)

练习

'''
    # 练习:获取移动平均值
    # 数据总和 / 数据次数  返回就可以了
'''

def average():
    sum =0
    cout =0
    avg = 0
    while True:
        num = yield avg
        sum += num
        cout +=1
        avg = sum/cout

avg_g=average()
avg_g.__next__()
avg1 = avg_g.send(10)
avg1 = avg_g.send(10)
avg1 = avg_g.send(88)

print(avg1)

5.6 预激携程的装饰器

# 2:装饰器

def init(func): # 激活生成器的装饰器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)
        g.__next__()    # 装饰器的作用,就是执行以下过程,后边就不走了
        return g
    return inner

@init
def average():
    sum,cout,avg=0,0,0

    while True:
        num = yield avg
        sum += num
        cout +=1
        avg = sum/cout

avg_g=average()
avg1 = avg_g.send(10)
avg1 = avg_g.send(10)
avg1 = avg_g.send(88)

print(avg1)

5.7 yield from

'''
    # yield from
'''

# def generator():
#     a = 'abcde'
#     b = '12345'
#     for i in a:
#         yield a
#     for i in b:
#         yield b

def generator():
    a = 'abcde'
    b = '12345'
    # yield a
    yield from a
    yield from b
    # yield b
g = generator()
for i in g:
    print(i)

5.8 列表推导式和生成器表达式

猜你喜欢

转载自www.cnblogs.com/liudianer/p/10417556.html