显然,Python字符串并不是天生平等的

时间:2016-08-29 19:24:35

标签: python python-3.x unicode encoding utf-8

我试图将我的大脑包围在文本编码标准中。当将一串字节解释为' text'时,必须知道哪个'编码符号'适用。我所知道的可能的候选人:

  • ASCII:非常基本的编码方案,支持128个字符。
  • CP-1252:拉丁字母的Windows编码方案。也称为' ANSI'。
  • UTF-8:Unicode表的编码方案(1.114.112个字符)。如果可能,用一个字节表示每个字符,如果需要则表示更多字节(最多4个字节)。
  • UTF-16:Unicode表的另一种编码方案(1.114.112个字符)。表示每个字符最少2个字节,最多4个字节。
  • UTF-32:Unicode表的另一种编码方案。表示每个字符有4个字节。
  • 。 。

现在我希望Python始终对其内置的String类型使用一种编码方案。我做了以下测试,结果让我颤抖。我开始相信Python并不是一直坚持使用一种编码方案来在内部存储它的字符串。换句话说:Python Strings似乎并非天生平等......

编辑:

我忘了提到我使用的是Python 3.x.对不起: - )

1。测试

我在文件夹中有两个简单的文本文件:myAnsi.txtmyUtf.txt。您可以猜测,第一个编码采用CP-1252编码方案,也称为ANSI。后者以utf-8编码。在我的测试中,我打开每个文件并读出其内容。我将内容分配给本机Python String变量。然后我关闭文件。之后,我创建一个新文件并将String变量的内容写入该文件。以下是执行所有操作的代码:

    ##############################
    #    TEST ON THE ANSI-coded  #
    #    FILE                    #
    ##############################
    import os
    file = open(os.getcwd() + '\\myAnsi.txt', 'r')
    fileText = file.read()
    file.close()

    file = open(os.getcwd() + '\\outputAnsi.txt', 'w')
    file.write(fileText)
    file.close()

    # A print statement here like:
    #    >> print(fileText)
    # will raise an exception.
    # But if you're typing this code in a python terminal,
    # you can just write:
    #    >> fileText
    # and get the content printed. In my case, it is the exact
    # content of the file.
    # PS: I use the native windows cmd.exe as my Python terminal ;-)

    ##############################
    #    TEST ON THE Utf-coded   #
    #    FILE                    #
    ##############################
    import os
    file = open(os.getcwd() + '\\myUtf.txt', 'r')
    fileText = file.read()
    file.close()

    file = open(os.getcwd() + '\\outputUtf.txt', 'w')
    file.write(fileText)
    file.close()

    # A print statement here like:
    #    >> print(fileText)
    # will just work fine (at least for me).

    ############# END OF TEST #############

2。我期望的结果

让我们假设Python始终坚持一种内部编码方案 - 例如utf-8 - 用于其所有字符串。将其他内容分配给String将导致某种隐式转换。根据这些假设,我希望两个输出文件都是utf-8类型:

    outputAnsi.txt   ->   utf-8 encoded
    outputUtf.txt    ->   utf-8 encoded

第3。我得到的结果

我得到的结果是:

    outputAnsi.txt   ->   CP-1252 encoded (ANSI)
    outputUtf.txt    ->   utf-8 encoded

从这些结果中,我必须得出结论,String变量fileText以某种方式存储它所遵循的编码方案。

许多人在答案中告诉我:

  

如果未明确传递编码,open()将使用首选   系统编码用于读取和写入。

我无法围绕那个陈述包围我的大脑。如果open()使用'首选系统编码' - 以cp1252为例 - 然后两个*.txt输出应该以这种方式编码,不是吗?

4。问题..

我的测试向我提出了几个问题:

(1)当我打开文件来阅读其内容时,Python如何知道该文件的编码方案?打开文件时我没有指定它。

(2)显然Python String可以遵循Python支持的任何编码方案。因此,并非所有Python字符串都是平等的。你如何找出特定String的编码方案,以及如何转换它?或者,如何确保新创建的Python String具有预期的类型?

(3)当我创建文件时,Python如何决定创建文件的编码方案?我在测试中创建这些文件时没有指定编码方案。尽管如此,Python在每种情况下做出了不同的(!)决定。

5。额外信息(基于对此问题的评论):

  • Python版本:Python 3.x(从Anaconda安装)
  • 操作系统:Windows 10
  • 终端:标准Windows命令提示符cmd.exe
  • 关于临时变量fileText提出了一些问题。显然,指令print(fileText)不适用于ANSI情况。抛出异常。但是在python终端窗口中,我只需输入变量名fileText并获取打印出的文件内容。
  • 对文件进行编码检测:首先检查Notepad ++的右下角,进行双重检查的在线工具:https://nlp.fi.muni.cz/projects/chared/
  • 测试开始时不存在输出文件outputAnsi.txtoutputUtf.txt。它们是在我使用open(..)选项发出'w'命令时创建的。

6。实际文件(完整性):

