Nacházíte se zde: Domů ‣ Ponořme se do Pythonu 3 ‣
Úroveň obtížnosti: ♦♦♦♦♦
chardet
pro Python 3❝ Words, words. They’re all we have to go on. ❞
(Slova, slova. Jsou vším, čeho se musíme držet.)
— Rosencrantz a Guildenstern jsou mrtvi
Otázka: Co je příčnou č. 1 vedoucí ke zmatenému textu na webu, ve vaší poštovní schránce a ve všech dokumentech, které kdy byly napsány, napříč všemi počítačovými systémy? Je to kódování znaků. V kapitole Řetězce jsme se bavili o historii kódování znaků a o vytvoření Unicode — „jedno kódování vládne všem“. Moc bych si přál, kdybych se na webových stránkách nikdy víc nesetkával se zmatenými znaky, protože by všechny systémy pro vytváření textu ukládaly přesnou informaci o kódování a protože by byly všechny přenosové protokoly připravené na používání Unicode a každý systém pro zpracování textu by při konverzi mezi kódováními zachovával perfektní věrnost.
Rád bych taky poníka.
Unicode poníka.
Kdyby to tak byl Uniponík.
Budu si muset osedlat autodetekci znakového kódování.
⁂
Rozumí se tím to, že vezmeme posloupnost bajtů v neznámém znakovém kódování a pokoušíme se kódování zjistit, abychom si text mohli přečíst. Podobá se to lámání kódu v situaci, kdy nemáme dešifrovací klíč.
Z obecného pohledu to opravdu je nemožné. Ale některá kódování jsou optimalizována pro určité jazyky a jazyky nejsou náhodné. Některé posloupnosti znaků se objevují neustále, zatímco jiné posloupnosti nedávají žádný smysl. Když osoba plynně ovládající angličtinu otevře noviny a najde „txzqJv 2!dasd0a QqdKjvz“, okamžitě pozná, že nejde o angličtinu (i když se text skládá pouze z písmen, která se v angličtině používají). Na základě studia velkého množství „typického“ textu může počítačový algoritmus simulovat zmíněný druh plynné znalosti a může provést kvalifikovaný odhad týkající se jazyka textu.
Jinými slovy, detekce kódování je ve skutečnosti detekcí jazyka, která se kombinuje se znalostí tendence jazyka používat určité znakové kódování.
Jak se ukazuje, tak ano. Všechny nejpoužívanější prohlížeče mají autodetekci kódování zabudovanou, protože web je plný stránek, které neobsahují vůbec žádnou informaci o kódování. Mozilla Firefox obsahuje knihovnu pro autodetekci kódování, která je open source. Knihovnu jsem přenesl do Pythonu 2 a modul jsem nazval chardet
. V této kapitole vás krok za krokem provedu procesem přepisování modulu chardet
z Pythonu 2 pro Python 3.
⁂
chardet
Než se do přepisu kódu pustíme, bylo by dobré, kdybyste rozuměli, jak funguje! Toto je stručná příručka pro usnadnění orientace ve vlastním kódu. Knihovna chardet
je příliš velká na to, abych její kód vložil do textu této knihy. Ale můžete si ji stáhnout z chardet.feedparser.org
.
Hlavním vstupním bodem detekčního algoritmu je universaldetector.py
. Obsahuje jednu třídu, UniversalDetector
. (Možná jste mysleli, že hlavním vstupním bodem je funkce detect
z chardet/__init__.py
. To je ale jen funkce pro zvýšení pohodlí, která vytvoří objekt třídy UniversalDetector
, zavolá jej a vrátí jeho výsledek.)
UniversalDetector
zvládá pět kategorií kódování:
Pokud text začíná značkou BOM, můžeme rozumně předpokládat, že je zakódován v UTF-8, UTF-16 nebo UTF-32. (Značka BOM nám přesně řekne, o které kódování jde. Byla pro tento účel navržena.) To se děje přímo v UniversalDetector
u, který vrátí výsledek okamžitě, bez dalšího zpracovávání textu.
Pokud text obsahuje rozpoznatelné posloupnosti s únikovými znaky (escape sequence), může to být příznakem použití kódování, kterému se v angličtině říká escaped encoding. UniversalDetector
vytvoří EscCharSetProber
(je definován v escprober.py
) a přivede do něj text.
EscCharSetProber
vytvoří sadu konečných automatů, které vycházejí z modelů pro HZ-GB-2312, ISO-2022-CN, ISO-2022-JP a ISO-2022-KR (jsou definovány v escsm.py
). EscCharSetProber
přivádí text do každého z těchto konečných automatů — bajt po bajtu. Pokud některý z konečných automatů skončí s jednoznačnou identifikací kódování, vrátí EscCharSetProber
okamžitě pozitivní výsledek objektu třídy UniversalDetector
, který jej vrátí volajícímu. Pokud kterýkoliv z konečných automatů narazí na nepřípustnou posloupnost, je vyřazen a další zpracování pokračuje jen s ostatními konečnými automaty.
Za předpokladu, že není použita značka BOM, UniversalDetector
zkontroluje, zda text obsahuje nějaké znaky s nastaveným osmým bitem. Pokud tomu tak je, vytvoří sérii „detekčních zařízení“ (prober) pro rozpoznání vícebajtových kódování, jednobajtových kódování a nakonec, jako poslední možnost, pro windows-1252
.
Detekční objekt pro vícebajtová kódování, MBCSGroupProber
(třída je definována v mbcsgroupprober.py
), je ve skutečnosti jen obálkou. Ovládá ostatní detekční objekty, po jednom pro každé vícebajtové kódování: Big5, GB2312, EUC-TW, EUC-KR, EUC-JP, SHIFT_JIS a UTF-8. MBCSGroupProber
směřuje text do každého z těchto specializovaných detekčních objektů a kontroluje výsledky. Pokud nějaký detekční objekt hlásí, že nalezl nepřípustnou posloupnost bajtů, je vyřazen z dalšího zpracování (takže například libovolné následné volání metody UniversalDetector
.feed()
vyřazený detekční objekt přeskočí). Pokud detekční objekt hlásí, že si je poměrně jistý rozpoznáním kódování, oznámí MBCSGroupProber
tento pozitivní výsledek objektu UniversalDetector
, který oznámí výsledek volajícímu.
Většina z detekčních objektů pro vícebajtová kódování je odvozena z MultiByteCharSetProber
(definována v mbcharsetprober.py
) a jednoduše se navěsí na příslušný konečný automat a analyzátor rozložení. Zbytek práce nechá na MultiByteCharSetProber
. MultiByteCharSetProber
prohání text přes konečné automaty specializované na jednotlivá kódování — bajt po bajtu. Vyhledává posloupnosti bajtů, které by indikovaly průkazné pozitivní nebo negativní výsledky. MultiByteCharSetProber
současně posílá text do analyzátoru rozložení, který je specifický pro každé kódování.
Analyzátory rozložení (jsou definovány v chardistribution.py
) používají jazykově specifické modely nejčastěji se vyskytujících znaků. Jakmile MultiByteCharSetProber
předá analyzátorům rozložení dostatečný objem textu, vypočítá ohodnocení spolehlivosti, které je založeno na počtu často používaných znaků, na celkovém počtu znaků a na jazykově závislém rozložení. Pokud je spolehlivost dostatečně velká, vrátí MultiByteCharSetProber
výsledek do MBCSGroupProber
, který jej vrátí do UniversalDetector
u, který jej vrátí volajícímu.
Případ japonštiny je obtížnější. Analýza rozložení podle jednotlivých znaků nevede vždy k rozlišení EUC-JP
a SHIFT_JIS
, takže SJISProber
(definován v sjisprober.py
) používá také dvojznakovou analýzu rozložení. SJISContextAnalysis
a EUCJPContextAnalysis
(definice se v obou případech nacházejí v jpcntx.py
a obě třídy dědí ze společné třídy JapaneseContextAnalysis
) v textu kontrolují frekvenci výskytů slabičných znaků hiragana. Jakmile bylo zpracováno dostatečné množství textu, vracejí úroveň spolehlivosti do SJISProber
, který zkontroluje oba analyzátory a vrátí výsledek s vyšší úrovní spolehlivosti do MBCSGroupProber
.
Detekční objekt pro jednobajtové kódování, SBCSGroupProber
(třída je definována v sbcsgroupprober.py
) je rovněž obálkou, která ovládá skupinu jiných detekčních objektů — jeden pro každou kombinaci jednobajtového kódování a jazyka: windows-1251
, KOI8-R
, ISO-8859-5
, MacCyrillic
, IBM855
a IBM866
(ruština); ISO-8859-7
a windows-1253
(řečtina); ISO-8859-5
a windows-1251
(bulharština); ISO-8859-2
a windows-1250
(čeština, maďarština, slovenština a další); TIS-620
(thajština); windows-1255
a ISO-8859-8
(hebrejština).
SBCSGroupProber
předává text do každého z těchto detekčních objektů a kontroluje výsledky. Všechny tyto detekční objekty jsou implementovány v jedné třídě, SingleByteCharSetProber
(definována v sbcharsetprober.py
), která prostřednictvím argumentu přebírá jazykový model. Jazykový model definuje, jak často se v typickém textu vyskytují dvojznakové posloupnosti. SingleByteCharSetProber
zpracovává text a zjišťuje nejčastěji se vyskytující dvojznakové posloupnosti. Jakmile byl zpracován dostatečný objem textu, vypočítá úroveň spolehlivosti, která je založena na počtu často se vyskytujících posloupností, na celkovém počtu znaků a na jazykově závislém rozložení.
Hebrejština se řeší jako zvláštní případ. Pokud se text na základě analýzy rozložení dvojznakových posloupností jeví jako hebrejština, snaží se HebrewProber
(třída definována v hebrewprober.py
) rozlišit mezi vizuální hebrejštinou (kdy je text uložen ve skutečnosti „pozpátku“ řádek po řádku a poté je zobrazen „normálně“, takže může být čten zprava doleva) a logickou hebrejštinou (kdy je zdrojový text uložen v pořadí čtení a klientský program ho vykresluje zprava doleva). Protože se některé znaky kódují jinak podle toho, zda se nacházejí uprostřed slova nebo na jeho konci, můžeme rozumně odhadnout směr zdrojového textu a vrátit příslušné kódování (windows-1255
pro logickou hebrejštinu nebo ISO-8859-8
pro vizuální hebrejštinu).
windows-1252
Pokud UniversalDetector
v textu detekuje znaky s nastaveným osmým bitem a žádný z vícebajtových nebo jednobajtových detekčních objektů nevrátil spolehlivý výsledek, vytvoří Latin1Prober
(třída je definována v latin1prober.py
) a snaží se detekovat anglický text v kódování windows-1252
. Tato detekce je ze své podstaty nespolehlivá, protože anglické znaky se kódují stejným způsobem v mnoha různých kódováních. Jediný způsob, jak lze kódování windows-1252
rozpoznat, je založen na běžně používaných symbolech, jako jsou střídavé uvozovky (smart quotes; knižní, jiný znak na začátku a jiný na konci), kulaté apostrofy, symbol copyright a podobně. Latin1Prober
automaticky redukuje ohodnocení své spolehlivosti, aby umožnil přesnějším detektorům vyhrát, pokud je to vůbec možné.
⁂
2to3
Jsme připraveni k přenesení modulu chardet
z Pythonu 2 do Pythonu 3. Python 3 se dodává s pomocným skriptem nazvaným 2to3
, který jako vstup přebírá zdrojový kód napsaný pro Python 2 a automaticky převádí vše, co dovede, do podoby pro Python 3. V některých případech je to snadné — funkce se přejmenovala nebo se přesunula do jiného modulu —, ale v ostatních případech to může být docela složité. Abyste získali představu, co vše umí převést, podívejte se na přílohu Přepis kódu do Python 3 s využitím 2to3
. V této kapitole začneme spuštěním 2to3
pro balík chardet
. Ale jak brzy uvidíte, po provedení kouzel automatickými nástroji nám zbude ještě spousta práce.
Hlavní balík chardet
je rozdělen do několika různých souborů. Všechny se nacházejí ve stejném adresáři. Skript 2to3
převod více souborů najednou usnadňuje. Jako argument na příkazovém řádku stačí předat jméno adresáře a 2to3
převede každý ze souborů, které se v něm nacházejí.
C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w chardet\ RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- chardet\__init__.py (original) +++ chardet\__init__.py (refactored) @@ -18,7 +18,7 @@ __version__ = "1.0.1" def detect(aBuf):- import universaldetector+ from . import universaldetector u = universaldetector.UniversalDetector() u.reset() u.feed(aBuf) --- chardet\big5prober.py (original) +++ chardet\big5prober.py (refactored) @@ -25,10 +25,10 @@ # 02110-1301 USA ######################### END LICENSE BLOCK #########################-from mbcharsetprober import MultiByteCharSetProber-from codingstatemachine import CodingStateMachine-from chardistribution import Big5DistributionAnalysis-from mbcssm import Big5SMModel+from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import Big5SMModel class Big5Prober(MultiByteCharSetProber): def __init__(self): --- chardet\chardistribution.py (original) +++ chardet\chardistribution.py (refactored) @@ -25,12 +25,12 @@ # 02110-1301 USA ######################### END LICENSE BLOCK #########################-import constants-from euctwfreq import EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, EUCTW_TYPICAL_DISTRIBUTION_RATIO-from euckrfreq import EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, EUCKR_TYPICAL_DISTRIBUTION_RATIO-from gb2312freq import GB2312CharToFreqOrder, GB2312_TABLE_SIZE, GB2312_TYPICAL_DISTRIBUTION_RATIO-from big5freq import Big5CharToFreqOrder, BIG5_TABLE_SIZE, BIG5_TYPICAL_DISTRIBUTION_RATIO-from jisfreq import JISCharToFreqOrder, JIS_TABLE_SIZE, JIS_TYPICAL_DISTRIBUTION_RATIO+from . import constants +from .euctwfreq import EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, EUCTW_TYPICAL_DISTRIBUTION_RATIO +from .euckrfreq import EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, EUCKR_TYPICAL_DISTRIBUTION_RATIO +from .gb2312freq import GB2312CharToFreqOrder, GB2312_TABLE_SIZE, GB2312_TYPICAL_DISTRIBUTION_RATIO +from .big5freq import Big5CharToFreqOrder, BIG5_TABLE_SIZE, BIG5_TYPICAL_DISTRIBUTION_RATIO +from .jisfreq import JISCharToFreqOrder, JIS_TABLE_SIZE, JIS_TYPICAL_DISTRIBUTION_RATIO ENOUGH_DATA_THRESHOLD = 1024 SURE_YES = 0.99 . . . (takto to chvíli pokračuje) . . RefactoringTool: Files that were modified: RefactoringTool: chardet\__init__.py RefactoringTool: chardet\big5prober.py RefactoringTool: chardet\chardistribution.py RefactoringTool: chardet\charsetgroupprober.py RefactoringTool: chardet\codingstatemachine.py RefactoringTool: chardet\constants.py RefactoringTool: chardet\escprober.py RefactoringTool: chardet\escsm.py RefactoringTool: chardet\eucjpprober.py RefactoringTool: chardet\euckrprober.py RefactoringTool: chardet\euctwprober.py RefactoringTool: chardet\gb2312prober.py RefactoringTool: chardet\hebrewprober.py RefactoringTool: chardet\jpcntx.py RefactoringTool: chardet\langbulgarianmodel.py RefactoringTool: chardet\langcyrillicmodel.py RefactoringTool: chardet\langgreekmodel.py RefactoringTool: chardet\langhebrewmodel.py RefactoringTool: chardet\langhungarianmodel.py RefactoringTool: chardet\langthaimodel.py RefactoringTool: chardet\latin1prober.py RefactoringTool: chardet\mbcharsetprober.py RefactoringTool: chardet\mbcsgroupprober.py RefactoringTool: chardet\mbcssm.py RefactoringTool: chardet\sbcharsetprober.py RefactoringTool: chardet\sbcsgroupprober.py RefactoringTool: chardet\sjisprober.py RefactoringTool: chardet\universaldetector.py RefactoringTool: chardet\utf8prober.py
Teď spustíme skript 2to3
na testovací skript test.py
.
C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w test.py RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- test.py (original) +++ test.py (refactored) @@ -4,7 +4,7 @@ count = 0 u = UniversalDetector() for f in glob.glob(sys.argv[1]):- print f.ljust(60),+ print(f.ljust(60), end=' ') u.reset() for line in file(f, 'rb'): u.feed(line) @@ -12,8 +12,8 @@ u.close() result = u.result if result['encoding']:- print result['encoding'], 'with confidence', result['confidence']+ print(result['encoding'], 'with confidence', result['confidence']) else:- print '******** no result'+ print('******** no result') count += 1-print count, 'tests'+print(count, 'tests') RefactoringTool: Files that were modified: RefactoringTool: test.py
No vida. Nebylo to tak hrozné. Konvertovalo se jen pár importů a příkazů print. Když už o tom mluvíme, jaký byl problém se všemi těmi příkazy import? Abychom na to mohli odpovědět, musíme rozumět tomu, jak se modul chardet
dělí na více souborů.
⁂
chardet
je vícesouborový modul. Mohl jsem se rozhodnout, že veškerý kód uložím do jednoho souboru (pojmenovaného chardet.py
), ale neudělal jsem to. Místo toho jsem vytvořil adresář (pojmenovaný chardet
) a v něm jsem vytvořil soubor __init__.py
. Pokud Python najde v adresáři soubor __init__.py
, předpokládá, že všechny ostatní soubory ve stejném adresáři jsou součástí stejného modulu. Jméno adresáře je jménem modulu. Soubory v adresáři se mohou odkazovat na ostatní soubory ve stejném adresáři nebo dokonce v jeho podadresářích. (Více si o tom řekneme za minutku.) Ale celá kolekce souborů se okolnímu pythonovskému kódu jeví jako jediný modul — jako kdyby všechny funkce a třídy byly definovány v jediném souboru s příponou .py
.
A co je vlastně v souboru__init__.py
? Nic. Všechno. Něco mezi tím. Soubor __init__.py
nemusí definovat vůbec nic. Může to být doslova prázdný soubor. Nebo jej můžeme použít k definici funkcí, které jsou našimi hlavními vstupními body. Nebo do něj můžeme umístit všechny naše funkce. Podstatná je jediná věc.
☞Adresář se souborem
__init__.py
se vždy považuje za vícesouborový modul. Pokud v adresáři není umístěn soubor__init__.py
, považuje se prostě za adresář, který nemá k souborům s příponou.py
žádný vztah.
Podívejme se, jak to funguje v praxi.
>>> import chardet >>> dir(chardet) ① ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__version__', 'detect'] >>> chardet ② <module 'chardet' from 'C:\Python31\lib\site-packages\chardet\__init__.py'>
chardet
jedinou věc a tou je funkce detect()
.
chardet
je víc než jen obyčejným souborem: u slova „module“ se ve výpisu objevuje soubor __init__.py
umístěný v adresáři chardet/
.
Nahlédněme do souboru __init__.py
.
def detect(aBuf): ①
from . import universaldetector ②
u = universaldetector.UniversalDetector()
u.reset()
u.feed(aBuf)
u.close()
return u.result
__init__.py
je definována funkce detect()
, která je hlavním bodem knihovny chardet
.
detect()
neobsahuje skoro žádný kód! Ve skutečnosti pouze importuje modul universaldetector
a začíná jej používat. Ale kde je definován universaldetector
?
Odpověď je skryta v tomto divně vypadajícím příkazu import
:
from . import universaldetector
V překladu do češtiny to znamená „importuj modul universaldetector
, který je umístěn ve stejném adresáři, jako já“. Tím „já“ se myslí soubor chardet/__init__.py
. Říká se tomu relativní import. Představuje způsob, jakým se mohou soubory ve vícesouborovém modulu na sebe vzájemně odkazovat, aniž by se musely starat o konflikty jmen s jinými moduly, které můžeme mít nainstalované v naší vyhledávací cestě pro import. Uvedený příkaz import
bude modul universaldetector
hledat pouze uvnitř adresáře chardet/
.
Zmíněné dva koncepty — __init__.py
a relativní importy — znamenají, že náš modul můžeme rozbít na tolik kousků, kolik si přejeme. Modul chardet
se skládá z 36 souborů s příponou .py
— z 36! A přitom vše, co musíme udělat, když jej chceme začít používat, je import chardet
. Pak můžeme zavolat hlavní funkci chardet.detect()
. Aniž o tom náš kód ví, funkce detect()
je ve skutečnosti definována v souboru chardet/__init__.py
. A aniž o tom musíme vědět my, funkce detect()
používá k odkazu na třídu definovanou uvnitř chardet/universaldetector.py
mechanismus relativního importu, který zase používá relativní import pěti dalších souborů, které se rovněž nacházejí v adresáři chardet/
.
☞Kdykoliv se přistihnete, že v Pythonu píšete rozsáhlou knihovnu (nebo, což je pravděpodobnější, když zjistíte, že se vaše malá knihovna rozrostla ve velkou), udělejte si čas na refaktorizaci a změňte ji na vícesouborový modul. Je to jedna z mnoha věcí, ve kterých je Python dobrý. Takže té výhody využijte.
⁂
2to3
neumíFalse
je syntaktická chybaTeď zkusíme skutečný test. Spustíme testovací sadu (test suite) na zkušební skript (test harness). Protože je testovací sada navržena tak, aby pokryla všechny možné cesty, kudy se běh programu může kódem ubírat, jde o dobrý způsob, jak ověřit, že v našem přeneseném kódu někde nejsou skryté chyby.
C:\home\chardet> python test.py tests\*\* Traceback (most recent call last): File "test.py", line 1, in <module> from chardet.universaldetector import UniversalDetector File "C:\home\chardet\chardet\universaldetector.py", line 51 self.done = constants.False ^ SyntaxError: invalid syntax
Hmm, to je jen drobnost. V Pythonu 3 je False
vyhrazeným slovem, takže je nemůžeme použít jako jméno proměnné. Podíváme se do constants.py
na to, kde je proměnná definována. Tady máme původní verzi z constants.py
předtím, než ji skript 2to3
změnil:
import __builtin__
if not hasattr(__builtin__, 'False'):
False = 0
True = 1
else:
False = __builtin__.False
True = __builtin__.True
Tento kus kódu byl navržen, aby knihovna běžela ve starších verzích Pythonu 2. Před Pythonem 2.3 neexistoval zabudovaný typ bool
. Uvedený kód detekuje nepřítomnost zabudovaných konstant True
a False
a v případě potřeby je definuje.
Ale v Pythonu 3 je typ bool
přítomen vždy, takže je celý úryvek kódu zbytečný. Nejjednodušší řešení spočívá v nahrazení všech výskytů constants.True
a constants.False
hodnotami True
a False
. Pak z constants.py
odstraníme onen mrtvý kód.
Takže následující řádek v universaldetector.py
self.done = constants.False
se změní na
self.done = False
Ách, nebylo to uspokojující? Kód je teď kratší a čitelnější.
constants
Nastal čas spustit znovu test.py
. Uvidíme, jak daleko se dostaneme.
C:\home\chardet> python test.py tests\*\* Traceback (most recent call last): File "test.py", line 1, in <module> from chardet.universaldetector import UniversalDetector File "C:\home\chardet\chardet\universaldetector.py", line 29, in <module> import constants, sys ImportError: No module named constants
Co to říká? Jaképak „No module named constants
“ (doslova „žádný modul jménem constants
“)? Modul constants
tam samozřejmě je! Je přímo tady v chardet/constants.py
.
Vzpomínáte si, jak skript 2to3
opravil všechny ty příkazy import? Tato knihovna používá množství relativních importů — moduly, které importují jiné moduly nacházející se uvnitř stejné knihovny —, ale v Pythonu 3 se změnila logika relativních importů. V Pythonu 2 jsme mohli jednoduše provést import constants
a Python by nejdříve prohledával adresář chardet/
. V Pythonu 3 jsou všechny příkazy import absolutní. Pokud chceme v Pythonu 3 provést relativní import, musíme to říct explicitně:
from . import constants
No moment. Neměl se o tohle postarat skript 2to3
za nás? No, on to udělal. Ale tento konkrétní příkaz import kombinoval dva typy importu na jednom řádku: relativní import modulu constants
, který se nachází uvnitř knihovny, a absolutní import modulu sys
, který je předinstalován jako součást pythonovské standardní knihovny. V Pythonu 2 jsme je mohli zkombinovat do jednoho řádku příkazu import. V Pythonu 3 to nejde a skript 2to3
není dost chytrý na to, aby příkaz import rozdělil na dva.
Řešení spočívá v ručním rozdělení příkazu import. Takže tento import „dva v jednom“…
import constants, sys
… musíme změnit na dva oddělené importy:
from . import constants
import sys
Variace tohoto problému jsou rozesety po celé knihovně chardet
. Na některých místech je to „import constants, sys
“, jinde je to „import constants, re
“. Oprava je stejná. Ručně rozdělíme příkaz import na dva řádky. Na jednom uvedeme relativní import, na druhém absolutní import.
Kupředu!
A zase jdeme na to. Spouštíme test.py
, abychom provedli naše testovací případy…
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 9, in <module> for line in file(f, 'rb'): NameError: name 'file' is not defined
Tak tohle mě překvapilo, protože tento obrat jsem používal, co mi paměť sahá. V Pythonu 2 byla globální funkce file()
jiným jménem (alias) pro funkci open()
, která představovala standardní způsob otvírání textových souborů pro čtení. V Pythonu 3 už globální funkce file()
neexistuje, ale funkce open()
je tu nadále.
Takže nejjednodušší řešení problému chybějící funkce file()
spočívá v jejím nahrazení voláním funkce open()
:
for line in open(f, 'rb'):
A to je vše, co o tom můžu říct.
Teď se začnou dít zajímavé věci. Slůvkem „zajímavé“ rozumím „pekelně matoucí“.
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 98, in feed if self._highBitDetector.search(aBuf): TypeError: can't use a string pattern on a bytes-like object
Abychom to odladili, podívejme se, co je self._highBitDetector. Je to definováno v metodě __init__ třídy UniversalDetector:
class UniversalDetector:
def __init__(self):
self._highBitDetector = re.compile(r'[\x80-\xFF]')
Jde o předkompilovaný regulární výraz, který má hledat znaky mimo ASCII, tj. v rozsahu 128–255 (0x80–0xFF). Počkat, tohle není úplně správně. Musíme použít přesnější terminologii. Tento vzorek je navržen pro hledání bajtů s hodnotou mimo ASCII, tedy v rozsahu 128–255.
A v tom je ten problém.
V Pythonu 2 byl řetězec polem bajtů. Jeho kódování znaků bylo zachyceno odděleně. Pokud jsme po Pythonu 2 chtěli, aby znakové kódování udržoval u řetězce, museli jsme použít Unicode řetězec (u''
). Ale v Pythonu 3 je řetězec vždy tím, co Python 2 nazýval Unicode řetězec — to znamená polem Unicode znaků (které mohou být vyjádřeny různým počtem bajtů). A protože je tento regulární výraz definován řetězcovým vzorkem, může být použit jen pro prohledávání řetězců, což je pole znaků. Ale my nechceme prohledávat řetězec. Prohledáváme pole bajtů. Pohledem na trasovací výpis zjistíme, že k chybě došlo v universaldetector.py
:
def feed(self, aBuf):
.
.
.
if self._mInputState == ePureAscii:
if self._highBitDetector.search(aBuf):
A co je to aBuf? Podívejme se ještě o kousek zpět, na místo, kde se volá UniversalDetector.feed()
. Jedno z míst, kde se volá, se nachází v testovacím kódu (test harness) test.py
.
u = UniversalDetector()
.
.
.
for line in open(f, 'rb'):
u.feed(line)
A tady máme odpověď: aBuf je řádek načítaný v metodě UniversalDetector.feed()
ze souboru na disku. Podívejte se pořádně na parametry, které se používají při otvírání souboru: 'rb'
. 'r'
znamená „read“ (čtení). No dobrá, to je toho. Čteme ze souboru. No jo! 'b'
znamená „binárně“. Bez příznaku 'b'
by cyklus for
četl soubor po řádcích a každý řádek by převáděl na řetězec — tedy na pole Unicode znaků — s využitím systémového výchozího znakového kódování. Ale s příznakem 'b'
čte cyklus for
ze souboru po řádcích a každý řádek ukládá do pole bajtů přesně v takovém tvaru, v jakém se nachází v souboru. Výsledné pole bajtů se předává do UniversalDetector.feed()
a nakonec se dostane až k předkompilovanému regulárnímu výrazu self._highBitDetector, aby se našly osmibitové… znaky. Ale my nemáme znaky. My máme bajty. A do prčic.
Potřebujeme, aby tento regulární výraz nehledal v poli znaků, ale v poli bajtů.
Když už jsme na to přišli, bude náprava jednoduchá. Regulární výrazy definované řetězci mohou hledat v řetězcích. Regulární výrazy definované poli bajtů mohou hledat v polích bajtů. Abychom definovali vzorek polem bajtů, jednoduše změníme typ argumentu, který používáme pro definici regulárního výrazu, na pole bajtů. (Hned na následujícím řádku je další případ téhož problému.)
class UniversalDetector:
def __init__(self):
- self._highBitDetector = re.compile(r'[\x80-\xFF]')
- self._escDetector = re.compile(r'(\033|~{)')
+ self._highBitDetector = re.compile(b'[\x80-\xFF]')
+ self._escDetector = re.compile(b'(\033|~{)')
self._mEscCharSetProber = None
self._mCharSetProbers = []
self.reset()
Když necháme ve všech zdrojových textech vyhledat použití modulu re
, objevíme další dva případy v charsetprober.py
. Jde opět o případy, kdy jsou regulární výrazy definovány jako řetězce, ale používáme je pro aBuf, což je pole bajtů. Řešení je stejné: definujeme vzorky regulárních výrazů jako pole bajtů.
class CharSetProber:
.
.
.
def filter_high_bit_only(self, aBuf):
- aBuf = re.sub(r'([\x00-\x7F])+', ' ', aBuf)
+ aBuf = re.sub(b'([\x00-\x7F])+', b' ', aBuf)
return aBuf
def filter_without_english_letters(self, aBuf):
- aBuf = re.sub(r'([A-Za-z])+', ' ', aBuf)
+ aBuf = re.sub(b'([A-Za-z])+', b' ', aBuf)
return aBuf
'bytes'
nelze implicitně převést na str
Divoucnější a divoucnější…
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 100, in feed elif (self._mInputState == ePureAscii) and self._escDetector.search(self._mLastChar + aBuf): TypeError: Can't convert 'bytes' object to str implicitly
Zde dochází k nešťastné kolizi mezi stylem zápisu zdrojového textu a interpretem Pythonu. Chyba TypeError
se může vázat na kteroukoliv část řádku, ale trasovací výpis nám neříká, kde přesně je. Může to být v první nebo v druhé části podmínky, ale z trasovacího výpisu se to nepozná. Abychom prostor pro hledání zúžili, měli bychom řádek rozdělit:
elif (self._mInputState == ePureAscii) and \
self._escDetector.search(self._mLastChar + aBuf):
A znovu spustíme test:
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 101, in feed self._escDetector.search(self._mLastChar + aBuf): TypeError: Can't convert 'bytes' object to str implicitly
Aha! Problém se nevyskytoval v první části podmínky (self._mInputState == ePureAscii
), ale v druhé. Takže co zde vlastně způsobuje chybu TypeError
? Možná si myslíte, že metoda search()
očekává hodnotu odlišného typu. To by ale nevygenerovalo takový trasovací výpis. Pythonovské funkce mohou přebírat libovolné hodnoty. Pokud předáme správný počet argumentů, funkce se provede. Pokud bychom předali hodnotu jiného typu, než funkce očekává, mohla by havarovat. Ale pokud by se tak stalo, trasovací výpis by ukazoval na místo někde uvnitř funkce. Jenže tento trasovací výpis říká, že se nikdy nedošlo tak daleko, aby se metoda search()
zavolala. Takže problém musí být skryt v operaci +
, protože ta se snaží o zkonstruování hodnoty, která bude nakonec předána metodě search()
.
Z předchozího ladění víme, že aBuf je polem bajtů. A co je tedy self._mLastChar
? Jde o členskou proměnnou definovanou v metodě reset()
, která je ve skutečnosti volána z metody __init__()
.
class UniversalDetector:
def __init__(self):
self._highBitDetector = re.compile(b'[\x80-\xFF]')
self._escDetector = re.compile(b'(\033|~{)')
self._mEscCharSetProber = None
self._mCharSetProbers = []
self.reset()
def reset(self):
self.result = {'encoding': None, 'confidence': 0.0}
self.done = False
self._mStart = True
self._mGotData = False
self._mInputState = ePureAscii
self._mLastChar = ''
A tady máme odpověď. Vidíte to? self._mLastChar je řetězec, ale aBuf je pole bajtů. Konkatenaci (zřetězení, spojení) nelze provádět pro řetězec a pole bajtů — ani když jde o řetězec nulové délky.
No dobrá, ale k čemu je tedy self._mLastChar? V metodě feed()
, jen pár řádků pod místem označeným v trasovacím výpisu, vidíme…
if self._mInputState == ePureAscii:
if self._highBitDetector.search(aBuf):
self._mInputState = eHighbyte
elif (self._mInputState == ePureAscii) and \
self._escDetector.search(self._mLastChar + aBuf):
self._mInputState = eEscAscii
self._mLastChar = aBuf[-1]
Volající funkce volá metodu feed()
pořád dokola s tím, že jí pokaždé předá pár bajtů. Metoda zpracuje zadané bajty (dostává je v aBuf) a potom uloží poslední bajt do self._mLastChar pro případ, že by jej potřebovala při dalším volání. (Při použití vícebajtového kódování by metoda feed()
mohla být zavolána pro polovinu znaku a pak by mohla být volána pro jeho druhou polovinu.) Ale protože je teď aBuf místo řetězce polem bajtů, musíme udělat pole bajtů i z self._mLastChar. Takže:
def reset(self):
.
.
.
- self._mLastChar = ''
+ self._mLastChar = b''
Když ve všech zdrojových souborech vyhledáme „mLastChar
“, najdeme podobný problém v mbcharsetprober.py
. Ale místo uchovávání posledního znaku se uchovávají poslední dva znaky. Třída MultiByteCharSetProber
používá k uchovávání posledních dvou znaků seznam jednoznakových řetězců. V Pythonu 3 musíme použít seznam celých čísel, protože ve skutečnosti neuchováváme znaky, ale bajty. (Bajty jsou prostě celá čísla v intervalu 0‒255
.)
class MultiByteCharSetProber(CharSetProber):
def __init__(self):
CharSetProber.__init__(self)
self._mDistributionAnalyzer = None
self._mCodingSM = None
- self._mLastChar = ['\x00', '\x00']
+ self._mLastChar = [0, 0]
def reset(self):
CharSetProber.reset(self)
if self._mCodingSM:
self._mCodingSM.reset()
if self._mDistributionAnalyzer:
self._mDistributionAnalyzer.reset()
- self._mLastChar = ['\x00', '\x00']
+ self._mLastChar = [0, 0]
'int'
a 'bytes'
Mám jednu dobrou a jednu špatnou zprávu. Ta dobrá je, že děláme pokroky…
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 101, in feed self._escDetector.search(self._mLastChar + aBuf): TypeError: unsupported operand type(s) for +: 'int' and 'bytes'
… Ta špatná je, že to někdy tak nevypadá.
Ale on to je pokrok! Opravdu! I když trasovací výpis označuje stejný řádek kódu, je to jiná chyba, než se hlásila dříve. Pokrok! Takže kdepak máme problém teď? Když jsme to kontrolovali minule, nesnažil se tento řádek řetězit int
s polem bajtů (bytes
). Ve skutečnosti jsme strávili dost času tím, abychom zajistili, že self._mLastChar bude pole bajtů. Jak se mohlo změnit na int
?
Odpověď není skrytá v předchozích řádcích kódu, ale v následujících.
if self._mInputState == ePureAscii:
if self._highBitDetector.search(aBuf):
self._mInputState = eHighbyte
elif (self._mInputState == ePureAscii) and \
self._escDetector.search(self._mLastChar + aBuf):
self._mInputState = eEscAscii
self._mLastChar = aBuf[-1]
Tato chyba se nevyskytne při prvním volání metody feed()
. Vyskytne se při druhém volání poté, co byl proměnné self._mLastChar přiřazen poslední bajt aBuf. No a v čem je tedy problém? Když z bajtového pole získáme jeden prvek, dostaneme celé číslo a ne bajtové pole. Abychom ten rozdíl viděli, ukážeme si to v interaktivním shellu:
>>> aBuf = b'\xEF\xBB\xBF' ① >>> len(aBuf) 3 >>> mLastChar = aBuf[-1] >>> mLastChar ② 191 >>> type(mLastChar) ③ <class 'int'> >>> mLastChar + aBuf ④ Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'bytes' >>> mLastChar = aBuf[-1:] ⑤ >>> mLastChar b'\xbf' >>> mLastChar + aBuf ⑥ b'\xbf\xef\xbb\xbf'
universaldetector.py
.
Takže abychom zajistili, že bude metoda feed()
v universaldetector.py
pokračovat v činnosti nezávisle na tom, jak často je volána, musíme inicializovat self._mLastChar polem bajtů o nulové délce a potom musíme zajistit, aby tato proměnná zůstala polem bajtů.
self._escDetector.search(self._mLastChar + aBuf):
self._mInputState = eEscAscii
- self._mLastChar = aBuf[-1]
+ self._mLastChar = aBuf[-1:]
ord()
očekávala řetězec o délce 1, ale byl nalezen int
Jste už unaveni? Už to máme skoro hotové…
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 116, in feed if prober.feed(aBuf) == constants.eFoundIt: File "C:\home\chardet\chardet\charsetgroupprober.py", line 60, in feed st = prober.feed(aBuf) File "C:\home\chardet\chardet\utf8prober.py", line 53, in feed codingState = self._mCodingSM.next_state(c) File "C:\home\chardet\chardet\codingstatemachine.py", line 43, in next_state byteCls = self._mModel['classTable'][ord(c)] TypeError: ord() expected string of length 1, but int found
OK, takže c je typu int
, ale funkce ord()
očekávala jednoznakový řetězec. No dobrá. Kde je definována proměnná c?
# codingstatemachine.py
def next_state(self, c):
# for each byte we get its class
# if it is first byte, we also get byte length
byteCls = self._mModel['classTable'][ord(c)]
To nám nepomůže. Tady se jen předává funkci. Podívejme se hlouběji do zásobníku.
# utf8prober.py
def feed(self, aBuf):
for c in aBuf:
codingState = self._mCodingSM.next_state(c)
Vidíte to? V Pythonu 2 byla proměnná aBuf řetězcem, takže proměnná c byla jednoznakovým řetězcem. (Ten dostáváme, když iterujeme přes řetězec — všechny znaky, jeden po druhém.) Ale teď je aBuf polem bajtů, takže c je typu int
a ne jednoznakový řetězec. Jinými slovy, už nepotřebujeme volat funkci ord()
, protože c už je typu int
!
Takže:
def next_state(self, c):
# for each byte we get its class
# if it is first byte, we also get byte length
- byteCls = self._mModel['classTable'][ord(c)]
+ byteCls = self._mModel['classTable'][c]
Vyhledáním „ord(c)
“ ve všech zdrojových textech odhalíme podobné problémy v sbcharsetprober.py
…
# sbcharsetprober.py
def feed(self, aBuf):
if not self._mModel['keepEnglishLetter']:
aBuf = self.filter_without_english_letters(aBuf)
aLen = len(aBuf)
if not aLen:
return self.get_state()
for c in aBuf:
order = self._mModel['charToOrderMap'][ord(c)]
… a v latin1prober.py
…
# latin1prober.py
def feed(self, aBuf):
aBuf = self.filter_with_english_letters(aBuf)
for c in aBuf:
charClass = Latin1_CharToClass[ord(c)]
Proměnná c iteruje přes aBuf, což znamená, že v ní bude celé číslo a ne jednoznakový řetězec. Řešení je stejné: ord(c)
změníme na prosté c
.
# sbcharsetprober.py
def feed(self, aBuf):
if not self._mModel['keepEnglishLetter']:
aBuf = self.filter_without_english_letters(aBuf)
aLen = len(aBuf)
if not aLen:
return self.get_state()
for c in aBuf:
- order = self._mModel['charToOrderMap'][ord(c)]
+ order = self._mModel['charToOrderMap'][c]
# latin1prober.py
def feed(self, aBuf):
aBuf = self.filter_with_english_letters(aBuf)
for c in aBuf:
- charClass = Latin1_CharToClass[ord(c)]
+ charClass = Latin1_CharToClass[c]
int()
>= str()
A spusťme to znovu.
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 116, in feed if prober.feed(aBuf) == constants.eFoundIt: File "C:\home\chardet\chardet\charsetgroupprober.py", line 60, in feed st = prober.feed(aBuf) File "C:\home\chardet\chardet\sjisprober.py", line 68, in feed self._mContextAnalyzer.feed(self._mLastChar[2 - charLen :], charLen) File "C:\home\chardet\chardet\jpcntx.py", line 145, in feed order, charLen = self.get_order(aBuf[i:i+2]) File "C:\home\chardet\chardet\jpcntx.py", line 176, in get_order if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \ TypeError: unorderable types: int() >= str()
A co se děje zase tady? „Unorderable types“ čili neuspořádatelné typy? (Neuspořádatelné ve smyslu, že mezi těmito hodnotami nelze určit pořadí.) A rozdíl mezi bajty a řetězci znovu vystrkuje svou ošklivou hlavu. Ale podívejte se na kód:
class SJISContextAnalysis(JapaneseContextAnalysis):
def get_order(self, aStr):
if not aStr: return -1, 1
# find out current char's byte length
if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \
((aStr[0] >= '\xE0') and (aStr[0] <= '\xFC')):
charLen = 2
else:
charLen = 1
A odkud se vzala proměnná aStr? Podívejme se hlouběji do zásobníku:
def feed(self, aBuf, aLen):
.
.
.
i = self._mNeedToSkipCharNum
while i < aLen:
order, charLen = self.get_order(aBuf[i:i+2])
Hele, podívejme. To je náš starý přítel aBuf. Jak už jste mohli odhadnout ze všech předchozích problémů, se kterými jsme se v této kapitole setkali, aBuf je pole bajtů. V tomto místě je metoda feed()
nepředává jako celek. Vytváří z něj výřez. Ale jak jsme viděli v této kapitole o něco dříve, výřezem z pole bajtů vznikne pole bajtů. Takže parametr aStr, který přebírá metoda get_order()
, je pořád pole bajtů.
A co se tento kód s aStr pokouší dělat? Získává první prvek z pole bajtů a srovnává jej s jednoznakovým řetězcem. V Pythonu 2 to fungovalo, protože aStr a aBuf byly řetězce a aStr[0] by byl taky řetězec. U řetězců můžeme zjišťovat, zda jsou různé. Ale v Pythonu 3 jsou proměnné aStr a aBuf poli bajtů a aStr[0] je celé číslo. Číslo a řetězec nemůžeme porovnávat na neshodu, aniž jednu z hodnot explicitně nepřevedeme na stejný typ.
V tomto případě nemusíme kód komplikovat přidáváním explicitního převodu typu. aStr[0] je celé číslo. Vše, s čím ho srovnáváme, jsou konstanty. Můžeme je změnit z jednoznakových řetězců na čísla. A když už to děláme, změňme také identifikátor aStr na aBuf, protože to ve skutečnosti není řetězec (string).
class SJISContextAnalysis(JapaneseContextAnalysis):
- def get_order(self, aStr):
- if not aStr: return -1, 1
+ def get_order(self, aBuf):
+ if not aBuf: return -1, 1
# find out current char's byte length
- if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \
- ((aBuf[0] >= '\xE0') and (aBuf[0] <= '\xFC')):
+ if ((aBuf[0] >= 0x81) and (aBuf[0] <= 0x9F)) or \
+ ((aBuf[0] >= 0xE0) and (aBuf[0] <= 0xFC)):
charLen = 2
else:
charLen = 1
# return its order if it is hiragana
- if len(aStr) > 1:
- if (aStr[0] == '\202') and \
- (aStr[1] >= '\x9F') and \
- (aStr[1] <= '\xF1'):
- return ord(aStr[1]) - 0x9F, charLen
+ if len(aBuf) > 1:
+ if (aBuf[0] == 202) and \
+ (aBuf[1] >= 0x9F) and \
+ (aBuf[1] <= 0xF1):
+ return aBuf[1] - 0x9F, charLen
return -1, charLen
class EUCJPContextAnalysis(JapaneseContextAnalysis):
- def get_order(self, aStr):
- if not aStr: return -1, 1
+ def get_order(self, aBuf):
+ if not aBuf: return -1, 1
# find out current char's byte length
- if (aStr[0] == '\x8E') or \
- ((aStr[0] >= '\xA1') and (aStr[0] <= '\xFE')):
+ if (aBuf[0] == 0x8E) or \
+ ((aBuf[0] >= 0xA1) and (aBuf[0] <= 0xFE)):
charLen = 2
- elif aStr[0] == '\x8F':
+ elif aBuf[0] == 0x8F:
charLen = 3
else:
charLen = 1
# return its order if it is hiragana
- if len(aStr) > 1:
- if (aStr[0] == '\xA4') and \
- (aStr[1] >= '\xA1') and \
- (aStr[1] <= '\xF3'):
- return ord(aStr[1]) - 0xA1, charLen
+ if len(aBuf) > 1:
+ if (aBuf[0] == 0xA4) and \
+ (aBuf[1] >= 0xA1) and \
+ (aBuf[1] <= 0xF3):
+ return aBuf[1] - 0xA1, charLen
return -1, charLen
Hledáním výskytu funkce ord()
ve zdrojových textech odkryjeme stejný problém v chardistribution.py
(konkrétně ve třídách EUCTWDistributionAnalysis
, EUCKRDistributionAnalysis
, GB2312DistributionAnalysis
, Big5DistributionAnalysis
, SJISDistributionAnalysis
a EUCJPDistributionAnalysis
). Ve všech případech se oprava podobá změnám, které jsme provedli v třídách EUCJPContextAnalysis
a SJISContextAnalysis
v souboru jpcntx.py
.
'reduce'
není definovánoJeště jedna trhlina…
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 12, in <module> u.close() File "C:\home\chardet\chardet\universaldetector.py", line 141, in close proberConfidence = prober.get_confidence() File "C:\home\chardet\chardet\latin1prober.py", line 126, in get_confidence total = reduce(operator.add, self._mFreqCounter) NameError: global name 'reduce' is not defined
Podle oficiálního průvodce What’s New In Python 3.0 byla funkce reduce()
vyňata z globálního prostoru jmen a přesunuta do modulu functools
. Citujme z průvodce: „Pokud opravdu potřebujete functools.reduce()
, použijte ji. Ale v 99 procentech případů je explicitní cyklus for
čitelnější.“ O tomto rozhodnutí se dočtete více na weblogu Guida van Rossuma: The fate of reduce() in Python 3000 (Osud reduce v Pythonu 3000).
def get_confidence(self):
if self.get_state() == constants.eNotMe:
return 0.01
total = reduce(operator.add, self._mFreqCounter)
Funkce reduce()
přebírá dva argumenty — funkci a seznam (přesněji řečeno, může to být libovolný iterovatelný objekt) — a kumulativně aplikuje zadanou funkci na každý z prvků seznamu. Jinými slovy, jde o efektní a nepřímý způsob realizace součtu všech prvků seznamu.
Tato obludnost byla tak běžná, že byla do Pythonu přidána globální funkce sum()
.
def get_confidence(self):
if self.get_state() == constants.eNotMe:
return 0.01
- total = reduce(operator.add, self._mFreqCounter)
+ total = sum(self._mFreqCounter)
Protože jsme přestali používat modul operator
, můžeme také ze začátku souboru odstranit příslušný příkaz import
.
from .charsetprober import CharSetProber
from . import constants
- import operator
A tož, možeme to otestovať?
C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\blog.worren.net.xml Big5 with confidence 0.99 tests\Big5\carbonxiv.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\catshadow.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\coolloud.org.tw.xml Big5 with confidence 0.99 tests\Big5\digitalwall.com.xml Big5 with confidence 0.99 tests\Big5\ebao.us.xml Big5 with confidence 0.99 tests\Big5\fudesign.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\kafkatseng.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\ke207.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\leavesth.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\letterlego.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\linyijen.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\marilynwu.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\myblog.pchome.com.tw.xml Big5 with confidence 0.99 tests\Big5\oui-design.com.xml Big5 with confidence 0.99 tests\Big5\sanwenji.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\sinica.edu.tw.xml Big5 with confidence 0.99 tests\Big5\sylvia1976.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\tlkkuo.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\tw.blog.xubg.com.xml Big5 with confidence 0.99 tests\Big5\unoriginalblog.com.xml Big5 with confidence 0.99 tests\Big5\upsaid.com.xml Big5 with confidence 0.99 tests\Big5\willythecop.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\ytc.blogspot.com.xml Big5 with confidence 0.99 tests\EUC-JP\aivy.co.jp.xml EUC-JP with confidence 0.99 tests\EUC-JP\akaname.main.jp.xml EUC-JP with confidence 0.99 tests\EUC-JP\arclamp.jp.xml EUC-JP with confidence 0.99 . . . 316 tests
No to mě podrž, ono to funguje! /me si trošku zatancuje
⁂
Co jsme se naučili?
2to3
nám částečně pomůže, ale postará se jen o snadnější části — přejmenování funkcí, přejmenování modulů, úpravy syntaxe. Jde o impozantní kus inženýrské práce, ale koneckonců jde jen o inteligentního robota provádějícího vyhledávání a náhrady.
chardet
je převod proudu bajtů na řetězec. Ale „s proudem bajtů“ se setkáváme častěji, než byste si mysleli. Čtete soubor v „binárním“ režimu? Dostáváte proud bajtů. Získáváte obsah webovské stránky? Voláte webové aplikační rozhraní? Také se vrací proud bajtů.
chardet
funguje v Pythonu 3, spočívá v tom, že jsem začal s testovací sadou, která prověřovala všechny hlavní cesty, kudy se kód ubírá. Pokud žádné testy nemáte, napište je dříve, než začnete přenos do Pythonu 3 realizovat. Pokud máte jen pár testů, napište jich víc. Pokud máte hodně testů, pak teprve může začít opravdová legrace.
© 2001–11 Mark Pilgrim