author: UpDown published: True created: 2022年5月10日15点20分 tags: Done version: Outer

参考:https://blog.updown.world/articles/python/pythonbook2/01-%E4%B8%89%E5%99%A8%E4%B8%80%E9%97%AD/index.html 参考:https://updownxh.notion.site/python-5afba0d07da74027a6b07cfb108050c1

迭代器

迭代

我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代

可迭代对象

只要是可以通过for…in…的形式进行遍历的,那么这个数据类型就是可以迭代的
下面的是可以迭代的数据类型

  • 列表
  • 元组
  • 字典
  • 字符串

不可以迭代的数据类型

  • 数字类型(包括整型,浮点,布尔)

    判断是否可迭代

    ```python from collections.abc import Iterable

print(isinstance([], Iterable)) # True

print(isinstance({}, Iterable)) # True

print(isinstance(‘abc’, Iterable)) # True

print(isinstance(100, Iterable)) # False

  1. <a name="k0gwm"></a>
  2. #### 可迭代对象的本质
  3. 可迭代对象的本质就是可以向我们提供一个这样的中间“人”,即迭代器帮助我们对其进行迭代遍历使用。<br />list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。
  4. <a name="R3Gpt"></a>
  5. #### 获取可迭代对象的迭代器
  6. ```python
  7. from collections.abc import Iterator
  8. nums = [11, 22, 33, 44]
  9. print(type(nums))
  10. # 获取列表的迭代器
  11. nums_iter = iter(nums)
  12. # 查看类型
  13. print(type(nums_iter))
  14. # 判断是不是迭代器
  15. print("nums", isinstance(nums, Iterator))
  16. print("nums_iter", isinstance(nums_iter, Iterator))
  17. """
  18. <class 'list'>
  19. <class 'list_iterator'>
  20. nums False
  21. nums_iter True
  22. """

获取迭代器的数据

nums = [11, 22, 33, 44]
# 获取列表的迭代器
nums_iter = iter(nums)

num1 = next(nums_iter)
print(num1)

num2 = next(nums_iter)
print(num2)

num3 = next(nums_iter)
print(num3)

num4 = next(nums_iter)
print(num4)
"""
11
22
33
44
"""

StopIteration异常

当调用的.next()多于迭代个数时,报错告知迭代结束的标志

iter

iter()方法必须是对”可迭代“对象 才能 提取到 ”迭代器“对象,但是怎样保证自定义的对象是”可迭代“对象呢?

  • 只要在类中,定义iter方法,那么这个类创建出来的对象一定是可迭代对象

一个类,只要有iter方法,那么这个类创建出来的对象就是可以迭代对象,那为什么依然报错呢?

  • 其实,当我们调用iter()函数提取一个可迭代对象的 迭代器时,实际上会自动调用这个对象的iter方法,并且这个方法返回迭代器,我们并没有返回迭代器。

    next

    在使用next()函数的时候,调用的就是迭代器对象的next方法
    python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现iter方法,而iter方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的iter方法返回自身即可。

一个实现了iter方法和next方法的对象,就是迭代器

class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        myiterator = MyIterator(self)
        return myiterator


class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self, mylist):
        self.mylist = mylist
        # current用来记录当前访问到的位置
        self.current = 0

    def __next__(self):
        # 判断是否全部迭代完毕
        if self.current < len(self.mylist.items):
            # 每次获取一个值然后返回
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self):
        return self


if __name__ == '__main__':
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    for num in mylist:
        print(num)

'''
1
2
3
4
5
'''

for…in…循环的本质

  1. 先通过iter()函数获取可迭代对象Iterable的迭代器
  2. 对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item
  3. 当遇到StopIteration异常后循环结束

    小总结

  • 凡是可作用于for循环的对象都是Iterable 类型;
  • 凡是可作用于 next() 函数的对象都是Iterator 类型
  • 集合数据类型如list 、dict、str等是 Iterable但不是Iterator,不过可以通过 iter()函数获得一个 Iterator对象
  • for循环迭代结束不会报StopIteration异常因为内部处理过了,但next迭代完再执行的话会报StopIteration异常

    isinstance() 与 type() 区别:

    https://www.runoob.com/python/python-func-isinstance.html

  • type() 不会认为子类是一种父类类型,不考虑继承关系。

  • isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。

class A:
    pass

class B(A):
    pass

isinstance(A(), A)    # returns True
type(A()) == A        # returns True
isinstance(B(), A)    # returns True
type(B()) == A        # returns False

生成器

https://blog.updown.world/articles/python/pythonbook2/01-%E4%B8%89%E5%99%A8%E4%B8%80%E9%97%AD/02-%E7%94%9F%E6%88%90%E5%99%A8.html

在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器是一类特殊的迭代器

  • 使用了yield关键字的函数不再是函数,而是生成器。
  • 使用next调用生成器,执行顺序:
    • 如果是第一次执行,则从def代码块的开始部分执行,直到遇到yield为止,并且把yield关键字后的数值返回,当做next()的返回值, yield在这里有了return返回值的作用。
    • 如果不是第一次执行,则从上一次暂停的位置执行(即从上一次yield关键字的下一个语句开始执行),直到遇到下一次yield为止,,并且把yield关键字后的数值返回,当做next()的返回值

      创建生成器(列表推导式变化)

      ```python l = [ x*2 for x in range(5)]

print(l) #[0, 2, 4, 6, 8]

g = ( x*2 for x in range(5))

print(g) # at 0x7f626c132db0>

创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。
```python
from collections.abc import Iterable, Iterator

g = (x * 2 for x in range(5))

