iT邦幫忙

0

python line_num 對csv檔reader物件的影響?

  • 分享至 

  • xImage

程式碼很短,請大家看一下。
我的理解是,
b是csv的reader物件
然而b.line_num之後,再做list(b)會失效。

關檔重開←被逼的

同設定,b是csv的reader物件
c=list(b)之後,b無法再做line_num。
↑猜測是不是,line_num跟list(),都是in place???

預期:reader物件經過line_num之後,仍可以list(),反之亦然,
結果不是,不論做哪一個,後面都無法再處理另一個,
我明明是把reader物件指給新變數了,照理不會動到原本的b,
而且,兩個動作之前,我印了type(b),都是顯示它仍是reader物件。
#----------------------

import csv, pprint

a = open('p356.csv')
b = csv.reader(a)

print("1 type b = ", type(b)) # 1 type b = <class '_csv.reader'> ←注意

print(b.line_num) # 0
for n in b:
print(b.line_num, str(n)) # line_num將自動迭代數字,從0到總row數-1

c = list(b)
print("2 type b = ", type(b)) # 2 type b = <class '_csv.reader'> ←沒變

print(b.line_num) # 7 總row數
print(c) # [] ←its empty???
#print(c[3][2]) # 出錯了list index out of range

a.close()

print("------------------------------")

a = open('p356.csv') # ←被逼的,因為上面的reader物件不知何故不能用了。
b = csv.reader(a)
c = list(b)
print(b.line_num) # 7
print(c[3][2]) # 52

print("type b = ", type(b)) # type b = <class '_csv.reader'> 注意←是一樣!!

for n in b:
print(b.line_num, str(n)) # print nothing ???為什麼?list又對b做什麼了?

a.close()

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

0
froce
iT邦大師 1 級 ‧ 2022-12-21 09:41:18
最佳解答

簡單的說,就是你不懂操作一個檔案的流程。
程式在讀取一個檔案的時候,會大致依照以下流程:

  1. 開啟一個檔案,並指派一個記憶體位置存放(open)
  2. 放入指定的處理器(reader、writer)
  3. 會有個指標,依照編碼對各個字元做操作,所以你可以看到file-like object裡有個seek方法,用來移動指標,read、readline、readlines都是對seek做包裝。
    https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#tut-files
  4. 關檔(close),釋放記憶體

接下來我們來拆解你的疑問,請搭配我給的連接看。

import csv, pprint

# 開檔,a會是一個file-like object
a = open('p356.csv')
# 把a傳入讀取器,用來處理格式、字元編碼之類的問題
b = csv.reader(a)

print("1 type b = ", type(b))

# https://docs.python.org/zh-tw/3/library/csv.html
print(b.line_num) #line_num是目前讀取的行數(指標經過的換行字符的個數),因為你還沒開始跑,當然是0

# 迴圈讀取,會把指標移到檔案結尾
for n in b:
    print(b.line_num, str(n))

# 因為已經讀取完了,所以不會繼續讀取,如果你自行操作指標回到某一個段落,c的內容會是段落以後的內容
c = list(b) 
# b你沒對他做任何操作,當然不會變,還是個csv.reader類型
print("2 type b = ", type(b))

# 關檔,a都關了,b建立在a的基礎上,當然一起關
a.close()

print("------------------------------")

# 當然不能用,a和b都沒了
a = open('p356.csv') # ←被逼的,因為上面的reader物件不知何故不能用了。

b = csv.reader(a)
c = list(b) ## 在這裡,[list()]會把[b]讀完(相當於執行過[for i in b:] loop),導致[b]的指標到結尾;
 ...

讀取器有指標這個概念可以應用到之後的sql操作,不過通常不會去自己操作指標,建立好觀念就可以了。

謝謝。我用pointer來想像,就比較能接受了。
(知道是一回事,在應用上總是會忘記)

請口有什麼方法在不關檔重開的情況下,
讓指標回到reader物件的最開頭嗎??

re.Zero iT邦研究生 5 級 ‧ 2022-12-21 19:36:42 檢舉

@iiuu
簡言,可以。
在你的案例裡,用 a.seek(0) 可將 a 的指標設至檔頭,就能再度使用 for n in b:
但可能造成(使用 a 運作的) b 發生無法預期的動作。
風險說明範例我在我的答案後做 Update 補充。

