迭代器與for循環的本質


1. 函數名的運用

​ 函數名的定義和變量的定義幾乎一致,在變量的角度,函數名其實就是一個變量,具有變量的功能:可以賦值;但是作為函數名他也有特殊的功能就是加上()就會執行對應的函數,所以我們可以把函數名當做一個特殊的變量,那么接下來,我們就來研究一下這個特殊的變量。

1.1 函數的內存地址

def func():        
    print("呵呵")
print(func)結果: <function func at 0x1101e4ea0>

​ 通過上面代碼可以我們知道,函數名指向的是這個函數的內存地址,其實深一步理解可得知,與其說函數名()可以執行這個函數,不如說是函數的內存地址()才是執行這個函數的關鍵,就好比:

a = 1
b = 2
c = a + b
print(c)  # 3

a + b 並不是變量的相加,而是 兩個變量指向的int對象的相加。

1.2 函數名可以賦值給其他變量

如果你理解了第一條,那么第二條就更容易的理解了:

def func():        
    print("呵呵")    
print(func) 
a = func  # 把函數當成一個變量賦值給另一個變量
a() # 函數調用 func()

通過變量的賦值,變量a,和變量func都指向的這個函數的內存地址,那么a() 當然可以執行這個函數了。

1.3 函數名可以當做容器類的元素

其實這個也不難理解,函數名就是一個變量,我的變量是可以當做容器類類型的元素的:

