Python CookBook —— Chapter 6 (个人笔记)

文章目录

  • Chap 6 数据编码和处理
    • 6.1 读写 CSV 数据 --- csv.reader(), csv.writer(), csv.DictReader(), csv.DictWriter()
    • 6.2 读写 JSON 数据 --- dumps(), loads(), dump(), load(), pprint() & indent 参数
    • 6.3 解析简单的 XML 数据 --- xml.etree.ElementTree.parse(), find(), iterfind(), findtext(), .tag, .text, .get('xxx')
    • 6.4 增量式解析大型 XML 文件 --- 使用迭代器&生成器进行增量式的数据处理
    • 6.5 将字典转换为 XML --- 使用 Element 类, tostring(), set(), escape(), unescape()
    • 6.6 解析和修改 XML --- doc.getroot(), root.remove(), list(root), root.insert(), doc.write()
    • 6.7 利用命名空间解析 XML 文档 --- ???
    • 6.8 与关系型数据库的交互 --- ⭐⭐⭐
    • 6.9 编码&解码十六进制数 --- binascii.b2a_hex(), binascii.a2b_hex(), base64.b16encode(), base64.b16decode()
    • 6.10 编码&解码 Base64 数据 --- base64.b64encode(), base64.b64decode()
    • 6.11 读写二进制数组数据 --- Struct(fmt), struct.pack(), struct.unpack(), struct.unpack_from(), struct.size
    • 6.12 读取嵌套和可变长二进制数据 --- ???
    • 6.13 数据的累加与统计操作 --- Pandas 库的简介

Chap 6 数据编码和处理

6.1 读写 CSV 数据 — csv.reader(), csv.writer(), csv.DictReader(), csv.DictWriter()

下面介绍了在读写 csv 文件时遇到的各种问题与解决方案:

# 1. 对大多数的 CSV 格式的数据读写问题, 都可以使用 csv 库:
import csv

with open('stocks.csv') as f:
    f_csv = csv.reader(f)    # csv.reader() 通过文件对象 f 创建一个 csv.reader 对象
    headers = next(f_csv)    # 对 csv.reader 对象迭代一次获取首行
    print(headers)

    for row in f_csv:    # row 是列表, 通过下标访问字段值
        print(row)


# 2. 使用命名元组增强代码可读性:
from collections import namedtuple

with open('stocks.csv') as f:
    f_csv = csv.reader(f)
    headings = next(f_csv)

    Row = namedtuple('Row', headings)    # 命名元组(类型名, 字段列表)

    for r in f_csv:
        row = Row(*r)    # 传递字段值以实例化命名元组对象
        print(row.Symbol, row.Price, row.Date, sep='\t')    # 通过字段名访问字段值(但要求列名为合法的Python标识符)


# 3. 还可以将数据读取到一个字典序列中:

with open('stocks.csv') as f:
    f_csv = csv.DictReader(f)    # csv.DictReader() 创建一个 csv.DictReader 对象 (内部是 OrderedDict 的可迭代对象)

    for row in f_csv:
        print(row['Symbol'], row['Price'], row['Date'], sep='\t')    # “有序字典”也能通过字段名访问字段值


# 4. 为写入 CSV 数据, 仍可使用 csv 模块, 不过得先创建一个 writer 对象:
headers = ['Symbol', 'Price', 'Date', 'Time', 'Change', 'Volume']
rows = [
    ('AA', 39.48, '6/11/2007', '9:36am', -0.18, 181800),
    ('AIG', 71.38, '6/11/2007', '9:36am', -0.15, 195500),
    ('AXP', 62.58, '6/11/2007', '9:36am', -0.46, 935000),
        ]

with open('stocks_plus.csv', 'w', newline='') as f:    # newline='' 确保写入时没有多余的空行
    f_csv = csv.writer(f)       # 首先创建 writer 对象
    f_csv.writerow(headers)     # 写入一行
    f_csv.writerows(rows)       # 写入多行


# 5. 将“字典序列型”数据写入 CSV 的实现方式:
headers = ['Symbol', 'Price', 'Date', 'Time', 'Change', 'Volume']
rows = [
    {'Symbol': 'AA', 'Price': 39.48, 'Date': '6/11/2007', 'Time': '9:36am', 'Change': -0.18, 'Volume': 181800},
    {'Symbol': 'AIG', 'Price': 71.38, 'Date': '6/11/2007', 'Time': '9:36am', 'Change': -0.15, 'Volume': 195500},
    {'Symbol': 'AXP', 'Price': 62.58, 'Date': '6/11/2007', 'Time': '9:36am', 'Change': -0.46, 'Volume': 935000},
    ]