0
re.Zero
iT邦研究生 5 級 ‧ 2022-12-21 02:39:26

你遇到的問題,是因為你不知道:

  • 疊代器(Iterator)for 陳述式的關聯特性。
  • csv.reader() 回傳之 Reader 物件具有可疊代(iterable)的特性(__next__())。

可以的話,我是想讓你看下 iterator和generator雜談之一———剖析for in內部機制 就好。
(因為短小精幹 + 中文;我知道很多人討厭長文~)
但感覺這篇文章可能不會讓你了解你面臨的狀況,所以我又寫一點範例以說明。
(先看完上附連結文章,有概念後再看我的範例。)

## 
print('■ Phase01:')
myList = list(range(1, 10, 2))
print('myList:', myList)
myListSlice = myList[:2] ## 只取[myList]前兩項,供在 Phase01 時做[疊代器];
print('myListSlice:', myListSlice)
## https://docs.python.org/zh-tw/3/tutorial/classes.html#iterators
myListIterator = iter(myListSlice) ## myListIterator 只有兩項;
try: ## 例外處理 for [A3];
    i = next(myListIterator); print('A1:', i)
    i = next(myListIterator); print('A2:', i)
    i = next(myListIterator); ## 因為沒有下一個,這裡會引發 StopIteration 例外。
    print('A3:', i)
except Exception as err:
	print('□ Get a Exception:[' + type(err).__name__ + ']:',end='');
	print(err)
## 
print('■ Phase02:')
print('myList:', myList)
myListIterator = iter(myList) ## myListIterator 有 5 項;
for i in myListIterator: ## 概念上,取一次[i],相當於跑一次 [__next__()];
	print('Iterator:(i):', i)
## https://docs.python.org/3/library/stdtypes.html#list
myRemain01 = list(myListIterator) ## list constructor: 將 myListIterator 剩下的(沒了)建立 list 物件(所以是空的);
print('myRemain01:[', type(myRemain01), ']:\n', myRemain01)
## 
print('■ Phase03:')
print('myList:', myList)
myListIterator = iter(myList)
myCount = 0
for i in myListIterator:
    print('Iterator:(i):', i)
    myCount += 1
    if myCount > 2: break ## 強制跳出,不讓 myListIterator 被跑完。
myRemain01 = list(myListIterator) ## 將 myListIterator 剩下的建立 list 物件;
print('myRemain01:[', type(myRemain01), ']:\n', myRemain01)
myRemain02 = list(myListIterator) ## 再將 myListIterator 剩下的(早就沒了)建立 list 物件(所以是空的);
print('myRemain02:[', type(myRemain02), ']:\n', myRemain02)
## 

以下是我的輸出
https://ithelp.ithome.com.tw/upload/images/20221221/20155649EbU1LhYKeQ.jpg