[復制代碼](javascript:void(0)😉

a = 1
b = 'alex'
c = '武sir'
l1 = [a, b, c]
for i in l1:
    print(i)
'''
# 結果:
1
alex
武sir
'''

[復制代碼](javascript:void(0)😉

那么函數名也是可以的:

img

[復制代碼](javascript:void(0)😉

def func1():
    print("in func1: 嘻嘻")
def func2():
    print("in func2: 哈哈")
def func3():
    print("in func3: 咯咯")
def func4():
    print("in func4: 吱吱")
lst = [func1, func2, func3, func4]
for i in lst:
    i()

[復制代碼](javascript:void(0)😉

1.4 函數名可以當做函數的參數

變量可以做的,函數名都可以做到。

img

[復制代碼](javascript:void(0)😉

def func1():
    print('in func1')

def func2(f):
    print('in func2')
    f()

func2(func1)

[復制代碼](javascript:void(0)😉

1.5 函數名可以作為函數的返回值

img

[復制代碼](javascript:void(0)😉

def func1():
    print('in func1')

def func2(f):
    print('in func2')
    return f

ret = func2(func1)
ret()  # ret, f, func1 都是指向的func1這個函數的內存地址

[復制代碼](javascript:void(0)😉

小結:函數名是一個特殊的變量,他除了具有變量的功能,還有最主要一個特點就是加上() 就執行,其實他還有一個學名叫第一類對象

2 Python新特性:f-strings格式化輸出

​ f-strings 是python3.6開始加入標准庫的格式化輸出新的寫法,這個格式化輸出比之前的%s 或者 format 效率高並且更加簡化,非常的好用,相信我,你們學完這個之后,以后再用格式化輸出這就是你們唯一的選擇。

2.1 簡單舉例

​ 他的結構就是F(f)+ str的形式,在字符串中想替換的位置用{}展位,與format類似,但是用在字符串后面寫入替換的內容,而他可以直接識別。碉堡了。

img

[復制代碼](javascript:void(0)😉

name = 'meet'
age = 18
sex = '男'
msg = F'姓名:{name},性別:{age},年齡:{sex}'  # 大寫字母也可以
msg = f'姓名:{name},性別:{age},年齡:{sex}'  
print(msg)
'''
輸出結果:
姓名:meet,性別:18,年齡:男
'''

[復制代碼](javascript:void(0)😉

2.2 任意表達式

他可以加任意的表達式,非常方便:

img

[復制代碼](javascript:void(0)😉

print(f'{3*21}')  # 63

name = 'barry'
print(f"全部大寫:{name.upper()}")  # 全部大寫:BARRY

# 字典也可以
teacher = {'name': 'meet', 'age': 18}
msg = f"The teacher is {teacher['name']}, aged {teacher['age']}"
print(msg)  # The comedian is meet, aged 18

# 列表也行
l1 = ['meet', 18]
msg = f'姓名:{l1[0]},年齡:{l1[1]}.'
print(msg)  # 姓名:meet,年齡:18.

[復制代碼](javascript:void(0)😉

2.3 也可以插入表達式

可以用函數完成相應的功能,然后將返回值返回到字符串相應的位置

img

def sum_a_b(a,b):
    return a + b
a = 1
b = 2
print('求和的結果為' + f'{sum_a_b(a,b)}')

2.4 多行f

img

[復制代碼](javascript:void(0)😉

name = 'barry'
age = 18
ajd = 'handsome'

# speaker = f'''Hi {name}.
# You are {age} years old.
# You are a {ajd} guy!'''

speaker = f'Hi {name}.'\
          f'You are {age} years old.'\
          f'You are a {ajd} guy!'
print(speaker)

[復制代碼](javascript:void(0)😉

2.5 其他細節

這里有一些注意的細節,

img

[復制代碼](javascript:void(0)😉

print(f"{{73}}")  # {73}
print(f"{{{73}}}")  # {73}
print(f"{{{{73}}}}")  # {{73}}
m = 21
# ! , : { } ;這些標點不能出現在{} 這里面。
# print(f'{;12}')  # 報錯
# 所以使用lambda 表達式會出現一些問題。
# 解決方式:可將lambda嵌套在圓括號里面解決此問題。
x = 5
print(f'{(lambda x: x*2) (x)}')  # 10

[復制代碼](javascript:void(0)😉

總結:f-string的格式化輸出更加簡潔,方便,易讀。而且他的處理速度對之前的%s 或者format 有了較高的提升,所以以后盡量使用此種格式化輸出。

3. 迭代器

3.1 可迭代對象

  1) 可迭代對象定義

​ 對於迭代器來說,我們更熟悉的應該是可迭代對象,之前無論是源碼還是講課中或多或少我們提到過可迭代對象這個詞。之前為了便於大家理解可迭代對象,可能解釋的不是很正確,所以今天我們正式的聊一聊什么是可迭代對象。從字面意思來說,我們先對其進行拆解:什么是對象?Python中一切皆對象,之前我們講過的一個變量,一個列表,一個字符串,文件句柄,函數名等等都可稱作一個對象,其實一個對象就是一個實例,就是一個實實在在的東西。那么什么叫迭代?其實我們在日常生活中經常遇到迭代這個詞兒,更新迭代等等,迭代就是一個重復的過程,但是不能是單純的重復(如果只是單純的重復那么他與循環沒有什么區別)每次重復都是基於上一次的結果而來。比如你爹生你,你生你爹,哦不對,你生你兒子,你兒子生你孫子等等,每一代都是不一樣的;還有你使用過得app,微信,抖音等,隔一段時間就會基於上一次做一些更新,那么這就是迭代。可迭代對象從字面意思來說就是一個可以重復取值的實實在在的東西。

​ 那么剛才我們是從字面意思分析的什么是可迭代對象,到目前為止我們接觸到的可迭代對象有哪些呢?

​ str list tuple dic set range 文件句柄等,那么int,bool這些為什么不能稱為可迭代對象呢?雖然在字面意思這些看着不符合,但是我們要有一定的判斷標准或者規則去判斷該對象是不是可迭代對象。

在python中,但凡內部含有__iter__方法的對象,都是可迭代對象

  2) 查看對象內部方法

​ 該對象內部含有什么方法除了看源碼還有什么其他的解決方式么?當然有了, 可以通過dir() 去判斷一個對象具有什么方法

s1 = 'alex'
print(dir(s1))

dir()會返回一個列表,這個列表中含有該對象的以字符串的形式所有方法名。這樣我們就可以判斷python中的一個對象是不是可迭代對象了:

s1 = 'alex'
i = 100
print('__iter__' in dir(i))  # False
print('__iter__' in dir(s1))  # True

  3)小結

​ 從字面意思來說:可迭代對象就是一個可以重復取值的實實在在的東西。

​ 從專業角度來說:但凡內部含有__iter__方法的對象,都是可迭代對象。

​ 可迭代對象可以通過判斷該對象是否有’iter’方法來判斷。

​ 可迭代對象的優點:

​ 可以直觀的查看里面的數據。

​ 可迭代對象的缺點:

​ \1. 占用內存。

​ \2. 可迭代對象不能迭代取值(除去索引,key以外)。

​ 那么這個缺點有人就提出質疑了,即使拋去索引,key以外,這些我可以通過for循環進行取值呀!對,他們都可以通過for循環進行取值,其實for循環在底層做了一個小小的轉化,就是先將可迭代對象轉化成迭代器,然后在進行取值的。那么接下來,我們就看看迭代器是個什么鬼。

3.2 迭代器

  1) 迭代器的定義

​ 從字面意思來說迭代器,是一個可以迭代取值的工具,器:在這里當做工具比較合適。

​ 從專業角度來說:迭代器是這樣的對象:實現了無參數的__next__方法,返回序列中的下一個元素,如果沒有元素了,那么拋出StopIteration異常.python中的迭代器還實現了__iter__方法,因此迭代器也可以迭代。 出自《流暢的python》

​ 那么對於上面的解釋有一些超前,和難以理解,不用過於糾結,我們簡單來說:在python中,內部含有'Iter'方法並且含有'next'方法的對象就是迭代器。

  2) 如何判斷該對象是否是迭代器

​ ok,那么我們有了這個定義,我們就可以判斷一些對象是不是迭代器或者可迭代對象了了,請判斷這些對象:str list tuple dict set range 文件句柄 哪個是迭代器,哪個是可迭代對象:

img

[復制代碼](javascript:void(0)😉

o1 = 'alex'
o2 = [1, 2, 3]
o3 = (1, 2, 3)
o4 = {'name': '太白','age': 18}
o5 = {1, 2, 3}
f = open('file',encoding='utf-8', mode='w')
print('__iter__' in dir(o1))  # True
print('__iter__' in dir(o2))  # True
print('__iter__' in dir(o3))  # True
print('__iter__' in dir(o4))  # True
print('__iter__' in dir(o5))  # True
print('__iter__' in dir(f))  # True
# hsagn
print('__next__' in dir(o1))  # False
print('__next__' in dir(o2))  # False
print('__next__' in dir(o3))  # False
print('__next__' in dir(o4))  # False
print('__next__' in dir(o5))  # False
print('__next__' in dir(f))  # True
f.close()

[復制代碼](javascript:void(0)😉

通過以上代碼可以驗證,之前我們學過的這些對象,只有文件句柄是迭代器,剩下的那些數據類型都是可迭代對象。

  3) 可迭代對象如何轉化成迭代器:

img

l1 = [1, 2, 3, 4, 5, 6]
obj = l1.__iter__() 
# 或者 iter(l1)print(obj) 
# <list_iterator object at 0x000002057FE1A3C8>

  4) 迭代器取值:

​ 可迭代對象是不可以一直迭代取值的(除去用索引,切片以及Key),但是轉化成迭代器就可以了,迭代器是利用__next__()進行取值:

img

[復制代碼](javascript:void(0)😉

l1 = [1, 2, 3,]
obj = l1.__iter__()  # 或者 iter(l1)
# print(obj)  # <list_iterator object at 0x000002057FE1A3C8>
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()  # StopIteration
print(ret)
# 迭代器利用next取值:一個next取對應的一個值,如果迭代器里面的值取完了,還要next,
# 那么就報StopIteration的錯誤。

[復制代碼](javascript:void(0)😉

  5) while模擬for的內部循環機制:

​ 剛才我們提到了,for循環的循環對象一定要是可迭代對象,但是這不意味着可迭代對象就可以取值,因為for循環的內部機制是:將可迭代對象轉換成迭代器,然后利用next進行取值,最后利用異常處理處理StopIteration拋出的異常。

img

[復制代碼](javascript:void(0)😉

l1 = [1, 2, 3, 4, 5, 6]
# 1 將可迭代對象轉化成迭代器
obj = iter(l1)
# 2,利用while循環,next進行取值
while 1:
    # 3,利用異常處理終止循環
    try:
        print(next(obj))
    except StopIteration:
        break

[復制代碼](javascript:void(0)😉

  6)小結:

​ 從字面意思來說:迭代器就是可以迭代取值的工具。

​ 從專業角度來說:在python中,內部含有'Iter'方法並且含有'next'方法的對象就是迭代器。

​ 迭代器的優點:

​ 節省內存。
​ 迭代器在內存中相當於只占一個數據的空間:因為每次取值都上一條數據會在內存釋放,加載當前的此條數據。

​ 惰性機制。
​ next一次,取一個值,絕不過多取值。​

​ 有一個迭代器模式可以很好的解釋上面這兩條:迭代是數據處理的基石。掃描內存中放不下的數據集時,我們要找到一種惰性獲取數據項的方式,即按需一次獲取一個數據項。這就是迭代器模式。

​ 迭代器的缺點:

​ 不能直觀的查看里面的數據。

​ 取值時不走回頭路,只能一直向下取值。

img

[復制代碼](javascript:void(0)😉

l1 = [1, 2, 3, 4, 5, 6]
obj = iter(l1)

for i in range(2):
    print(next(obj))

for i in range(2):
    print(next(obj))

[復制代碼](javascript:void(0)😉

3.3 可迭代對象與迭代器對比

​ 我們今天比較深入的了解了可迭代對象與迭代器,接下來我們說一下這兩者之間比較與應用:

可迭代對象:

​ 是一個私有的方法比較多,操作靈活(比如列表,字典的增刪改查,字符串的常用操作方法等),比較直觀,但是占用內存,而且不能直接通過循環迭代取值的這么一個數據集。

應用:當你側重於對於數據可以靈活處理,並且內存空間足夠,將數據集設置為可迭代對象是明確的選擇。

迭代器:

​ 是一個非常節省內存,可以記錄取值位置,可以直接通過循環+next方法取值,但是不直觀,操作方法比較單一的數據集。

應用:當你的數據量過大,大到足以撐爆你的內存或者你以節省內存為首選因素時,將數據集設置為迭代器是一個不錯的選擇。(可參考為什么python把文件句柄設置成迭代器)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM