Python 源码分析(九)

时间:2020-10-22 作者:admin

Python 的源码探索

  1. 简言
  2. _compression 内部类压缩算法
  3. 优化日志

1、简言

Hello
   又见面了,朋友们,人们总是会在满足期待以后就失去它。这次看看压缩算法的逻辑构造,谈不上深入,希望对大家也有帮助。

2、_compression 内部类压缩算法

算法
   _compression 这个 .py 文件有个首行注释:gzip,lzma 和 bz2 模块使用的内部类,该文件有两个类, BaseStream(基本流)和 DecompressReader(解压缩器)。
   ① BaseStream 类:

class BaseStream(io.BufferedIOBase):
    """Mode-checking helper functions."""

    def _check_not_closed(self):
        if self.closed:
            raise ValueError("I/O operation on closed file")
            
    def _check_can_read(self):
        if not self.readable():
            raise io.UnsupportedOperation("File not open for reading")

    def _check_can_write(self):
        if not self.writable():
            raise io.UnsupportedOperation("File not open for writing")

    def _check_can_seek(self):
        if not self.readable():
            raise io.UnsupportedOperation("Seeking is only supported "
                                          "on files open for reading")
        if not self.seekable():
            raise io.UnsupportedOperation("The underlying file object "
                                          "does not support seeking")


   类档注释:模型检查助手功能。
   该类继承 io 的 BufferedIOBase。
   四个方法:检查未关闭的,检查可以打开的,检查可以读写的,检查可以识别的。
   = 检查未关闭的:函数参数是默认的 self,代码块内是一个 if 判断,当出现 self.closed 这个条件,使用 raise 关键字抛出异常信息。异常类型为 ValueError,类型错误。抛出信息为:关闭文件的I / O操作。如果在 python 解释器直接执行该语句,如下:Python 源码分析(九)
   = 检查可以打开的:代码块内是一个 if not 判断,如果没有触发 self.readable() 函数,则使用 raise 语句抛出,抛出信息为【文件未打开以供读取】,测试如下:Python 源码分析(九)
   = 后面两个以此类推,抛出信息为【文件无法打开以进行写入】、【仅在打开以供读取的文件上支持搜索】、【基础文件对象不支持查找】,由此可见,BaseStream 类为(解)压缩对象提供了容错,并处理了可能会出现的异常。
   ② DecompressReader 类:Python 源码分析(九)
   类档注释:使解压缩器 API 适应 RawIOBase 阅读器 API。
   该类继承自 io 的 RawIOBase。
   九个方法:可读的,属性,close 方法,可识别的,读取,read 方法,回溯,seek 方法,返回。
   = 可读的:该方法有且仅有一个返回,return True。

def readable(self):
    return True


   = 属性:定义类的属性,可传入参数有 fp,decomp_factory,trailing_error=(),**decomp_args,定义了八个属性,如下:

def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args):
    self._fp = fp
    self._eof = False
    self._pos = 0  # 减压缩流中的当前偏移
    # 知道解压缩后的流的大小,设置为 SEEK_END
    self._size = -1

    # 保存解压缩器工厂和参数。
    # 如果文件包含多个压缩流,则每个
    # 流将需要一个单独的解压缩器对象,新的解压缩器
    # 实现向后的 seek() 时也需要对象。
    self._decomp_factory = decomp_factory
    self._decomp_args = decomp_args
    self._decompressor = self._decomp_factory(**self._decomp_args)

    # 从解压缩器捕获的异常类表示无效
    # 尾随数据要忽略
    self._trailing_error = trailing_error


   = close 方法:使用 self._decompressor = None 对 _decompressor 对象重新赋值为 None,然后返回 super().close() 方法。

def close(self):
    self._decompressor = None
    return super().close()


   = 可识别的:返回 self._fp.seekable() 方法。

def seekable(self):
    return self._fp.seekable()


   = 读取:使用 with…as… 读取对象,这里用到的技巧是我之前也没见过的,就是【with memoryview(b) as view, view.cast(“B”) as byte_view:】里面,先是读取 memoryview(b) 然后传给 view,然后使用逗号隔开,继续执行 with 方法,读取 view.cast(“B”) 然后传给 byte_view,with 代码块内【data = self.read(len(byte_view))】中,使用 len 方法计算出 byte_view 的文件长度,然后调用 read 方法处理,最后赋值给 data。接着【byte_view[:len(data)] = data】将 byte_view 中直到 len(data) 个元素修改为,也就是重新赋值为 data,最后返回 len(data)。

def readinto(self, b):
    with memoryview(b) as view, view.cast("B") as byte_view:
        data = self.read(len(byte_view))
        byte_view[:len(data)] = data
    return len(data)


   = read 方法:设置默认参数 size=-1,如果该参数小于 0,返回 self.readall(),如果没有 size 或是 self._eof,返回由 b”” 格式化后的字符,如果遇到 EOF,则为默认值,将 data 的值赋值为 None,根据输入数据,我们对解压缩器的调用可能不会返回任何数据,在这种情况下,读取另一个块后再试一次,所以执行 while 语句。