with open('stocks_plus.csv', 'a', newline='') as f:
    f_csv = csv.DictWriter(f, headers)    # 首先创建 DictWriter 对象 (headers 指定首行, 即字段名)
    f_csv.writeheader()         # 写入上面指定的“首行”
    f_csv.writerows(rows)       # 写入字典序列中的数据


# 6. 你应该总是优先选择 csv 模块来 “分割 or 解析” CSV 数据:
with open('stocks.csv') as f:
    f_csv = csv.reader(f, delimiter=',')    # 关键字参数 delimiter 用于指定分隔符
    for row in f_csv:
        print(row)

""" Remark: 不要自己写代码来分割 csv 的字段, 你有可能会遇到一些棘手的问题(See Page 177) """


# 7. 若你正在读取 CSV 数据并将它们转换为命名元组, 需注意对列名进行 “合法性认证” (See Page 177):
import re

with open('stocks.csv') as f:
    f_csv = csv.reader(f)
    # 使用 RegEx 替换原 CSV 首行(即字段名)中的非法标识符
    # 只允许 a-z & A-Z & (_) 这 3 类字符
    headers = [re.sub(r'[^a-zA-Z_]', '_', h) for h in next(f_csv)]
    Row = namedtuple('Row', headers)    # 使用替换完成后的 headers 创建命名元组类
    for r in f_csv:
        r = Row(*r)
        print(r.Symbol, r.Volume)


# 8. 在 CSV 数据上执行“类型转换”的例子:
""" Remark: 要强调的是, csv 产生的数据都是字符串类型的 """
col_types = [str, float, str, str, float, int]

with open('stocks.csv') as f:
    f_csv = csv.reader(f)
    headers = next(f_csv)

    for row in f_csv:
        # 将 row 中数据按照 col_types 中指定的类型进行转换(这是类型转换的一个重要例子!)
        typed_row = tuple(convert(value) for convert, value in zip(col_types, row))    # tuple 中是一个 genExpr
        print(typed_row)


# 9. 转换从 CSV 中读取的字典中特定字段的例子:
print(format(' Reading as dicts with type conversion ', '*^80'))

# 9.1 首先定义要做类型转换的字段的元组序列
field_types = [
    ('Price', float),
    ('Change', float),
    ('Volume', int)
]

# 9.2 以字典方式读取 CSV 数据的同时进行类型转换
with open('stocks.csv') as f:
    f_csv = csv.DictReader(f)
    for row in f_csv:
        row.update(    # 通过字典的 update 方法直接更新字典的值 (value 经过了类型转换)
            (key, conversion(row[key])) for key, conversion in field_types    # 这也是个 genExpr
        )
        print(row)

若你读取 CSV 数据的目的是做数据分析和统计的话,不妨试一试 Pandas 包。Pandas 包含了一个非常方便的函数 pandas.read_csv(),它可以加载 CSV 数据到一个 DataFrame 对象中去。然后利用该对象就可以生成各种形式的统 计、过滤数据以及执行其他高级操作了。

6.2 读写 JSON 数据 — dumps(), loads(), dump(), load(), pprint() & indent 参数

读写 JSON (JavaScript Object Notation) 编码格式的数据,json 模块提供了一种很简单的方式来编码 & 解码 JSON 数据。

# 1. json.dumps() 将 Python Dict 转换为 JSON Str:
import json

data = {
    'name': 'ACME',
    'shares': 100,
    'price': 542.23,
}

json_str = json.dumps(data)
print(type(json_str), '---', json_str)


# 2. json.loads() 将 JSON Str 转换回 Python Dict:
data = json.loads(json_str)
print(type(data), '---', data)    # 注意 key 的引号变化


# 3. 处理 Json 文件时, 可使用 json.dump() & json.load() 来编码 & 解码 Json 数据:

# 3.1 Writing Json data
with open('data.json', 'w') as f:
    json.dump(data, f)    # 将 Python Dict 写入 Json 文件

# 3.2 Reading Json data
with open('data.json', 'r') as f:
    data = json.load(f)
    print(type(data), data)    # 将 Json 文件读取为 Python Dict


