2016-08-29 34 views
4

我試圖圍繞'文本編碼標準'來包裝我的大腦。當將一組字節解釋爲「文本」時,必須知道哪個「編碼時間片」適用。可能的候選人,我知道的:顯然,Python字符串不是'天生平等'

  • 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對其內置的字符串類型始終使用一種編碼方案。我做了下面的測試,結果讓我發抖。我開始相信Python並不是一貫堅持一種編碼方案來在內部存儲它的字符串。換句話說:Python中似乎是「不生而平等」 ..

編輯:

我忘了提,我使用Python 3.x都有。對不起:-)

1.測試

我有一個文件夾中的兩個簡單的文本文件:myAnsi.txtmyUtf.txt。如您所知,第一種編碼方式編碼方式爲CP-1252,也稱爲ANSI。後者編碼在utf-8。在我的測試中,我打開每個文件並讀出其內容。我將內容分配給本地Python字符串變量。然後關閉文件。之後,我創建一個新文件並將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 

從這些結果中,我必須得出結論:字符串變量fileText以某種方式存儲它所遵守的編碼方案。

很多人告訴我,在他們的答案:

當沒有編碼顯式傳遞,open()使用首選 系統編碼無論是閱讀和寫作。

我只是不能把我的大腦包裹在那句話中。如果open()使用'首選系統編碼' - 比如cp1252作爲例子 - 那麼兩個*.txt輸出應該以這種方式編碼,對嗎?

4.問題..

我的測試提出了幾個問題,對我說:

(1)當我打開一個文件讀取其內容,Python是怎樣知道文件的編碼方案?打開文件時我沒有指定它。 (2)顯然Python字符串可以遵循Python支持的任何編碼方案。所以並非所有的Python字符串天生就是平等的。你如何找出特定字符串的編碼方案,以及如何轉換它?或者你如何確保你剛創建的Python字符串是預期的類型? (3)當我創建一個文件時,Python如何決定文件將被創建的編碼方案?在我的測試中創建這些文件時,我沒有指定編碼方案。儘管如此,Python在每種情況下做出了不同的(!)決定。

5.額外信息(根據意見對這個問題):

  • Python版本:Python的3.X(從巨蟒安裝)
  • 操作系統:Windows 10
  • 終端:標準Windows命令提示符cmd.exe
  • 提出了一些關於臨時變量fileText的問題。顯然,指令print(fileText)不適用於ANSI案例。拋出異常。但在python終端窗口中,我可以簡單地鍵入變量名稱fileText並獲取打印出的文件內容。
  • 編碼文件的檢測:記事本+ +的第一張支票,在線工具,仔細檢查右下角:
  • 輸出文件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.                  */ 
/*--------------------------------------------------------------------------------------------------*/ 
+7

那麼**這些文件中的**數據是什麼?不,'open()'使用默認編碼** always **來打開文件。您沒有指定任何編解碼器,因此數據將被解碼,然後使用相同編解碼器再次對所有文件操作進行編碼。如果您給出了可以使用默認編解碼器解碼的字節,則文件內容無關緊要。 –

+3

例如,CP-1252可以解碼UTF-8。這將是*垃圾*,但你不會注意到,當你所做的一切都是用相同的編解碼器再次編碼垃圾時,你會注意到CP-1252使用 –

+0

我剛測試過,看看我是否垃圾。顯然,在兩種測試情況下,變量'fileText'都不是垃圾。當用一個Python'print(fileText)'語句打印出來時,我會打印出文件的實際內容。 –

回答

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在內部表示字符串。即使使用UTF-8和UTF-16而沒有連續字節,Python也會嘗試使用「最短」表示,因此查找始終爲O(1)。


總之,您已經閱讀使用系統編碼兩個文件,並書面使用相同的編碼兩個文件(因此無需任何轉換)。您讀取的文件內容與CP-1252和UTF-8兼容。

+0

我不明白你的說法:'open()使用首選的系統編碼既用於讀取又用於寫入'。如果我的系統默認是'cp1252',爲什麼Python在'cp1252'中寫入第一個文件而在'utf-8'中寫入第二個文件?在這兩個寫命令中,我沒有指定特定的編碼。所以根據你的陳述,Python會默認使用系統首選項。儘管如此,Python在每種情況下都會做出不同的決定。 –

+0

@ K.Mulier:如果您的系統默認爲cp1252,那麼您的文件正在用cp1252打開。你的utf-8文件被解釋爲一個cp1252文件。正如我在我的例子中所示,一些有效的utf-8字符串的字節字符串也是有效的cp1252字符串 –

+0

我仍然不明白爲什麼我的測試的第一個示例中的print語句引發了異常,而在第二個例如它工作得很好.. –

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未分配字節),而當他們的法律,如此讀取並以相同的編碼寫回是非變異的。

+2

CP-1252是Windows上*文件*的默認編碼,但如果你在終端窗口中運行,你會得到CP-437的'stdout'和'stdin' ...有趣。 –

