您的位置:首页 > 编程语言 > Python开发

Python 文本和字节序列

2017-08-18 14:34 531 查看
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  Python 3 明确区分了人类可读的文本字符串和原始的字节序列。隐式地把字节序列转换成 Unicode 文本已成过去。

  深入理解 Unicode 对你可能十分重要,也可能无关紧要,这取决于Python 编程的场景。说到底,本章涵盖的问题对只处理 ASCII 文本的程序员没有影响。但是即便如此,也不能避而不谈字符串和字节序列的区别。此外,你会发现专门的二进制序列类型所提供的功能,有些是Python 2 中“全功能”的 str 类型不具有的。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }

字符问题

  在 2015 年,“字符”的最佳定义是 Unicode 字符。因此,从 Python 3 的str 对象中获取的元素是 Unicode 字符,这相当于从 Python 2 的unicode 对象中获取的元素,而不是从 Python 2 的 str 对象中获取的原始字节序列。

举个🌰 编码和解码

>>> s = 'café'
>>> len(s)
4
>>> b = s.encode('utf-8')
>>> b
b'caf\xc3\xa9'
>>> len(b)
5
>>> b.decode('utf-8')
'café'


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  如果想帮助自己记住 .decode() 和 .encode() 的区别,可以把字节序列想成晦涩难懂的机器磁芯转储,把 Unicode 字符串想成“人类可读”的文本。那么,把字节序列变成人类可读的文本字符串就是解码,而把字符串变成用于存储或传输的字节序列就是编码。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
字节概要

  新的二进制序列类型在很多方面与 Python 2 的 str 类型不同。首先要知道,Python 内置了两种基本的二进制序列类型:Python 3 引入的不可变bytes 类型和 Python 2.6 添加的可变 bytearray 类型。(Python 2.6 也引入了 bytes 类型,但那只不过是 str 类型的别名,与 Python 3 的bytes 类型不同。)

  bytes 或 bytearray 对象的各个元素是介于 0~255(含)之间的整数,而不像 Python 2 的 str 对象那样是单个的字符。然而,二进制序列的切片始终是同一类型的二进制序列,包括长度为 1 的切片,如示例:

>>> cafe = bytes('café', encoding='utf_8')
>>> cafe
b'caf\xc3\xa9'
>>> cafe[0]
99
>>> cafe[:1]
b'c'
>>> cafe_arr = bytearray(cafe)
>>> cafe_arr
bytearray(b'caf\xc3\xa9')
>>> cafe_arr[-1:]
bytearray(b'\xa9')


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
二进制序列有个类方法是 str 没有的,名为 fromhex,它的作用是解析十六进制数字对(数字对之间的空格是可选的),构建二进制序列:

>>> bytes.fromhex('31 4B CE A9')
b'1K\xce\xa9'
>>> bytes.fromhex('31 4B CE A9').decode('utf-8')
'1KΩ'


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
使用数组中的原始数据初始化 bytes 对象

>>> import array
>>> numbers = array.array('h', [-2, -1, 0, 1, 2])
>>> octets = bytes(numbers)
>>> octets
b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00'


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
结构体和内存视图

  struct 模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的字节序列。struct 模块能处理 bytes、bytearray 和 memoryview 对象。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
使用 memoryview 和 struct 查看一个 GIF 图像的首部

>>> import struct
>>> fmt = '<3s3sHH' # ➊
>>> with open('filter.gif', 'rb') as fp:
... img = memoryview(fp.read()) # ➋
...
>>> header = img[:10] # ➌
>>> bytes(header) # ➍
b'GIF89a+\x02\xe6\x00'
>>> struct.unpack(fmt, header) # ➎
(b'GIF', b'89a', 555, 230)
>>> del header # ➏
>>> del img


结构体的格式:< 是小字节序,3s3s 是两个 3 字节序列,HH 是两个16 位二进制整数

使用内存中的文件内容创建一个memoryview对象

