本节主要内容:
1. 函数名的使用及第一类对象
2. 闭包
3. 迭代器
一. 函数名的运用.
函数名是一个变量,但它是一个特殊的变量,与括号配合可以指向函数的变量.
1. 函数名的内存地址(直接打印函数名,不带括号即可)
def func(): # 这里定义一个函数"func" print('哈哈') print(func) # 这里调用的"func"后边没有带() # 所以得到的结果是这个函数在内存里的地址: # >>> <function func at 0x002EA618>
2. 函数名可以赋值给其他变量
def func(): print('哈哈') print(func) # <function func at 0x002FA618> a = func # 把函数地址当成一个变量赋值给另一个变量a a() # a 加上括号,就相当于 func 加上括号 # 所以结果是: # >>> <function func at 0x002FA618> # >>> 哈哈
3.函数名可以当作容器类的元素(可以被容器装下)
def func1(): print('哈哈',end=',') def func2(): print('呵呵',end=',') def func3(): print('呼呼',end=',') def func4(): print('吼吼',end=',') lst = [func1, func2, func3, func4] for i in lst: i() # 结果>>> 哈哈,呵呵,呼呼,吼吼,
4. 函数名可以当作函数的参数
def func(): print('吃了么') def func2(fn): print('我是func2') fn() # 执行传递过来的fn,这里的fn()等于func() print('我是func2') func2(func) # 把函数名func当作参数传递给func2的参数fn. # 结果>>> 我是func2 # 结果>>> 吃了么 # 结果>>> 我是func2
5. 函数名可以作为函数的返回值
def func_1(): print('这里是函数1') def func_2(): print('这里是函数2') print('这里是函数1') return func_2 fn = func_1() # 执行函数1, 函数1返回的是一个函数名"func_2",这时fn指向的就是上面函数2 的内存地址 print(fn) fn() # 执行上面返回的函数 # 结果>>> 这里是函数1 # 结果>>> 这里是函数1 # 结果>>> <function func_1.<locals>.func_2 at 0x013773D8> # 结果>>> 这里是函数2
二. 闭包
闭包就是内层函数,对外层函数(非全局)的变量的引用,叫做闭包.
def func1(): name = 'alex' def func2(): print(name) func2() func1() # 结果>>> alex
我们可以使用" __closure__ " 来监测函数是否是闭包. 使用函数名.__closure__ 返回cell就是闭包,返回None就不是闭包.
def func1(): name = 'alex' def func2(): print(name) func2() print(func2.__closure__) func1() # 结果>>> alex # 结果>>> (<cell at 0x03801E50: str object at 0x01C073C0>,)
问题,如何在函数外边调用内部函数呢?
def outer(): name = 'alex' def inner(): print(name) return inner fn = outer() # 访问外部函数,获取到内部函数的函数地址 fn() # 访问内部函数
那如果多层嵌套呢?很简单,只需要一层一层的往外层返回就行了
def func1(): def func2(): def func3(): print('哈哈') return func3 return func2 print(func1()) # func2 print(func1()()) # func3 print(func1()()()) # 等于func(),打印'哈哈',返回None # 结果>>> <function func1.<locals>.func2 at 0x02B77420> # 结果>>> <function func1.<locals>.func2.<locals>.func3 at 0x02B772B8> # 结果>>>哈哈 # 结果>>>None
由它我们可以引出闭包的好处. 由于我们在外界可以访问内部函数. 那这个时候内部函数访问的时间和时机就不一定了, 因为在外部, 我可以选择在任意的时间去访问内部函数. 这个时候. 想一想. 我们之前说过, 如果一个函数执行完毕. 则这个函数中的变量以及局部命名空间中的内容都将会被销毁. 在闭包中. 如果变量量被销毁了. 那内部函数将不能正常执行. 所以. python规定. 如果你在内部函数中访问了了外层函数中的变量. 那么这个变量将不会消亡.将会常驻在内存中. 也就是说. 使用闭包, 可以保证外层函数中的变量在内存中常驻. 这样做有什么好处呢? 非常大的好处. 我们来看一个关于爬虫的代码:
from urllib.request import urlopen def but(): content = urlopen("http://www.xiaohua100.cn/index.html").read() def get_content(): return content return get_content fn = but() # 这个时候就开始加载校花100的内容,返回的是函数名'get_content' # 后面需要用到这里面的内容就不需要在执行非常耗时的网络连接操作了 content = fn() # 调用内部函数get_content(),获取内容 print(content) content2 = fn() # 重新获取内容,从内存直接拿 print(content2)
三. 迭代器
我们之前一直在用可迭代对象进行迭代操作,那么到底什么是可迭代对象?首先我吗先回顾下目前我们所熟悉的可迭代对象有哪些:
str, list, tuple, dict, set ,那为什么我们可以称他们为可迭代对象呢?因为他们都遵循了可迭代协议,什么是可迭代协议,首先我们先看一段代码:
# 对的: s = 'abc' for c in s: print(c) # 错的: for i in 123: print(i) # 报错>>> TypeError: 'int' object is not iterable
注意看报错信息中有这样⼀句话. 'int' object is not iterable . 翻译过来就是整数类型对象是不可迭代的. iterable表⽰可迭代的. 表示可迭代协议. 那么如何进⾏验证你的数据类型是否符合可迭代协议. 我们可以通过dir函数来查看类中定义好的所有⽅法.
s = '哈哈哈' print(dir(s)) #可以打印对象中的方法和函数 print(dir(str)) #也可以打印类中声明的方法和函数
在打印结果中,寻找" __iter__ ",如果能找到,那么这个类的对象就是一个可迭代对象.
我们发现,这几个可以进行for循环的东西都有" __iter__ "函数,包括"range"也有.
这是查看一个对象是夫是可迭代对象的第一种方发,我们还可以通过"isinstence()"函数来查看一个对象是什么类型的.
l = [1,2,3] l_iter = l.__iter__() from collections import Iterable from collections import Iterator print(isinstance(l,Iterable)) # True print(isinstance(l,Iterator)) # False print(isinstance(l_iter,Iterable)) # True print(isinstance(l_iter,Iterator)) # True
综上. 我们可以确定. 如果对象中有__iter__函数. 那么我们认为这个对象遵守了可迭代协议.就可以获取到相应的迭代器. 这⾥的__iter__是帮助我们获取到对象的迭代器. 我们使⽤迭代器中的__next__()来获取到⼀个迭代器中的元素. 那么我们之前讲的for的⼯作原理到底是什么? 继续看代码:
s = "我爱北京天安⻔" c = s.__iter__() # 获取迭代器 print(c.__next__()) # 使用迭代器进行迭代,获取一个元素 我 print(c.__next__()) # 爱 print(c.__next__()) # 北 print(c.__next__()) # 京 print(c.__next__()) # 天 print(c.__next__()) # 安 print(c.__next__()) # ⻔ print(c.__next__()) # StopIteration
for循环的机制:
使用while循环+迭代器来模拟for循环(必须要掌握)
lst = [1,2,3] lst_iter = lst.__iter__() while True: try: i = lst_iter.__next__() print(i) except StopIteration: break
总结:
1. Iterable: 可迭代对象.内部包含" __iter__() "函数
2. Iterator: 迭代器. 内部包含 " __iter__() "同时包含 " __next__() "
3. 迭代器的特点:
3.1 节省内存
3.2 惰性机制
3.3 不能反复,只能向下执行,不能向上执行
4. 我们可以把要迭代的内容当成⼦弹. 然后呢. 获取到迭代器__iter__(), 就把⼦弹都装在弹夹中. 然后发射就是__next__()把每⼀个⼦弹(元素)打出来. 也就是说, for循环的时候. ⼀开始的时候是__iter__()来获取迭代器. 后⾯每次获取元素都是通过__next__()来完成的. 当程序遇到StopIteration将结束循环.