首页 新闻 会员 周边 捐助

这个f对象,encoding底层是怎么取值的,这个TextIOWrapper 干啥用的

0
[已解决问题] 解决于 2025-08-31 09:42
with open("a.txt","r") as f:
    print(f)
<_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'> 显示cp936,这个怎么来的
这个f对象,encoding底层是怎么取值的,这个TextIOWrapper 干啥用的
_java_python的主页 _java_python | 小虾三级 | 园豆:984
提问于:2025-08-31 07:53
< >
分享
最佳答案
0

TextIOWrapper 是 “字节流 → 字符流” 的桥梁,位于 Python I/O 栈的最顶层:

磁盘/内核 → 操作系统文件描述符 → io.FileIO (原始字节流)
                                      ↓
                               io.BufferedReader/Writer (带缓冲)
                                      ↓
                               io.TextIOWrapper (编码/解码)
                                      ↓
                              你的代码(拿到的 str)

它主要干三件事:

  1. 解码 / 编码
    读文件时把字节按指定 encoding 解码成 str;写文件时把 str 编码成字节。
  2. 处理换行符
    根据 newline= 参数把 \r\n\r\n 统一成你想要的风格。
  3. 提供高级 API
    readline()readlines()__iter__()write() 等,都是它实现的。
  4. 你看到的 cp936 来自 Windows 的默认 ANSI 代码页;
  5. TextIOWrapper 是 Python 用来“把底层原始文件变成方便操作的 Unicode 文本对象”的封装。

 


为什么打印 f 会看到 cp936


open() 在文本模式("r""w" 等)时,返回的并不是裸文件描述符,而是一个 io.TextIOWrapper 对象。


这个对象的 __repr__ 方法会把自己内部的 name / mode / encoding 打印出来,于是你就看到了:

<_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>


TextIOWrapper 并不凭空发明 一个编码,它按下面顺序找:

 


  1. 你在 open() 里显式写了 encoding= —— 那就用你给的。
  2. 你没写,就用 locale.getpreferredencoding(False) 得到的值。
    这一步发生在 CPython 的 C 源码 _io/textio.c 里,伪代码大致如下
  3. if (encoding == NULL) {
  4.     encoding = PyUnicode_FromString(PyUnicode_GetDefaultEncoding());
  5. }
  6. PyUnicode_GetDefaultEncoding() 最终落到:
  7. return get_codeset(); /* Windows 上调用 GetACP() */
  8. 在简体中文 Windows 上,GetACP() 返回 936,于是 Python 把它翻译成字符串 "cp936"
  9. 如果上面两步都没拿到,再退而求其次用 "utf-8"
  10. 因此:
    • 在简体中文 Windows 的 cmd / PowerShell 里跑脚本 → cp936
    • 在英文 Windows 上 → cp1252
    • 在大多数 Linux 上 → utf-8
    • 你可以自己在 Python 里验证:
      Python
      复制
      import locale
      print(locale.getpreferredencoding(False))
  11. locale.getpreferredencoding(False) 是 Python 中用于获取系统首选编码的函数,其返回值通常与系统的区域设置(locale)相关

 


 


 


用户态视角:一行代码

with open('a.txt', 'r', encoding='utf-8') as f:
    line = f.readline()

 Python 解释器启动后,会依次创建 3 个对象,它们层层包裹,形成一条 “俄罗斯套娃” 式的流水线。

1 最里层:io.FileIO(Raw I/O)

  • 作用:
    直接和操作系统文件描述符(Linux 下是 int fd,Windows 下是 HANDLE)打交道,只认识字节bytes)。
  • 数据形态:
    读出来的是 b'hello\n' 这种原始字节串;写进去也必须喂 bytes
  • 内部调用链(Linux):
    FileIO.read() → C 函数 _io_FileIO_read → 系统调用 read(fd, buf, n) → 内核 VFS → 块设备驱动 → 磁盘控制器。
  • 特点:
    无缓冲,每次 read() 都会触发一次系统调用,性能差;因此 Python 几乎不会直接把它暴露给用户。

2 中间层:BufferedReader / BufferedWriter(缓冲 I/O)

  • 作用:
    给 FileIO 包上一层 内存缓冲,减少系统调用次数;同时支持 peek()read1() 等更灵活的 API。
  • 数据形态:
    仍然是 bytes,只是多了一段用户态内存缓冲。
    例如默认 8192 字节的 bufferread(10) 可能只需要从缓冲区里拷贝 10 字节,而不必到内核。
  • 创建时机:
    你在文本模式 open() 时,Python 会悄悄选择 buffering=-1(默认 8 KB,8192字节)或你手动指定的 buffering,然后自动套一层 BufferedReader/Writer
  • BufferedReader.read() → 先看缓冲区够不够
                          ↓ 不够 → FileIO.readinto(buffer) → 系统调用
                          ↓ 够 → memcpy 给用户

     

3 最外层:TextIOWrapper(文本 I/O)

  • 作用:
    把 Buffered 传来的 字节流 解码为 字符流str),反向则编码。
    顺带处理换行符转换、UNIX/Windows 差异、字符计数、seek/tell 的字符偏移等。
  • 数据形态:
    读:bytesstr(已解码)
    写:strbytes(已编码)
  • 关键字段:
    • .buffer 指向下一层的 Buffered 对象
    • .encoding 保存你显式指定的,或 locale 推断出的编码名
    • .decoder 是 Python 的 codecs.getincrementaldecoder(encoding) 实例
  • 解码流程(读): 
  • TextIOWrapper.readline()
          ↓ 调 BufferedReader.read()
          ↓ 拿到 bytes
          ↓ decoder.decode(bytes) → str
          ↓ 按 newline 规则切出行尾

     

  • 编码流程(写):
  • TextIOWrapper.write(text:str)
          ↓ encoder.encode(text) → bytes
          ↓ BufferedWriter.write(bytes)

     

一张图把三层关系画在一起

Python 代码
      │
      │  "hello世界"
      ▼
┌------------------------------┐
│   TextIOWrapper              │  ← f
│   encoding='utf-8'           │
│   decode/encode + newline    │
└-------------┬----------------┘
              │  bytes
┌-------------┴----------------┐
│   BufferedReader/Writer      │  ← f.buffer
│   8 KB 用户态缓冲             │
└-------------┬----------------┘
              │  bytes
┌-------------┴----------------┐
│   FileIO (RawIOBase)         │  ← f.buffer.raw
│   fd=3 (a.txt)               │
└-------------┬----------------┘
              │  系统调用
         Linux 内核
              │
              ▼
         磁盘控制器

5 如何亲手拆“套娃”验证

>>> f = open('a.txt', 'r', encoding='utf-8')
>>> type(f)
<class '_io.TextIOWrapper'>
>>> type(f.buffer)
<class '_io.BufferedReader'>
>>> type(f.buffer.raw)
<class '_io.FileIO'>
  1. 如果 open 用 "rb" 二进制模式?
    Python 就不会套 TextIOWrapper,直接把 BufferedReader/Writer 给你。
  2. buffering=0 会怎样?
    连 Buffered 层都不要了,直接给你裸 FileIO,性能最差,但控制权最大。
  3. 为什么 seek() 时字符偏移和字节偏移可能不一致?
    TextIOWrapper 内部维护了 字节 ↔ 字符 的映射表,seek 时会做转换。

 

磁盘/内核 → 操作系统文件描述符 → io.FileIO (原始字节流)
                                      ↓
                               io.BufferedReader/Writer (带缓冲)
                                      ↓
                               io.TextIOWrapper (编码/解码)
                                      ↓
                              你的代码(拿到的 str)
_java_python | 小虾三级 |园豆:984 | 2025-08-31 08:06
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册