然后使用它的切片在创建一个memoryview对象,这里不会复制字节序列

转换成字节序列,这里只是为了显示,这里复制了是个字节

拆包memoryview对象,得到一个元祖,包含类型、版本、宽度和高度

删除引用,释放memoryview实例所占用的内存

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
处理UnicodeEncodeError

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  多数非 UTF 编解码器只能处理 Unicode 字符的一小部分子集。把文本转换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出UnicodeEncodeError 异常,除非把 errors 参数传给编码方法或函数,对错误进行特殊处理。

举个🌰 编码成字节序列:成功和错误处理

1 city = 'São Paulo'
2
3 #'utf_?' 编码能处理任何字符串
4 u8 = city.encode('utf_8')
5 print('utf-8:', u8)
6
7 u16 = city.encode('utf_16')
8 print('utf-16:', u16)
9
10 #'iso8859_1' 编码也能处理字符串 'São Paulo
11 iso = city.encode('iso8859_1')
12 print('iso:', iso)
13
14 #报错咯,'cp437' 无法编码 'ã'(带波形符的“a”)
15 #city.encode('cp437')
16
17 #解决方法如下
18 cp_ig = city.encode('cp437', errors='ignore')
19 print('cp ignore:', cp_ig)
20
21 cp_rp = city.encode('cp437', errors='replace')
22 print('cp replace:', cp_rp)


以上代码执行的结果为:

utf-8: b'S\xc3\xa3o Paulo'
utf-16: b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00'
iso: b'S\xe3o Paulo'
cp ignore: b'So Paulo'
cp replace: b'S?o Paulo'


注意:

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }

error='ignore' 处理方式悄无声息地跳过无法编码的字符;这样做通常很是不妥

编码时指定error='replace',把无法编码的字符替换成'?';数据损坏了,但是用户知道出现了问题

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
处理文本文件

  处理文本的最佳实践是“Unicode 三明治”(如图下图所示)。 意思是,要尽早把输入(例如读取文件时)的字节序列解码成字符串。这种三明治中的“肉片”是程序的业务逻辑,在这里只能处理字符串对象。在其他处理过程中,一定不能编码或解码。对输出来说,则要尽量晚地把字符串编码成字节序列。多数 Web 框架都是这样做的,使用框架时很少接触字节序列。例如,在 Django 中,视图应该输出 Unicode 字符串;Django 会负责把响应编码成字节序列,而且默认使用 UTF-8 编码。



p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
处理文本文件很简单。但是,如果依赖默认编码,你会遇到麻烦。举个🌰

1 #打开一个文件cafe.txt并写入内容,w是对文件的模式操作(写操作), encoding是对文件操作的编码
2 fp = open('cafe.txt', 'w', encoding='utf_8')
3 fp_len = fp.write('café')
4 print('fp的io信息:', fp)
5 print('写入到文件中内容的长度:', fp_len)
6 fp.close()
7
8 #获取文件的内容
9 fp2 = open('cafe.txt')
10 print('fp2的io信息:', fp2)
11 '''
12 因为和上面的写入的编码不同,所以直接以默认的编码打开,无法处理é而引发异常
13 '''
14 #print(fp2.read())
15 fp2.close()
16
17 #解决fp2无法或许文件内容的方法指定打开的时候编码
18 fp3 = open('cafe.txt', encoding='utf-8')
19 print('fp3的io信息:', fp3)
20 print('fp3中的文件内容:', fp3.read())
21 fp3.close()
22
23 fp4 = open('cafe.txt', 'rb')
24 print('fp4的io信息:', fp4)
25 print('fp4的文件内容:', fp4.read().decode('utf-8'))
26 fp4.close()
27
28 #另外一种不太可取的解决方案, errors可以设置成replace或者ignore
29 fp5 = open('cafe.txt', 'r', errors='ignore')
30 print('fp5的io信息:', fp5)
31 print('fp5的文件内容:', fp5.read())