# 检测是否可以迭代
print(isinstance(g, Iterable))
# 是否是迭代器
print(isinstance(g, Iterator))

for x in g:
    print(x)

"""
True
True
0
2
4
6
8
"""

创建生成器(yield)

只要在def中有yield关键字的 就称为 生成器,代码中的gen()得到的是一个生成器,可以用next()来获取生成器的数据,生成器是可以迭代的

def gen():
    i = 0
    while i < 5:
        yield i
        i += 1

# 此时获取的是一个生成器 不是在执行一个函数
g = gen()
print(g) # <generator object gen at 0x000001AF5E151F48>

print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

获取return的值
使用用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,需要使用next()调用,并且必须捕获StopIteration错误,返回值包含在StopIteration的value中:

def nmb():
    print(1)
    yield 2
    return 'sbi'


try:
    nmb1 = nmb()
    next(nmb1)
    next(nmb1)
except StopIteration as e:
    print(f"返回值:{e.value}")

send

我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。
send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。

def gen():
    i = 0

    while i < 5:
        print(2)
        temp = yield i

        print(temp)

f = gen()

print(next(f))
print('--------------------')
print(next(f))
print('--------------------')
print(f.send('haha'))
print('--------------------')
"""
2
0
--------------------
None
2
0
--------------------
haha
2
0
--------------------
"""

特点

生成器的特点:

  1. 生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
  2. 存储的是生成数据的方式(即算法),而不是存储生成的数据,因此节约内存

    装饰器

    ```python def check(func): def inner():

     # 验证1
     # 验证2
     # 验证3
     func()
    

    return inner

@check def f1(): print(‘f1’)


1. @check等价于 check(f1)
1. 即将check的返回值再重新赋值给 f1, 也就是 新f1 = inner
<a name="DBwEV"></a>
#### 小总结
普通的闭包和把闭包作为装饰器使用有什么区别呢?

- 普通闭包:内部函数将使用的外部变量当做数据来用
- 将闭包当做装饰器:内部函数将使用的外部变量当做可调用的对象(例如函数)来调用
<a name="peGod"></a>
#### 有参数装饰器
```python
from time import ctime, sleep

def timefun(func):
    def wrapped_func(a, b):
        print("%s called at %s" % (func.__name__, ctime()))
        print(a, b)
        func(a, b)
    return wrapped_func

@timefun
def foo(a, b):
    print(a+b)

foo(3,5)
sleep(2)
foo(2,4)

image.png

对带有不定长参数的函数进行装饰

from time import ctime, sleep

def timefun(func):
    def wrapped_func(*args, **kwargs):
        print("%s called at %s"%(func.__name__, ctime()))
        func(*args, **kwargs)
    return wrapped_func

@timefun
def foo(a, b, c):
    print(a+b+c)

foo(3,5,7)

image.png

return

  • 一般情况下为了让装饰器更通用,可以有return,即使这个被装饰的函数默认没有返回值也不会有问题,因为此时相当于return None

image.png

多层嵌套装饰器

image.png

装饰器带参数

外层参数,中层函数

  • 三个函数嵌套 ```python def out_def1(pre): def out_def(func):

      def in_def(a,b):
          print('haha')
          print(pre)
          func(a,b)
    
      return in_def
    

    return out_def

@out_def1(‘nihao’) def bei(a,b): print(a,b)

bei(1,2)

<a name="lgdlo"></a>
#### 类装饰器
> [https://blog.updown.world/articles/python/pythonbook2/01-%E4%B8%89%E5%99%A8%E4%B8%80%E9%97%AD/04-%E8%A3%85%E9%A5%B0%E5%99%A8.html](https://blog.updown.world/articles/python/pythonbook2/01-%E4%B8%89%E5%99%A8%E4%B8%80%E9%97%AD/04-%E8%A3%85%E9%A5%B0%E5%99%A8.html)

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的。
```python
class Test(object):
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s"%func.__name__)
        self.__func = func
    def __call__(self):
        print("---装饰器中的功能---")
        self.__func()
#说明:
#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
#   并且会把test这个函数名当做参数传递到__init__方法中
#   即在__init__方法中的属性__func指向了test指向的函数
#
#2. test指向了用Test创建出来的实例对象
#
#3. 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法
#
#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
#   所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
@Test
def hi():
    print("----test---")


hi()

闭包

  • 在一个函数里,嵌套定义了另外一个函数,里面的函数使用的外部函数中的变量,最后把里面的函数作为外部函数的返回值返回,这样就形成一个闭包 ```python

    定义一个函数

    def test(a): def test_in(b):
      print("in test_in 函数, b is %d" % b)
      return a+b
    

    其实这里返回的就是闭包的结果

    return test_in

给test函数赋值,这个20就是给参数a

ret = test(20)

注意这里的100其实给参数b

print(ret(100))

注 意这里的200其实给参数b

print(ret(200)) “”” in test_in 函数, b is 100 120

in test_in 函数, b is 200 220 “””

由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
<a name="dfKDB"></a>
#### nonlocal
nonlocal可以访问外部函数里的非全局变量<br />global则可以修改全局变量
```python
def foo():
    m = 0

    def foo1():
        nonlocal m
        m = 1
        print(m)

    print(m)
    foo1()
    print(m)


foo()
"""
0
1
1
"""
m = 6
def foo():
    m = 0

    def foo1():
        def haha():
            nonlocal m
            m = 1
            print(m)
            # return m

        # return haha()
        haha()
    foo1()
    print('----')
    return m


print(foo())
print(m)
"""
1
----
1
6
"""