+0

非常感謝您的有趣答案。只有一件事我不明白。考慮到'cp-1252'確實是Windows上的默認編碼系統,爲什麼我的print語句print(fileText)會崩潰? –

+1

@ K.Mulier:請參閱@ DietrichEpp的評論;看起來像終端默認使用不同的編碼。因此,1252中不存在的字符不能正確輸出。您可以嘗試使用'chcp'來改變終端編碼(在啓動Python之前),例如'chcp 1252',所以文件系統和終端編碼匹配(我現在沒有安裝Windows Python測試)。假定Python動態確定終端編碼;副手,我不知道。 – ShadowRanger

1

要全面掌握答案,我們需要查看一下文檔。

讓我們從open()函數開始。根據Python 3. *文檔

open()返回一個文件對象,並且通常使用兩個參數:open(filename,mode)。 1

這意味着我們正在處理一個文件對象這可能意味着原始二進制,緩衝二進制或在這種情況下,文本文件2。但是這個文本文件對象怎麼知道它是編碼?再次,根據文檔

一個文件對象能夠讀取和寫入str對象。通常,文本文件實際上訪問一個面向字節的數據流並自動處理文本編碼。 3

所以我們有它,它會自動處理。而且這兩種格式都屬於支持的編解碼器。 Python知道如何在給定文件對象的情況下對文件進行編碼。

+0

非常感謝。但是當Python創建一個不存在的文件時,我仍然沒有得到什麼,比如:'file = open(os.getcwd()+'\\ outputAnsi.txt','w')'。文件'outputAnsi.txt'不存在。 Python創建它,沒有任何編碼規範。然而,Python做出了這個決定。 –

+0

這是因爲您正在以「文本模式」打開文件。 python中的open()文檔聲明: _在文本模式下(默認值,或當't'包含在mode參數中時),文件內容以str形式返回,字節首先使用平臺解碼 - 依賴編碼或使用指定的編碼,如果given_ 所以我相信發生了什麼是'fileText'變量具有與它相關的編碼,當python打開一個新文件時,它將該編碼寫入'fileText ' – msleevi

+0

所以變量'fileText'本身有一個'編碼'屬性。更一般地說,Python中的每個字符串都有一個「編碼」屬性。所以我理論上可以問每一個String它的編碼是什麼。我對麼? –

0

你希望的是不幸的是不可能的。

文件不包含編碼信息,因此無法在不提供編碼或假設編碼的情況下將其作爲文本讀取。

當做類似text = open("myfile.txt").read()這樣的事情時,如果文件包含ASCII以外的字符,世界上就沒有人可以確切地說明使用什麼編碼將文件中包含的字節流轉換成unicode點。請注意,甚至有可能一個文件不包含單個編碼中的文本(只是將編碼爲iso-8859-1的編碼與使用utf-8代替的編碼連接起來)。

IIRC俄語語言環境例如可以包含從0x000xFF的任何字節,因此任何文件都可以被解釋爲包含俄語區域設置文本而不會解碼錯誤。

有些圖書館試圖通過基於統計的猜測來解決這個問題,但他們只是猜測,並不能確定。

Python 3字符串是unicode,因此在讀取文本文件時,它會使用「系統默認值」應用解碼,除非明確提供不同的解碼。

如果系統默認不是正確的,但可以解碼包含的所有8位字節,您將只是默默獲取錯誤的內容。當寫回字符串時,它將再次使用系統默認值,從而在輸出中重寫相同的字節。

這是發生在你身上的事。

如果系統默認編碼不能解碼文件內容,您將得到一個UnicodeDecodeError異常。錯誤檢測取決於文件內容和使用的編碼。

例如讀取Cittàiso8859-1編碼就好像它是一個utf-8內容(我的系統默認),你會得到一個錯誤的字符àiso8859-10xe0和有效utf-8文件由t之前該字節可以不存在,是(0x74)。

但是做相反(即讀Città編碼utf-8就好像它是iso8859-1)顯然將「工作」,但會錯誤地給文本Città(即一個大寫A在上面0xc3和非打​​破空間波浪線0xA0)。

+0

我不明白你的說法:'當寫回字符串時,它[Python]將再次使用系統默認值,從而在輸出中重寫相同的字節。如果我的系統默認是'cp1252',爲什麼Python在'cp1252'中寫入第一個文件而在'utf-8'中寫入第二個文件?在這兩個寫命令中,我沒有指定特定的編碼。儘管如此,Python在每種情況下都會做出不同的決定。 –

+0

@ K.Mulier:如果您的默認值是cp1252,那麼它將使用cp1252將這兩個文件寫回。你已經很幸運了,utf-8文件的內容可以被解釋爲合法的cp1252(但是會得到一個亂碼的unicode文本)。當使用cp1252寫回時,這個錯誤的unicode文本恰好在輸出上再次變爲有效的utf-8。我引用了「幸運」,因爲這真是運氣不好(你的數據默默無聞)。 – 6502