# 4. Json 编码前后 True, False, None 的变化:
before = {'a': True, 'b': False, 'c': 'Hello', 'd': None}
after = json.dumps(before)
print(before, after, sep='\n')


# 5. 使用 pprint 模块的 pprint() 函数以打印出 Json 数据的结构:
from pprint import pprint

weather = {
    "HeWeather6": [
        {
            "basic": {
                "cid": "CN101010100",
                "location": "北京",
                "parent_city": "北京",
                "admin_area": "北京",
                "cnty": "中国",
                "lat": "39.90498734",
                "lon": "116.40528870",
                "tz": "8.0"
            },
            "daily_forecast": [
                {
                    "cond_code_d": "103",
                    "cond_code_n": "101",
                    "cond_txt_d": "晴间多云",
                    "cond_txt_n": "多云",
                    "date": "2017-10-26",
                    "hum": "57",
                    "pcpn": "0.0",
                    "pop": "0",
                    "pres": "1020",
                    "tmp_max": "16",
                    "tmp_min": "8",
                    "uv_index": "3",
                    "vis": "16",
                    "wind_deg": "0",
                    "wind_dir": "无持续风向",
                    "wind_sc": "微风",
                    "wind_spd": "5"
                },
                {
                    "cond_code_d": "101",
                    "cond_code_n": "501",
                    "cond_txt_d": "多云",
                    "cond_txt_n": "雾",
                    "date": "2017-10-27",
                    "hum": "56",
                    "pcpn": "0.0",
                    "pop": "0",
                    "pres": "1018",
                    "tmp_max": "18",
                    "tmp_min": "9",
                    "uv_index": "3",
                    "vis": "20",
                    "wind_deg": "187",
                    "wind_dir": "南风",
                    "wind_sc": "微风",
                    "wind_spd": "6"
                },
                {
                    "cond_code_d": "101",
                    "cond_code_n": "101",
                    "cond_txt_d": "多云",
                    "cond_txt_n": "多云",
                    "date": "2017-10-28",
                    "hum": "26",
                    "pcpn": "0.0",
                    "pop": "0",
                    "pres": "1029",
                    "tmp_max": "17",
                    "tmp_min": "5",
                    "uv_index": "2",
                    "vis": "20",
                    "wind_deg": "2",
                    "wind_dir": "北风",
                    "wind_sc": "3-4",
                    "wind_spd": "19"
                }
            ],
            "status": "ok",
            "update": {
                "loc": "2017-10-26 23:09",
                "utc": "2017-10-26 15:09"
            }
        }
    ]
}
pprint(weather)    # pprint() 会按照 key 的“字母顺序”以一种更加美观的方式输出 Json


# 6. 若想获得漂亮の格式化字符串后输出, 可使用 json.dumps() 的 indent 参数, 它会使输出和 pprint() 效果类似:
data = {
    'name': 'ACME',
    'shares': 100,
    'price': 542.23,
}
# 对比下面两种输出效果
print(json.dumps(data))
print(json.dumps(data, indent=4))    # indent 使 json 得以结构化地输出

一般来讲,JSON 解码会根据提供的数据创建 dicts or lists,若想要创建其他类型的对象,可以给 json.loads() 传递 object_pairs_hook or object_hook 参数

# 7. 下面演示如何解码 JSON 数据并在一个 OrderedDict 中保留其顺序:
from collections import OrderedDict

# 7.1 这是一个 Json Str, 注意这里的 key 必须使用双引号(")
s = '{"name": "ACME", "shares": 50, "price": 490.1}'

# 7.2 关键字参数 object_pairs_hook 指定将 Json Str 输出为 OrderedDict
data = json.loads(s, object_pairs_hook=OrderedDict)
print(type(data), data)

最后两个例子暂时看不懂

6.3 解析简单的 XML 数据 — xml.etree.ElementTree.parse(), find(), iterfind(), findtext(), .tag, .text, .get(‘xxx’)

从简单的 XML 文档中提取数据。

from urllib.request import urlopen
import xml.etree.ElementTree as ET

# 1. Download the RSS feed and parse it
u = urlopen('http://planet.python.org/rss20.xml')
# print(u.read().decode('utf-8'))    # 查看原始 XML 文档 (与下一行代码冲突?)
doc = ET.parse(u)    # parse() 函数解析整个 XML 文档并返回一个文档对象