以上代码执行的结果为:

fp的io信息: <_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
写入到文件中内容的长度: 4
fp2的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='US-ASCII'>
fp3的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf-8'>
fp3中的文件内容: café
fp4的io信息: <_io.BufferedReader name='cafe.txt'>
fp4的文件内容: café
fp5的io信息: <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='US-ASCII'>
fp5的文件内容: caf


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
探索编码默认值

1 import  sys, locale
2
3
4 expressions = """
5     locale.getpreferredencoding()
6     type(my_file)
7     my_file.encoding
8     sys.stdout.isatty()
9     sys.stdout.encoding
10     sys.stdin.isatty()
11     sys.stdin.encoding
12     sys.stderr.isatty()
13     sys.stderr.encoding
14     sys.getdefaultencoding()
15     sys.getfilesystemencoding()
16 """
17
18 with open('dummy', 'w') as my_file:
19     for expression in expressions.split():
20         value = eval(expression)
21         print('{:>30}'.format(expression), '->', repr(value))
22
23 '''
24 locale.getpreferredencoding() 是最重要的设置
25 文本文件默认使用 locale.getpreferredencoding()
26 输出到控制台中,因此 sys.stdout.isatty() 返回 True
27 因此,sys.stdout.encoding 与控制台的编码相同
28 '''


以上代码执行的结果为(终端运行):

ocale.getpreferredencoding() -> 'UTF-8'
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'UTF-8'
sys.stdout.isatty() -> True
sys.stdout.encoding -> 'UTF-8'
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'UTF-8'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'UTF-8'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'utf-8'


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
为了正确比较而规范化Unicode字符串

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  因为 Unicode 有组合字符(变音符号和附加到前一个字符上的记号,打印时作为一个整体),所以字符串比较起来很复杂。

🌰 例如,“café”这个词可以使用两种方式构成,分别有 4 个和 5 个码位,但是结果完全一样:

>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  'é' 和 'e\u0301' 这样的序列叫“标准等价物”(canonical equivalent),应用程序应该把它们视作相同的字符。但是,Python 看到的是不同的码位序列,因此判定二者不相等。

  解决方案是使用 unicodedata.normalize 函数提供的Unicode 规范化。这个函数的第一个参数是这 4 个字符串中的一个:'NFC'、'NFD'、'NFKC' 和 'NFKD'。下面先说明前两个。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  NFC(Normalization Form C)使用最少的码位构成等价的字符串,而NFD 把组合字符分解成基字符和单独的组合字符。这两种规范化方式都能让比较行为符合预期:

1 from unicodedata import normalize
2
3
4 s1 = 'café' # 把"e"和重音符组合在一起
5 s2 = 'cafe\u0301' # 分解成"e"和重音符
6 print('s1和s2的长度:', len(s1), len(s2))
7
8 print('NFC标准化处理以后的s1,s2的长度:', len(normalize('NFC', s1)), len(normalize('NFC', s2)))
9 print('NFD标准化处理以后的s1,s2的长度:', len(normalize('NFD', s1)), len(normalize('NFD', s2)))
10 print(normalize('NFC', s1), normalize('NFC', s2))


以上代码执行的结果为:

s1和s2的长度: 4 5
NFC标准化处理以后的s1,s2的长度: 4 4
NFD标准化处理以后的s1,s2的长度: 5 5
café café


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  在另外两个规范化形式(NFKC 和 NFKD)的首字母缩略词中,字母 K表示“compatibility”(兼容性)。这两种是较严格的规范化形式,对“兼容字符”有影响。虽然 Unicode 的目标是为各个字符提供“规范的”码位,但是为了兼容现有的标准,有些字符会出现多次。例如,虽然希腊字母表中有“μ”这个字母(码位是 U+03BC,GREEK SMALL LETTER MU),但是 Unicode 还是加入了微符号 'μ'(U+00B5),以便与 latin1 相互转换。因此,微符号是一个“兼容字符”。