這樣,能多點概念嗎?(尤其是 if myCount > 2: break ## 強制跳出,不讓 myListIterator 被跑完。 之後的狀況,希望能加強你的印象。)

接著,來看看你的程式:

## 
#----------------------
import csv, pprint

a = open('p356.csv')
b = csv.reader(a)

print("1 type b = ", type(b))

print(b.line_num) ## line_num : 表示讀取了多少行,可看作概念上的 指標/指針。
## https://docs.python.org/3/library/csv.html#csv.csvreader.line_num
for n in b: ## 在這個[for],概念上,取一次[n],相當於跑一次[b]的[__next__()];
    print(b.line_num, str(n))

c = list(b) ## 在前面[for n in b:]後,[b]的指標已到結尾,所以沒有剩下的;這樣[c]只能被指派個空的 list;
print("2 type b = ", type(b))

print(b.line_num) ## 7 : 表示讀取了 7 行
print(c) ## [c]為空的 list 之原因已於前述。
#print(c[3][2]) ## 「出錯了」是正常運作。

a.close()

print("------------------------------")

a = open('p356.csv') # ←被逼的,因為上面的reader物件不知何故不能用了。
## Python:「[b]用完了怪我?我是無辜的……」
b = csv.reader(a)
c = list(b) ## 在這裡,[list()]會把[b]讀完(相當於執行過[for i in b:] loop),導致[b]的指標到結尾;
print(b.line_num) ## 7 : 表示讀取了 7 行
print(c[3][2])

print("type b = ", type(b))

for n in b:
    print(b.line_num, str(n)) ## 前面[c = list(b)]後,[b]的指標已到結尾,所以沒有剩下的;

a.close()
#----------------------
## 

希望你能了解 Python 神奇(恐怖)之處。
/images/emoticon/emoticon37.gif

Update:
因為 froce 提到 file-like object 的 seek、指標,
而 iiuu 想問能否「讓指標回到reader物件的最開頭」,
所以補充內容,以下案例:

## 
print('■ Phase01: Just-a-normal-run:')
## 
F = open('myTable.csv') ## F: File;
R = csv.reader(F) ## R: Reader;
print('A: R.line_num:', R.line_num) ## R 記得已讀了 0 行;
for i in R: print(R.line_num, ':', str(i)) ## 正常列印;
print('B: R.line_num:', R.line_num) ## R 記得已讀了 4 行;
## 
print('■ Phase02: Try-reset:')
## 
## https://docs.python.org/3/library/io.html#io.IOBase.seek
F.seek(0) ## 在 F 將檔案的指標移至檔頭;
print('A: R.line_num:', R.line_num) ## R 記得已讀了 4 行;
for i in R: print(R.line_num, ':', str(i)) ## 還是正常列印;
print('B: R.line_num:', R.line_num) ## R 記得已讀了 8 行;
## 所以,移動檔案 F 的指標不會讓 R 做出對應的動作以修正(?)已讀紀錄;
## 
F.close()
## 

輸出:
https://ithelp.ithome.com.tw/upload/images/20221221/20155649MaAcNKxbXx.jpg
從以上結果能看到,檔案 F 只有 4 行,但 R 卻記得已讀了 8 行;
因為 Reader (R) 沒被設計能知道 File (F) 的指標異動與因應行為。
所以,要這樣操作的話要清楚你在做啥、看過文件、做各項測試掌握特性,才能使用以避免意外。
(例如,你確定用不到 R.line_num 的值,也測試過沒啥詭異影響,才能試著使用。)

看更多先前的回應...收起先前的回應...

太恐怖了,依然看不懂。(對您的長文感到抱歉),
我有看了您推薦的文章,了解yield的調性。
(也只了解到該部份,後面的又霧煞煞,希望有看懂的一天)

re.Zero iT邦研究生 5 級 ‧ 2022-12-21 20:24:55 檢舉

抱歉讓你看無~
(不過你怎會跑去看 generator 的部分……)

iterator的文章有提到,就一路看過去……文中還有一些別的連結,就走上歧路(???)。generator是含有yield的函數對吧……會扔東西然後中途暫停的,後面yield from就又看無了。

re.Zero iT邦研究生 5 級 ‧ 2022-12-22 18:34:43 檢舉

「(不過你怎會跑去看 generator 的部分……)」是我胡言亂語而已,不用太在意;本來能更進一步的閱覽、學習是你的自由與權益!(自由萬歲!!)
generator(產生器) — 術語表 定義中,你的「generator是含有yield的函數對吧」想法是正確的。
而關於 "yield from" ,常見用途是用 "yield from g" 替換 "for v in g: yield v"。
實際內容上, "yield from " 語法提議 有更詳盡的 敘述/探討;或是看 Yield expressions — 6. Expressions 後的黃色區塊內的參考。
(不過,那內容給新手看會不會傻掉就是另一個問題了~~)
而簡要(?)且實務上的 敘述/探討,可看 In practice, what are the main uses for the "yield from" syntax in Python 3.3? 的各項討論內容。
(除了最佳解答外,其他的討論也蠻有趣的,能看到許多看法~~)

froce iT邦大師 1 級 ‧ 2022-12-23 09:52:50 檢舉

yield from 後面接的是一個 generator,yield是接普通的物件
有些像是 os.walk 要遞迴做遍歷的就會用到

下面這兩個是相等的。

 for x in walk(new_path, topdown, onerror, followlinks):
       yield x
       
 yield from walk(new_path, topdown, onerror, followlinks)

我要發表回答

立即登入回答