# 2. Extract and output tags of interest
for item in doc.iterfind('channel/item'):    # iterfind() 搜索所有在 channel 元素下的 item 元素
    title = item.findtext('title')    # item.findtext() 从已找到的 item 元素位置开始搜索 title 元素
    date = item.findtext('pubDate')
    link = item.findtext('link')
    print(title, date, link, sep='\n')


# 3. ElementTree 中每个元素有一些重要の属性 & 方法, 在解析时非常有用:
print(''*26)
print(doc)
e = doc.find('channel/title')
print(e)
print('tag & text: ', e.tag, ' & ', e.text)
print(e.get('some_attribute'))

有一点要强调的是 xml.etree.ElementTree 并不是 XML 解析的唯一方法。对于更高级的应用程序,可考虑使用 lxml。

6.4 增量式解析大型 XML 文件 — 使用迭代器&生成器进行增量式的数据处理

使用尽可能少的内存从一个超大的 XML 文档中提取数据。

# 1. 任何时候只要遇到 <增量式的数据处理>, 第一时间就应该想到迭代器 & 生成器:
from collections import Counter
from xml.etree.ElementTree import iterparse, parse


def parse_and_remove(filename, path):
    path_parts = path.split('/')    # 拆分 xml 文档的 path 为不同元素节点
    doc = iterparse(filename, ('start', 'end'))
    """ 
    iterparse(source, events=None, parser=None):
        Parses an XML section into an element tree incrementally, and reports what's going on to the user. 
            source is a filename or file object containing XML data. 
            events is a sequence of events to report back. 
                The supported events are the strings "start", "end", "comment", "pi", "start-ns" and "end-ns".
                     ~ 'start'  &   ~ 'end' 
        Returns an iterator providing (event, elem) pairs.
    """
    # Skip the root element
    next(doc)

    tag_stack, elem_stack = [], []
    for event, elem in doc:
        if event == 'start':    # 节点开始
            tag_stack.append(elem.tag)    # 存放节点标签
            elem_stack.append(elem)    # 存放节点元素
        elif event == 'end':    # 节点闭合
            if tag_stack == path_parts:    # 判断解析是否匹配指定路径
                yield elem
                # 从 elem 的父节点中删除 elem (注意此时 elem 已经被 yield)
                elem_stack[-2].remove(elem)    
            try:
                tag_stack.pop()    # 弹出(已闭合的)节点标签
                elem_stack.pop()    # 弹出(已闭合的)节点元素
            except IndexError:
                pass


# Method 1: 读取整个文档到内存中 (速度快, 吃内存)
potholes_by_zip = Counter()    # 创建 Counter 实例
doc = parse('potholes.xml')

for pothole in doc.iterfind('row/row'):
    # pothole 元素中找到 zip 元素的内容, 对其增加 Counter 计数
    potholes_by_zip[pothole.findtext('zip')] += 1    

for zipcode, num in potholes_by_zip.most_common():
    print(zipcode, num)
# Remark: 该脚本唯一的问题是它会先将整个 XML 文件加载到内存中然后解析, 当 XML 庞大时会耗费大量内存。


# Method 2: 迭代式解析 & 删除 (文档树结构从始至终没被完整的创建过, 速度稍慢, 占用少量内存)
potholes_by_zip2 = Counter()
data = parse_and_remove('potholes.xml', 'row/row')

for pothole in data:
    potholes_by_zip2[pothole.findtext('zip')] += 1

for zipcode, num in potholes_by_zip2.most_common():
    print(zipcode, num)
# Remark: 该版本的代码运行时可显著节约内存资源, 但缺点是其运行性能较前一种方法差一些。

potholes.xml 的内容参考下面:

<response>
    <row>
        <row>
            <creation_date>2012-11-18T00:00:00creation_date>
            <status>Completedstatus>
            <completion_date>2012-11-18T00:00:00completion_date>
            <service_request_number>12-01906549service_request_number>
            <type_of_service_request>Pot Hole in Streettype_of_service_request>
            <current_activity>Final Outcomecurrent_activity>
            <most_recent_action>CDOT Street Cut ... Outcomemost_recent_action>
            <street_address>4714 S TALMAN AVEstreet_address>
            <zip>60632zip>
            <x_coordinate>1159494.68618856x_coordinate>
            <y_coordinate>1873313.83503384y_coordinate>
            <ward>14ward>
            <police_district>9police_district>
            <community_area>58community_area>
            <latitude>41.808090232127896latitude>
            <longitude>-87.69053684711305longitude>
            <location latitude="41.808090232127896"
            longitude="-87.69053684711305" />
        row>
        <row>
            <creation_date>2012-11-18T00:00:00creation_date>
            <status>Completedstatus>
            <completion_date>2012-11-18T00:00:00completion_date>
            <service_request_number>12-01906695service_request_number>
            <type_of_service_request>Pot Hole in Streettype_of_service_request>
            <current_activity>Final Outcomecurrent_activity>
            <most_recent_action>CDOT Street Cut ... Outcomemost_recent_action>
            <street_address>3510 W NORTH AVEstreet_address>
            <zip>60647zip>
            <x_coordinate>1152732.14127696x_coordinate>
            <y_coordinate>1910409.38979075y_coordinate>
            <ward>26ward>
            <police_district>14police_district>
            <community_area>23community_area>
            <latitude>41.91002084292946latitude>
            <longitude>-87.71435952353961longitude>
            <location latitude="41.91002084292946"
            longitude="-87.71435952353961" />
        row>
    row>
response>

xml.etree.elementtree 相关内容参考链接:https://docs.python.org/zh-cn/3/library/xml.etree.elementtree.html

6.5 将字典转换为 XML — 使用 Element 类, tostring(), set(), escape(), unescape()

使用 Python 字典存储数据,并将它转换成 XML 格式

# 1. xml.etree.ElementTree 库通常用来做解析工作, 但它也可以创建 XML 文档:
from xml.etree.ElementTree import Element
from xml.etree.ElementTree import tostring


def dict_2_xml(tag, d):
    """ Turn a simple dict of key/value pairs into XML """
    # 1. 创建 Element 实例
    elem = Element(tag)
    for key, val in d.items():
        # 2. 以 key 为节点名创建 Element 实例
        child = Element(key)
        # 3. 指定该实例的内容为 str(val)
        child.text = str(val)
        # 4. 将 child 添加至 elem 元素的 (子元素的) 内部列表末尾
        elem.append(child)
    return elem


s = {'name': 'GOOG', 'shares': 100, 'price': 490.1}    # Python dict
e = dict_2_xml('stock', s)
print(type(e), e, sep='\t')    # 转换结果为 Element 实例


# 2. 使用 xml.etree.ElementTree.tostring() 函数可将 Element 实例转换为 <字节字符串>:
print(tostring(e))


# 3. 若想给某个元素添加属性值, 可使用 set() 方法:
e.set('_id', '2333')
print(tostring(e))    # 
""" 若想保持元素顺序, 可考虑构造一个 OrderedDict 来代替一个普通字典 """


# 4. 下面是手动构造的做法, 不推荐(why?)!
def dict_2_xml_str(tag, d):
    # 1. 开始节点标签
    parts = ['<{}>'.format(tag)]

    for key, val in d.items():
        # 2. 根据字典构建节点元素
        parts.append('<{0}>{1}'.format(key, val))

    # 3. 闭合节点标签
    parts.append(''.format(tag))
    return ''.join(parts)


# 5. 下面对这个 Python Dict 测试上面两个版本的函数
d = {'name': ''}

# 5.1 String creation
xml = dict_2_xml_str('item', d)
print(xml)    # 

# 5.2 Proper XML creation
xml2 = dict_2_xml('item', d)
print(tostring(xml2))    # <spam>

# 5.3 若需手动转换这些字符, 可使用 xml.sax.saxutils 中的 escape() & unescape() 函数:
from xml.sax.saxutils import escape, unescape

print(escape(''))    # <spam>
print(unescape(escape('')))    # 

6.6 解析和修改 XML — doc.getroot(), root.remove(), list(root), root.insert(), doc.write()

如何读取一个 XML 文档,对它做一些修改后再将结果写回 XML 文档 ?使用 xml.etree.ElementTree 模块可以很容易地处理这些任务,第一步是以通常的方式来解析该文档

# 下面利用 ElementTree 来读取这个文档并对它做一些修改:
from xml.etree.ElementTree import parse, Element

doc = parse('pred.xml')
root = doc.getroot()    # Returns the root element for this tree.
print(root)