NFC的具体应用🌰

>>> from unicodedata import normalize
>>> half = '½'
>>> normalize('NFKC', half)
'1⁄2'
>>> four_squared = '4²'
>>> normalize('NFKC', four_squared)
'42'
>>> micro = 'μ'
>>> micro_kc = normalize('NFKC', micro)
>>> micro, micro_kc
('μ', 'μ')
>>> ord(micro), ord(micro_kc)
(956, 956)


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  使用 '1/2' 替代 '½' 可以接受,微符号也确实是小写的希腊字母'μ',但是把 '4²' 转换成 '42' 就改变原意了。某些应用程序可以把'4²' 保存为 '4<sup>2</sup>',但是 normalize 函数对格式一无所知。因此,NFKC 或 NFKD 可能会损失或曲解信息,但是可以为搜索和索引提供便利的中间表述:用户搜索 '1 / 2 inch' 时,如果还能找到包含 '½ inch' 的文档,那么用户会感到满意。

注意:

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  使用 NFKC 和 NFKD 规范化形式时要小心,而且只能在特殊情况中使用,例如搜索和索引,而不能用于持久存储,因为这两种转换会导致数据损失。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
规范化文本匹配实用函数

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  由前文可知,NFC 和 NFD 可以放心使用,而且能合理比较 Unicode 字符串。对大多数应用来说,NFC 是最好的规范化形式。不区分大小写的比较应该使用 str.casefold()。

  如果要处理多语言文本,工具箱中应用nfc_equal 和fold_equal 函数。

🌰 比较规范化 Unicode 字符串

1 from unicodedata import normalize
2
3
4 def nfc_equal(str1, str2):
5     return normalize('NFC', str1) == normalize('NFC', str2)
6
7 def fold_equal(str1, str2):
8     return (normalize('NFC', str1).casefold() ==
9             normalize('NFC', str2).casefold())
10
11 s1 = 'café'
12 s2 = 'cafe\u0301'
13 print('s1 equal s2:',nfc_equal(s1, s2))
14
15 print(nfc_equal('A', 'a'))
16
17 s3 = 'Straße'
18 s4 = 'strasse'
19
20 print('s3 equal s4', nfc_equal(s3, s4))
21 #转换字符成小写
22 print(fold_equal(s3, s4))


以上代码的执行结果为:

s1 equal s2: True
False
s3 equal s4 False
True


极端“规范化”:去掉变音符号

去掉变音符号还能让 URL 更易于阅读,至少对拉丁语系语言是如此。下面是维基百科中介绍圣保罗市(São Paulo)的文章的URL:

http://en.wikipedia.org/wiki/S%C3%A3o_Paulo


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
其中,“%C3%A3”是 UTF-8 编码“ã”字母(带有波形符的“a”)转义后得到的结果。下述形式更友好,尽管拼写是错误的:

http://en.wikipedia.org/wiki/Sao_Paulo


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
如果想把字符串中的所有变音符号都去掉,看 🌰

1 import unicodedata
2
3
4 def shave_marks(txt):
5     """去掉全部变音符号"""
6
7     norm_txt = unicodedata.normalize('NFD', txt)        #把所有字符分解成基字符和组合记号
8     shaved = ''.join(c for c in norm_txt
9                      if not unicodedata.combining(c))   #过滤掉所有组合记号
10     return unicodedata.normalize('NFC', shaved)         #重组所有字符
11
12
13 order = '“Herr Voß: • ½ cup of OEtker™ caffè latte • bowl of açaí.”'
14 print(shave_marks(order))
15
16 Greek = 'Zέφupoς, Zéfiro'
17 print(shave_marks(Greek))


以上代码执行的结果为:

“Herr Voß: • ½ cup of OEtker™ caffe latte • bowl of acai.”
Zεφupoς, Zefiro


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }

Unicode文本排序

  Python 比较任何类型的序列时,会一一比较序列里的各个元素。对字符串来说,比较的是码位。可是在比较非 ASCII 字符时,得到的结果不尽如人意。

🌰 来了~,对一个生长在 🇧🇷 的水果排序

>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted(fruits)
['acerola', 'atemoia', 'açaí', 'caju', 'cajá']


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  不同的区域采用的排序规则有所不同,葡萄牙语等很多语言按照拉丁字母表排序,重音符号和下加符对排序几乎没什么影响。 因此,排序时“cajá”视作“caja”,必定排在“caju”前面。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  排序后的 fruits 列表应该是:

['açaí', 'acerola', 'atemoia', 'cajá', 'caju']


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica; color: #0430f3 }
span.s1 { color: #000000 }
  在 Python 中,非 ASCII 文本的标准排序方式是使用 locale.strxfrm函数,根据 locale 模块的文档(https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm),这 个函数会“把字符串转换成适合所在区域进行比较的形式”。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
   使用 locale.strxfrm 函数之前,必须先为应用设定合适的区域设置,还要祈祷操作系统支持这项设置。在区域设为 pt_BR 的GNU/Linux(Ubuntu 14.04)中,可以使用示例中的命令:

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  使用 locale.strxfrm 函数做排序键

1 import locale
2
3 #设置时区
4 print(locale.setlocale(locale.LC_COLLATE, 'pt_BR.UTF-8'))
5
6 fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
7 fruits_sort = sorted(fruits, key=locale.strxfrm)
8 print('搞定:', fruits_sort)


以上代码的执行结果为:

pt_BR.UTF-8
搞定: ['acerola', 'atemoia', 'açaí', 'caju', 'cajá']


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
使用Unicode排序算法排序

🌰 使用 pyuca.Collator.sort_key 方法

>>> import pyuca
>>> coll = pyuca.Collator()
>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted_fruits = sorted(fruits, key=coll.sort_key)
>>> sorted_fruits
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
支持字符串和字节序列的双模式API

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
  标准库中的一些函数能接受字符串或字节序列为参数,然后根据类型展现不同的行为。re 和 os 模块中就有这样的函数。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
正则表达式中的字符串和字节序列

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
🌰 ramanujan.py:比较简单的字符串正则表达式和字节序列正则表达式的行为

1 import re
2
3
4 re_numbers_str = re.compile(r'\d+')     #编译匹配字符串的数字的正则,连续数字,至少出现一次
5 re_words_str = re.compile(r'\w+')
6 re_numbers_bytes = re.compile(rb'\d+')  #编译匹字节序列配数字的正则,连续数字,至少出现一次
7 re_words_bytes = re.compile(rb'\w+')
8
9 text_str = ("Ramanujan saw \u0be7\u0bed\u0be8\u0bef"
10             " as 1729 = 1³ + 12³ = 9³ + 10³.")
11
12 text_bytes = text_str.encode('utf_8')
13
14 print('Text', repr(text_str), sep='\n ')
15 print('Numbers')
16 print(' str :', re_numbers_str.findall(text_str))
17 print(' bytes:', re_numbers_bytes.findall(text_bytes))
18 print('Words')
19 print(' str :', re_words_str.findall(text_str))
20 print(' bytes:', re_words_bytes.findall(text_bytes))


以上代码执行的结果为:

Text
'Ramanujan saw ௧௭௨௯ as 1729 = 1³ + 12³ = 9³ + 10³.'
Numbers
str : ['௧௭௨௯', '1729', '1', '12', '9', '10']
bytes: [b'1729', b'1', b'12', b'9', b'10']
Words
str : ['Ramanujan', 'saw', '௧௭௨௯', 'as', '1729', '1³', '12³', '9³', '10³']
bytes: [b'Ramanujan', b'saw', b'as', b'1729', b'1', b'12', b'9', b'10']


p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: