天天看點

python 常用檔案讀寫及with的用法一  CSV檔案二 普通文本檔案及with三 xml json讀寫

一  CSV檔案

     csv是逗号分隔值檔案格式,一般用WORDPAD或記事本(NOTE),EXCEL打開。csv(逗号分隔值)是一種用來存儲資料的純文字檔案,通常都是用于存放電子表格或資料的一種檔案格式。這種格式的資料通常應用在資料處理方面,比如我們爬蟲的資料量較小,不用插入資料庫,或者隻是臨時測試,那麼可以把資料存放在.csv檔案中,很是友善。下面我們結合pythonCSV子產品的源碼講解具體怎麼用

       要用到操作csv檔案的庫,要導入csv子產品,我們進入到csv源碼之中來看看。

“""
csv.py - read/write/investigate CSV files
"""

import re
from _csv import Error, __version__, writer, reader, register_dialect, \
                 unregister_dialect, get_dialect, list_dialects, \
                 field_size_limit, \
                 QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
                 __doc__
from _csv import Dialect as _Dialect

from collections import OrderedDict
from io import StringIO
           

       首先來看,csv的引入。可以看出它導入了_csv子產品,那麼也就是說,此子產品的實作是有兩層,最底層的方法實作是在_CSV子產品中,CSV子產品隻是對于_csv子產品的一個再次封裝和功能強化。

        來看具體用法,首先是讀csv操作,csv子產品有提供了兩種方式來讀,一種是csv.reader,另一種是Dictreader,先看reader:

import csv


with open('z.csv', 'r+', newline='',encoding='utf-8') as csv_file:
    reader = csv.reader(csv_file)
    for row in reader:
        print(str(row))
           

      運作結果:

['name', 'city', 'price']
['清華珠三角研究院', '廣州-黃埔區', '1-1.5萬/月']
['上海橋之隊教育科技有限公司', '上海-楊浦區', '6-8千/月']
['文思海輝技術有限公司Pactera Tec...', '成都', '1-1.5萬/月']
['上海皓維電子股份有限公司', '上海-嘉定區', '1-2萬/月']
           

         with的用法稍後讨論,上面運作結果,那麼我們深入到reader這個方法本身去看看究竟,python源代碼如下:

def reader(iterable, dialect='excel', *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ 
    """
    csv_reader = reader(iterable [, dialect='excel']
                            [optional keyword args])
        for row in csv_reader:
            process(row)
    
    The "iterable" argument can be any object that returns a line
    of input for each iteration, such as a file object or a list.  The
    optional "dialect" parameter is discussed below.  The function
    also accepts optional keyword arguments which override settings
    provided by the dialect.
    
    The returned object is an iterator.  Each iteration returns a row
    of the CSV file (which can span multiple input lines).
    """
    pass
           

    這是python官方對于此方法的解釋,用法全部都說了。簡單明了,是以養成看官方文檔的習慣很重要,reader函數,接收一個可疊代的對象,可以是一個清單,也可以是一個檔案句柄,傳回的便是每一行的資料。我試過了,如果你輸入的是一個清單或者字元串,他會把其中的元素都進行疊代出來。iterable方法是衡量一個對象是否是可疊代對象,但是可疊代對象都是疊代器嗎?No,要想一個對象是可疊代對象,首先要實作其next方法,這樣的對象才會被稱為可疊代對象。

      再來看下一種方式,DictReader方法,首先看此方法的實際運用:

import csv


for d in csv.DictReader(open('z.csv', 'r+', newline='',encoding='utf-8')):
    print (d)
           

      運作結果:

OrderedDict([('name', '清華珠三角研究院'), ('city', '廣州-黃埔區'), ('price', '1-1.5萬/月')])
OrderedDict([('name', '上海橋之隊教育科技有限公司'), ('city', '上海-楊浦區'), ('price', '6-8千/月')])
OrderedDict([('name', '文思海輝技術有限公司Pactera Tec...'), ('city', '成都'), ('price', '1-1.5萬/月')])
OrderedDict([('name', '上海皓維電子股份有限公司'), ('city', '上海-嘉定區'), ('price', '1-2萬/月')])
           

    OrderedDict為python中的有序字典,他會記錄字段的順序,按順序輸出。

    再來分析python關于此類的源碼:

class DictReader:
    def __init__(self, f, fieldnames=None, restkey=None, restval=None,
                 dialect="excel", *args, **kwds):
        self._fieldnames = fieldnames   # list of keys for the dict
        self.restkey = restkey          # key to catch long rows
        self.restval = restval          # default value for short rows
        self.reader = reader(f, dialect, *args, **kwds)
        self.dialect = dialect
        self.line_num = 0

    def __iter__(self):
        return self

    @property
    def fieldnames(self):
        if self._fieldnames is None:
            try:
                self._fieldnames = next(self.reader)
            except StopIteration:
                pass
        self.line_num = self.reader.line_num
        return self._fieldnames

    @fieldnames.setter
    def fieldnames(self, value):
        self._fieldnames = value

    def __next__(self):
        if self.line_num == 0:
            # Used only for its side effect.
            self.fieldnames
        row = next(self.reader)
        self.line_num = self.reader.line_num

        # unlike the basic reader, we prefer not to return blanks,
        # because we will typically wind up with a dict full of None
        # values
        while row == []:
            row = next(self.reader)
        d = OrderedDict(zip(self.fieldnames, row))
        lf = len(self.fieldnames)
        lr = len(row)
        if lf < lr:
            d[self.restkey] = row[lf:]
        elif lf > lr:
            for key in self.fieldnames[lr:]:
                d[key] = self.restval
        return d
           

     這個類裡面用到了python的進階用法,裝飾器的嵌套應用,此部分内容移步到我的裝飾器講解篇,這樣會更加的清晰,那麼在這裡要分析的是,這個類裡面的next方法,實際上調用了_csv子產品裡的reader,把其中的每行的元素與字段,再次封裝成一個有序字典來進行傳回,這裡可以看到我剛才說的,這兩種讀取方法都傳回了可疊代對象,那麼可疊代對象必然要實作next方法。

    說完讀方法,再來讨論一下CSV檔案的寫操作。那麼在平常的應用之中,我們經常把爬取到的内容寫入CSV,我們知道,從爬蟲的大部分實作方法來說,都是生成了一個字典,那麼我們寫入CSV時,我們想簡單一些,把爬取出來的字典直接寫入,這樣很友善,那麼python顯然已經為我們實作了這個方法,這就是DictWriter。先看用法:

file = open('zane.csv', 'w', newline='', encoding='utf-8')
    headers = ['price', 'district', 'name', 'layout', "space", 'floor', 'RentType','loc']
    writers = csv.DictWriter(file, headers)
    writers.writeheader()
    for item in a.Prase_info(url):
            writers.writerow(item)
           

     這段代碼直接是用不了的,因為是我爬蟲程式删減過後的,知道用法,自己寫一個很簡單,看看運作結果:

price,district,name,layout,space,floor,RentType,loc
5000,香洲區,華發新城二期,4室2廳,164平米,高層/11層,整租,"113.526232893,22.2376243653"
5800,香洲區,華發新城二期,4室2廳,165平米,中層/11層,整租,"113.526232893,22.2376243653"
3000,香洲區,嘉園,4室2廳,95.11平米,高層/6層,整租,"117.179603933,39.1072266636"
2800,香洲區,心海州 (心悅灣),2室2廳,60平米,高層/29層,整租,"113.530491251,22.2476647077"
5000,橫琴新區,華融琴海灣,2室2廳,87平米,高層/19層,整租,"113.549125605,22.1416825418"
4300,香洲區,華發新城五期,2室2廳,90平米,中層/30層,整租,"113.516777146,22.2377163421"
           

    上述結果是爬蟲結果的一部分,可以看出已經成功寫入,下面結合python源代碼進行分析:

class DictWriter:
    def __init__(self, f, fieldnames, restval="", extrasaction="raise",
                 dialect="excel", *args, **kwds):
        self.fieldnames = fieldnames    # list of keys for the dict
        self.restval = restval          # for writing short dicts
        if extrasaction.lower() not in ("raise", "ignore"):
            raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
                             % extrasaction)
        self.extrasaction = extrasaction
        self.writer = writer(f, dialect, *args, **kwds)

    def writeheader(self):
        header = dict(zip(self.fieldnames, self.fieldnames))
        self.writerow(header)

    def _dict_to_list(self, rowdict):
        if self.extrasaction == "raise":
            wrong_fields = rowdict.keys() - self.fieldnames
            if wrong_fields:
                raise ValueError("dict contains fields not in fieldnames: "
                                 + ", ".join([repr(x) for x in wrong_fields]))
        return (rowdict.get(key, self.restval) for key in self.fieldnames)

    def writerow(self, rowdict):
        return self.writer.writerow(self._dict_to_list(rowdict))

    def writerows(self, rowdicts):
        return self.writer.writerows(map(self._dict_to_list, rowdicts))

# Guard Sniffer's type checking against builds that exclude complex()
try:
    complex
except NameError:
    complex = float
           

    從我們首先執行此句writers = csv.DictWriter(file, headers)時,可以看出在DictWriter類中,我們的headers也就是我們要寫入的字段名,被傳入到了fieldnames變量中,可以看此變量的注釋,他說這是字典鍵的清單。結合用法,我們最後傳入到這裡的字典不就是以我們每個字段名為鍵的字典嗎。再看,當我們執行writers.writeheader()時,代碼裡首先用zip函數,把清單轉換成了元組,把元組在轉換為字典,寫入到了首行之中。接下來的方法,_dict_to_list就是把我們最後傳入的字典轉換為清單,根據鍵名去寫值。這裡還看到,如果我們傳入的字典的鍵與之前寫入的對不上,會抛出異常。最後我們隻需要在我們需要疊代寫入的地方,調用writerow方法,可以實作把字典寫入我們的CSV檔案之中。

二 普通文本檔案及with

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
           

  程式示例:

f = open(yourfilepath, 'r')
ss = f.read()  # 讀進檔案的全部内容傳回一個字元串
ss = f.read(1024)  # 讀取指定位元組的内容傳回一個字元串

line = f.readline()  # 讀取檔案的一行傳回字元串(包含換行符)
line = line.strip("\n")  # 處理的時候一般要去掉換行符(這裡是\n)

lines = f.readlines()  # 讀取所有行傳回一個清單list,每個元素(類型為字元串)為檔案的一行
for line in f.readlines():
    pass
f.close()  # 檔案用完要記得關閉,可以用with關鍵字,不用手動關閉,程式會自動關閉

# 以下均用with來讀寫檔案
with open('yourfilepath', 'w') as tmpf:
    a = 100; b = 97.5; c = 'Good'
    tmpf.write('number=%d  score=%f  result%s' % (a, b, c))
    # 或者直接寫入檔案内容——字元串(或二進制資料)
    ss = 'yourstring'
    f.write(ss)  # 直接寫入

ss = 'yourstring'
f.writeline(ss)  # 寫入時會自動加入換行符

ss = ['a', 'b', 'c']
f.writelines(ss)  # 參數為字元串序列
           

    python中的open和作業系統的open傳回值都是檔案描述符,但是有的人會問,為什麼用with。那麼我們來分析一下,如果我們open一個檔案之後,如果讀寫發生了異常,是不會調用close()的,那麼這會造成檔案描述符的資源浪費,久而久之,會造成系統的崩潰。那麼我們怎麼去解決呢,python裡面為我們提供了一種解決方案,那就是with,也叫上下文管理器。那麼with具體是怎麼實作的呢。在疊代器的部分,我們知道所有可疊代對象都實作了iterable方法,疊代器又多實作了next方法,那麼,在with中,我們必須實作兩個方法才能進行上下文也就是with的作用,那就是__enter__方法和__exit__方法。看下面執行個體:

class File():

    def __init__(self,filename,mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename,self.mode)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("will exit")

with File('out.txt','w') as f:
    f.write("hello")
           

運作結果如下:

C:\Users\Administrator\PycharmProjects\裝飾器\venv\Scripts\python.exe C:/Users/Administrator/PycharmProjects/裝飾器/with上下文管理器.py
entering
will exit

Process finished with exit code 0
           

可以看出,with ...as之後的語句,相當于調用了__enter__方法,在讀取成功或者異常,它在調用__exit__方法。那麼還有一種實作with的方法,那就是裝飾器,這個裝飾器是python提供的,請看執行個體:

from contextlib import contextmanager

@contextmanager
def my_open(path,mode):
    f = open(path,mode)
    yield f
    f.close()


with my_open('out.txt','w') as f:
    f.write("hello, the simplest context manager")
           

   這裡的用法這麼了解最好,yield上面便是enter方法,下面便是exit方法,用生成器來實作上下文管理。

三 xml json讀寫

     xml 如果用傳統語言的話,最好用的庫是libxml,python也支援了,這裡隻講最簡單的寫法。

rom lxml import etree, objectify

E = objectify.ElementMaker(annotate=False)
anno_tree = E.annotation(
    E.folder('VOC2014_instance'),
    E.filename("test.jpg"),
    E.source(
        E.database('COCO'),
        E.annotation('COCO'),
        E.image('COCO'),
        E.url("http://test.jpg")
    ),
    E.size(
        E.width(800),
        E.height(600),
        E.depth(3)
    ),
    E.segmented(0),
)

etree.ElementTree(anno_tree).write("text.xml", pretty_print=True)
           

  輸出結果:

<annotation>
  <folder>VOC2014_instance/person</folder>
  <filename>test.jpg</filename>
  <source>
    <database>COCO</database>
    <annotation>COCO</annotation>
    <image>COCO</image>
    <url>http://test.jpg</url>
  </source>
  <size>
    <width>800</width>
    <height>600</height>
    <depth>3</depth>
  </size>
  <segmented>0</segmented>
  <object>
    <name>person</name>
    <bndbox>
      <xmin>100</xmin>
      <ymin>200</ymin>
      <xmax>300</xmax>
      <ymax>400</ymax>
    </bndbox>
    <difficult>0</difficult>
  </object>
</annotation>
           

       讀取xml檔案,用xpath最簡單,百度xpath用法就好了,後續關于爬蟲的章節,會對xpath重點講。

    下面來說說json 爬蟲的時候最幸福的事就是遇到json,不再用xpath解析了,可以像操作字典一樣操作json例子如下:

{
    "fontFamily": "微軟雅黑",
    "fontSize": 12,
    "BaseSettings":{
        "font":1,
        "size":2
                   }
}
           
import json

def loadFont():
    f = open("Settings.json", encoding='utf-8')  //設定以utf-8解碼模式讀取檔案,encoding參數必須設定,否則預設以gbk模式讀取檔案,當檔案中包含中文時,會報錯
    setting = json.load(f)
    family = setting['BaseSettings']['size']   //注意多重結構的讀取文法
    size = setting['fontSize']   
    return family

t = loadFont()

print(t)

結果:
           

   很簡單的。