# 1. Remove a few elements
root.remove(root.find('sri'))    # 从 root 中通过 find() 找到 sri 子节点, 然后从“父节点”中删除该元素
root.remove(root.find('cr'))


# 2. Insert a new element after ...
# 2.1 Locate Element 'nm' first
# idx_nm = root.getchildren().index(root.find('nm'))
""" getchildren() is Deprecated, use list(root) instead. """
idx_nm = list(root).index(root.find('nm'))    # index() 返回列表中首个匹配元素的索引位置
print(idx_nm)

# 2.2 Define new Element 'spam' & its content
e = Element('spam')
e.text = 'This is a test.'

# 2.3 Insert Element 'spam' at idx = idx_nm + 1
root.insert(idx_nm + 1, e)    # Inserts subelement at the given position in this element.


# 3. Write back to a file
doc.write('newpred.xml', xml_declaration=True)    # Writes the element tree to a file, as XML.
"""   xml_declaration controls if an XML declaration (即这样的内容 “” ) should be added to the file.   """

Remark:修改一个 XML 文档结构时必须牢记:所有修改都是针对父节点元素,将它 作为一个列表 来处理。例如,若要删除某个元素,通过调用父节点的 remove() 方法从该元素的直接父节点中删除;若要插入 or 增加新元素,同样使用父节点元素的 insert() & append() 方法。另外,还能对元素使用索引 & 切片操作,比如 element[i] or element[i:j]。

6.7 利用命名空间解析 XML 文档 — ???

对这节内容不太理解…


uvw


abc

6.8 与关系型数据库的交互 — ⭐⭐⭐

比较简单的数据库操作,稍后环境弄好了再补上


uvw


abc

6.9 编码&解码十六进制数 — binascii.b2a_hex(), binascii.a2b_hex(), base64.b16encode(), base64.b16decode()

将一个十六进制字符串解码成一个字节字符串 or 将一个字节字符串编码成一个十六进制字符串

# 1. 简单的解码 or 编码一个十六进制的原始字符串, 可使用 binascii or base64 模块
from binascii import b2a_hex, a2b_hex
from base64 import b16encode, b16decode

s = b'Hello'    # 原始的 byte string

# byte string to hex string (Encode)
h = b2a_hex(s)
hh = b16encode(s)
print(h, hh)

# hex string to byte string (Decode)
b = a2b_hex(h)
bb = b16decode(hh)
print(b, bb)

"""
    上面两种技术的主要区别在于大小写的处理:
        函数 base64.b16decode() & base64.b16encode() 只能操作大写形式的十六进制字母;
        而 binascii 模块中的函数大小写都能处理.
"""

# 另外, 编码函数的输出总是一个“字节字符串”, 若想强制以 Unicode 形式输出, 可采用下述解码步骤:
print(b, '>>>以 Unicode 形式输出>>>', b.decode('ascii'))
print(hh, '>>>以 Unicode 形式输出>>>', hh.decode('ascii'))
print(hh, '>>>以 utf-8 形式输出>>>', hh.decode('utf-8'))

Remark:在解码十六进制时,函数 b16decode() & a2b_hex() 可接受字节 or unicode 字符串,但 unicode 字符串只允许包含 ASCII 编码的十六进制数。

6.10 编码&解码 Base64 数据 — base64.b64encode(), base64.b64decode()

要使用 Base64 格式解码 or 编码二进制数据,可使用 base64 模块中の函数 b64encode() & b64decode()

from base64 import b64encode, b64decode    # 注意上一节用是 ‘16’

# 0. initial byte data
s = b'hello python world'

# 1. Encode as Base64
a = b64encode(s)
print(a)

# 2. Decode from Base64
b = b64decode(a)
print(b)

Remark:Base64 编码仅用于面向字节的数据,如字节字符串 & 字节数组。此外,编码处理的输出总是一个字节字符串。

# 3. 若想混合使用 Base64 编码的数据 & Unicode 文本, 可使用下述解码步骤:
print(b64encode(b'Aimer'), ' >>> ', b64encode(b'Aimer').decode('ascii'))
print(b64encode(b'Aimer'), ' >>> ', b64encode(b'Aimer').decode('utf-8'))

Page 198 最后的 Remark 不明白。。。

6.11 读写二进制数组数据 — Struct(fmt), struct.pack(), struct.unpack(), struct.unpack_from(), struct.size