我收到了一些评论,鼓励我分享我正在进行此测试的实际文件。那些文件非常大,所以我把它们修剪下来并重新进行了测试。结果相似。这是文件(PS:当然,我的文件包含源代码,还有什么?):

myAnsi.txt

/*
******************************************************************************
**
**  File        : LinkerScript.ld
**
**  Author      : Auto-generated by Ac6 System Workbench
**
**  Abstract    : Linker script for STM32F746NGHx Device from STM32F7 series
**
**  Target      : STMicroelectronics STM32
**
**  Distribution: The file is distributed “as is,” without any warranty
**                of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>&copy; COPYRIGHT(c) 2014 Ac6</center></h2>
**
*****************************************************************************
*/

/* Entry Point */
/*ENTRY(Reset_Handler)*/
ENTRY(Default_Handler)

/* Highest address of the user mode stack */
_estack = 0x20050000;    /* end of RAM */

_Min_Heap_Size = 0;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Memories definition */
MEMORY
{
  RAM (xrw)     : ORIGIN = 0x20000000, LENGTH = 320K
  ROM (rx)      : ORIGIN = 0x8000000, LENGTH = 1024K
}

fileText变量的print语句导致以下异常:

>>> print(fileText)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "C:\Anaconda3\lib\encodings\cp850.py", line 19, in encode
        return codecs.charmap_encode(input,self.errors,encoding_map)[0]
    UnicodeEncodeError: 'charmap' codec can't encode character '\u201c' in position 357: character maps to <undefined>

但只需键入变量名称即可打印出内容而不会出现问题:

>>> fileText
    ### contents of the file are printed out :-) ###

myUtf.txt

/*--------------------------------------------------------------------------------------------------------------------*/
/*           _ _ _                                                                                                    */
/*          / -,- \                   __  _            _                                                              */
/*         //  |  \\                 / __\ | ___   ___| | __                   _            _                         */
/*         |   0--,|                / /  | |/ _ \ / __| |/ /    __ ___ _ _  __| |_ __ _ _ _| |_ ___                   */
/*         \\     //               / /___| | (_) | (__|   <    / _/ _ \ ' \(_-<  _/ _` | ' \  _(_-<                   */
/*          \_-_-_/                \____/|_|\___/ \___|_|\_\   \__\___/_||_/__/\__\__,_|_||_\__/__/                   */
/*--------------------------------------------------------------------------------------------------------------------*/


#include "clock_constants.h"
#include "../CMSIS/stm32f7xx.h"
#include "stm32f7xx_hal_rcc.h"


/*--------------------------------------------------------------------------------------------------*/
/*          S y s t e m C o r e C l o c k       i n i t i a l        v a l u e                      */
/*--------------------------------------------------------------------------------------------------*/
/*                                                                                                  */
/* This variable is updated in three ways:                                                          */
/*      1) by calling CMSIS function SystemCoreClockUpdate()                                        */
/*      2) by calling HAL API function HAL_RCC_GetHCLKFreq()                                        */
/*      3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency        */
/*          Note: If you use this function to configure the system clock; then there                */
/*                is no need to call the 2 first functions listed above, since SystemCoreClock      */
/*                variable is updated automatically.                                                */
/*                                                                                                  */
uint32_t SystemCoreClock = 16000000;
const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};


/*--------------------------------------------------------------------------------------------------*/
/*          S y s t e m C o r e C l o c k       v a l u e      u p d a t e                          */
/*--------------------------------------------------------------------------------------------------*/
/*                                                                                                  */
/* @brief  Update SystemCoreClock variable according to Clock Register Values.                      */
/*         The SystemCoreClock variable contains the core clock (HCLK), it can                      */
/*         be used by the user application to setup the SysTick timer or configure                  */
/*         other parameters.                                                                        */
/*--------------------------------------------------------------------------------------------------*/

4 个答案:

答案 0 :(得分:3)

CP-1252基本上是字节编解码器的字节;它可以解码任意字节,包括UTF-8编码的字节。如此有效,假设您在Windows上使用西方语言环境,其中提供给open的默认编码是cp-1252,如果您从不使用Python中的字符串,只需读取和写入,您可以以及以二进制模式读取和写入。如果您尝试以暴露问题的方式使用字符串,则只会发现问题。

例如,请考虑以下测试文件中包含单个UTF-8编码字符:

with open('utf8file.txt', 'w', encoding='utf-8') as f:
    f.write('é')

该文件中的实际字节为C3 A9

如果您在cp-1252中读取该文件,它会很乐意这样做,因为每个字节都是合法的cp-1252字节:

with open('utf8file.txt') as f:
    data = f.read()

但它不是字符串'é',而是cp-1252"é"中这两个字节恰好代表的东西(你可以打印它们或检查长度,你会看到,假设您的控制台编码处理非ASCII)

如果你只是回写它而不使用它,你永远不会看到它;输出步骤是(默认)编码"é"C9 A9,它会恢复您期望的原始字节。

你的问题是,大多数文件都是合法的cp-1252文本文件(并且Python可能会默认将未分配的字节作为等效的Unicode序列读取;我知道latin-1对于\x8d这样的未分配字节如{{} 1}}),当它们合法时,如此阅读并以相同的编码回写是非变异的。

答案 1 :(得分:2)

如果没有明确传递编码,open() uses the preferred system encoding用于读取和写入(不确定如何在Windows上检测到首选编码)。

所以,当你写:

file = open(os.getcwd() + '\\myAnsi.txt', 'r')
file = open(os.getcwd() + '\\outputAnsi.txt', 'w')
file = open(os.getcwd() + '\\myUtf.txt', 'r')
file = open(os.getcwd() + '\\outputUtf.txt', 'w')

所有四个文件都使用相同的编码打开,无论是阅读还是写作。

如果您想确保使用这些编码打开文件,则必须通过encoding='cp1252'encoding='utf-8'

file = open(os.getcwd() + '\\myAnsi.txt', 'r', encoding='cp1252')
file = open(os.getcwd() + '\\outputAnsi.txt', 'w', encoding='cp1252')
file = open(os.getcwd() + '\\myUtf.txt', 'r', encoding='utf-8')
file = open(os.getcwd() + '\\outputUtf.txt', 'w', encoding='utf-8')

(作为旁注,我不是Windows专家,但我认为你可以写'myAnsi.txt'代替os.getcwd() + '\\myAnsi.txt'。)

除此之外,您还必须考虑使用不同的编码以相同的方式表示某些字符。例如,字符串hello在ASCII,CP-1252或UTF-8中具有相同的表示形式。通常,您必须使用一些非ASCII字符才能看到一些差异:

>>> 'hello'.encode('cp1252')
b'hello'
>>> 'hello'.encode('utf-8')
b'hello'  # different encoding, same byte representation

不仅如此,一些字节字符串在两种不同的编码中完全有效,即使它们具有不同的含义,因此当您尝试使用错误的编码解码文件时,您不会收到错误,但是一个奇怪的字符串:

>>> b'\xe2\x82\xac'.decode('utf-8')
'€'
>>> b'\xe2\x82\xac'.decode('cp1252')
'€'  # same byte representation, different string

对于记录,Python uses UTF-8, UTF-16 or UTF-32在内部表示字符串。 Python尝试使用“最短”表示,即使使用UTF-8和UTF-16而没有连续字节,因此查找始终为O(1)。

简而言之,您已使用系统编码读取了两个文件,并使用相同的编码编写了两个文件(因此没有任何转换)。您阅读的文件内容与CP-1252和UTF-8兼容。

答案 2 :(得分:1)

要完全掌握答案,我们需要稍微查看一下文档。

让我们从 open()函数开始。根据Python 3. *文档

  

open()返回一个文件对象,最常用的有两个参数:open(filename,mode)。 1

这意味着我们正在处理一个文件对象,它可能意味着原始二进制,缓冲二进制或在这种情况下是文本文件2。但是这个文本文件对象如何知道它的编码?再好,根据文件

  

能够读写str对象的文件对象。通常,文本文件实际上访问面向字节的数据流并自动处理文本编码。3

所以我们拥有它,它会自动处理。因为这两种格式都属于受支持的编解码器。 Python知道如何在给定文件对象的写入时对文件进行编码。

答案 3 :(得分:0)

不幸的是,你不希望这样做。

文件不包含编码信息,因此如果不提供编码或假设编码信息,则无法将其作为文本读取。

在执行像text = open("myfile.txt").read()这样的事情时,世界上没有人可以确定如果文件包含超出ASCII的字符,使用什么编码将文件中包含的字节流转换为unicode点。请注意,甚至可能文件不包含单个编码中的文本(只需使用iso-8859-1utf-8编码的文件连接到另一个编码,而不是0x00

例如,IIRC俄语语言环境可以包含从0xFFUnicodeDecodeError的任何字节,因此任何文件都可以被解释为包含俄语语言环境文本而不会解码错误。

有些图书馆试图通过基于统计的猜测来解决这个问题,但他们只是猜测,而不是肯定地说。

Python 3字符串是unicode,因此在读取文本文件时,它使用&#34;系统默认值&#34;除非明确提供另一个。

如果系统默认值不正确,但可以解码所有包含的8位字节,则会以静默方式获取错误的内容。当写回字符串时,它将再次使用系统默认值,从而在输出中重写相同的字节。

这就是发生在你身上的事。

但是,如果系统默认编码无法解码文件内容,则会出现Città异常。错误检测取决于文件内容和使用的编码。

例如,阅读iso8859-1中编码的utf-8,好像它是à内容(我系统上的默认值),您会在{{{{}}}中收到错误iso8859-1 1}}是0xe0,该字节不能出现在utf-8t)之前的有效0x74文件中。

然而,相反的做法(即在Città中阅读utf-8,就好像它是iso8859-1一样)显然会#34;工作&#34;但会错误地提供文字Città(即大写A在顶部0xc3上有一个波浪号和一个不间断的空格0xA0。)