python 常用文件读写及with的用法

一  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)

结果:

   很简单的。

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值