可以使用 struct 模块处理二进制数据。下面将一个 Python 元组列表写入一个二进制文件,并使用 struct 将每个元组编码为一个结构体。

from struct import Struct
from collections import namedtuple
import numpy as np

def write_records(records, format, f):
    """ Write a sequence of tuples to a binary file of structures. """
    record_struct = Struct(format)    # 创建 Struct 实例(以声明一个结构体)
    for r in records:
        f.write(record_struct.pack(*r))    # 通过 struct 实例的 pack() 方法将元组数据打包


def read_records(format, f):
    """ 以块的形式增量读取文件 """
    record_struct = Struct(format)
    # iter() 创建一个迭代器, 返回固定大小的数据块, 且该迭代器会不断调用参数中的匿名函数, 直到它返回特殊值 b'' 时, 迭代停止
    chunks = iter(lambda: f.read(record_struct.size), b'')    # read 中的可选参数指定读取的字节数
    return (record_struct.unpack(chunk) for chunk in chunks)    # genExpr


def unpack_records(format, data):
    record_struct = Struct(format)
    """
        下面的 unpack_from() 对于从一个大型二进制数组中提取二进制数据非常有用, 
            因为它不会产生任何临时对象 or 进行内存复制操作。
            你只需要给它一个字节字符串 (或数组) & 一个字节偏移量, 它会从那个位置开始直接解包数据。 
    """
    return (record_struct.unpack_from(data, offset)
            for offset in range(0, len(data), record_struct.size))    # genExpr


if __name__ == '__main__':
    pass
    # 1. 将元组列表写入二进制文件:
    records = [
        (1, 2.3, 4.5),
        (6, 7.8, 9.0),
        (12, 13.4, 56.7)
    ]

    with open('data.b', 'wb') as f:
        write_records(records, ', f)

    print('' * 47)
    # 2. 以块的形式增量读取上述二进制文件:
    with open('data.b', 'rb') as f:
        for rec in read_records(', f):
            print(rec)

    print('' * 47)
    # 3. 将整个二进制文件一次性读取到一个字节字符串中, 然后再分片解析:
    with open('data.b', 'rb') as f:
        data = f.read()
    for rec in unpack_records(', data):
        print(rec)

    print('' * 47)
    """
        Struct 实例有很多属性 & 方法用来操作相应类型的结构.
            size 属性包含了结构的字节数;
            pack() & unpack() 方法被用来打包和解包数据.
    """
    record_struct = Struct(')
    print(record_struct.size)
    print(record_struct.pack(1, 2.0, 3.0))
    print(record_struct.unpack(
        b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@'
    ))

    print('' * 47)
    # 4. 上面的 read_records() 函数中, iter() 创建一个迭代器, 返回固定大小的数据块;
    # 且该迭代器会不断调用参数中的匿名函数, 直到它返回特殊值 b'' 时, 迭代停止
    f = open('data.b', 'rb')
    chunks = iter(lambda: f.read(20), b'')
    print(chunks)    # 

    for chk in chunks:
        print(chk)

    print('' * 47)
    # 5. 在解包时, 使用 namedtuple 设置属性名:
    Record = namedtuple('Record', ['kind', 'x', 'y'])
    with open('data.b', 'rb') as f:
        records = (Record(*r) for r in read_records(', f))    # genExpr
        for r in records:
            print(r.kind, r.x, r.y)

    print('' * 47)
    # 6. 若程序需处理大量二进制数据, 最好使用 numpy 模块。
    # 你可以将一个二进制数据读取到一个结构化数组中而不是一个元组列表中:
    f = open('data.b', 'rb')
    # np.fromfile() 函数根据文本 or 二进制文件中的数据构造一个数组
    records = np.fromfile(f, dtype=')    
    print(type(records), records, sep='\n')
    print(records[0], records[1], sep='\n')

    # Remark: 不到万不得已没有必要重复造轮子.

6.12 读取嵌套和可变长二进制数据 — ???

太复杂了看不懂0.0


uvw


abc

6.13 数据的累加与统计操作 — Pandas 库的简介

对任何涉及到统计、时间序列及其他相关技术的数据分析问题,都可考虑使用 Pandas 库

​ 没有书中提到的 csv 文件,就不做代码部分笔记了。

Pandas 中文文档:https://www.pypandas.cn/docs/

你可能感兴趣的:(Python,进阶,个人笔记)