def read(self, size=-1):
    if size < 0:
        return self.readall()

    if not size or self._eof:
        return b""
    data = None  # Default if EOF is encountered
    # Depending on the input data, our call to the decompressor may not
    # return any data. In this case, try again after reading another block.
    while True:
        if self._decompressor.eof:
            rawblock = (self._decompressor.unused_data or
                        self._fp.read(BUFFER_SIZE))
            if not rawblock:
                break
            # Continue to next stream.
            self._decompressor = self._decomp_factory(
                **self._decomp_args)
            try:
                data = self._decompressor.decompress(rawblock, size)
            except self._trailing_error:
                # Trailing data isn't a valid compressed stream; ignore it.
                break
        else:
            if self._decompressor.needs_input:
                rawblock = self._fp.read(BUFFER_SIZE)
                if not rawblock:
                    raise EOFError("Compressed file ended before the "
                                   "end-of-stream marker was reached")
            else:
                rawblock = b""
            data = self._decompressor.decompress(rawblock, size)
        if data:
            break
    if not data:
        self._eof = True
        self._size = self._pos
        return b""
    self._pos += len(data)
    return data


   使用 while True 形成自循环,循环开始,先判断如果 self._decompressor.eof 成立,则【rawblock = (self._decompressor.unused_data or self._fp.read(BUFFER_SIZE))】,这行代码是给 rawblock 赋值很容易看出来,但用的小括号和 or 我也没见过,经过测试,如下:Python 源码分析(九)
   由此可见,只会赋值 or 之前的值,所以该行代码经过 python 解释器执行后,rawblock 的值应该是 _decompressor 的 unused_data,该语句执行后,有一个嵌套在同级的 if not 判断,作为结束 while 循环的条件,条件为 if not rawblock,如果没有执行,就继续执行下一个流,因为结构相对复杂,所以把这个 while 循环单独拿出来看,如下:

while True:
    if self._decompressor.eof:
        rawblock = (self._decompressor.unused_data or
                    self._fp.read(BUFFER_SIZE))
        if not rawblock:
            break
        # 继续下一个流。
        self._decompressor = self._decomp_factory(
            **self._decomp_args)
        try:
            data = self._decompressor.decompress(rawblock, size)
        except self._trailing_error:
            # 尾随数据不是有效的压缩流,忽略它。
            break
    else:
        if self._decompressor.needs_input:
            rawblock = self._fp.read(BUFFER_SIZE)
            if not rawblock:
                raise EOFError("Compressed file ended before the "
                               "end-of-stream marker was reached")    # 压缩文件在到达流结束标记之前结束
        else:
            rawblock = b""
        data = self._decompressor.decompress(rawblock, size)
    if data:
        break


   可以看到,有很多 if 判断,甚至嵌套 if,还有 try-except 结构,为了避免混乱,就一个一个分开理解,首先是 while 循环的触发条件,True,当使用该关键字作为循环执行条件时,可视作条件成立,直接进入循环,直到结束条件触发,这个结束条件在该循环体系中,一共有三个,该循环体系第一层嵌套是 if-else-if,if 成立条件是 self._decompressor.eof,否则执行 else 语句块的代码,如果有 data,则立刻结束该 while 循环。
   = 回溯:将文件倒带到数据流的开头,除了 _fp 调用了 seek(),并传入参数 0,使其回溯,其他三个 _eof、_pos 和 _decompressor 都是用定义属性时的值赋值,达到回溯目的。

# Rewind the file to the beginning of the data stream.
def _rewind(self):
    self._fp.seek(0)
    self._eof = False
    self._pos = 0
    self._decompressor = self._decomp_factory(**self._decomp_args)


   = seek 方法:重新计算偏移量作为绝对文件位置,回溯中 _fp 调用过的 seek 方法,参数有 offset 和 whence=io.SEEK_SET。

def seek(self, offset, whence=io.SEEK_SET):
    # Recalculate offset as an absolute file position.
    if whence == io.SEEK_SET:
        pass
    elif whence == io.SEEK_CUR:
        offset = self._pos + offset
    elif whence == io.SEEK_END:
        # Seeking relative to EOF - we need to know the file's size.
        if self._size < 0:
            while self.read(io.DEFAULT_BUFFER_SIZE):
                pass
        offset = self._size + offset
    else:
        raise ValueError("Invalid value for whence: {}".format(whence))

    # Make it so that offset is the number of bytes to skip forward.
    if offset < self._pos:
        self._rewind()
    else:
        offset -= self._pos

    # Read and discard data until we reach the desired position.
    while offset > 0:
        data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset))
        if not data:
            break
        offset -= len(data)

    return self._pos


   = 返回:返回当前文件位置,方法里面直接使用 return 返回 self._pos,就是当前文件位置。

def tell(self):
    """Return the current file position."""
    return self._pos


   到这里,基本就是 _compression 文件的全部内容了,可以知道解压缩和压缩的算法并不简单,通过层层的迭代和嵌套,把操作的对象一步一步的按逻辑执行,最终得到想要的输出。

3、优化日志

日志
   2020-10-21,第一次发布。
   
   感谢 CSDN,感谢 C 友们的支持

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。