Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Probleme d'encoding multiplateforme

+3 votes

En python 2.7, les fonctions du module os.path sont capables de traiter a la fois des chaines unicode (u"") et des bytestrings ("").

Lorsqu'un nom de fichier contient des caractères accentués, il est donc possible de faire :

fname = u"accentué.ext"

# avec un objet unicode
os.path.exists(fname)
# avec une string
os.path.exists(fname.encode(sys.getfilesystemencoding())) 

Ça marche, pas de probleme.

J'ai par contre un probleme dans un cas très particulier : admettons un fichier dont le nom contient des accents, zippé sous OSx et dezippé sous Windows.
Comme décrit dans cet article, les différentes implémentation de zip font a peu prêt n'importe quoi avec les encodings.

Admettons un fichier qui s’appellerait ''accentuée.ext", qui aurait été zippé sous OSx et dézippé sous windows, je me retrouve dans le cas suivant

# listdir avec un unicode renvoit des unicodes
In [30]: os.listdir(u'.')
Out[30]:[u'accentue\u2560\xfce.ext',]

# listdir avec un str renvoit des str
In [320]: os.listdir('.')
Out[320]:['accentue\xa6\xfce.ext',]

# Avec un objet unicode, on est capables de retrouver le fichier 
In [319]: os.path.exists(os.listdir(u'.')[0])
Out[319]: True

# mais pas avec un str renvoyé par listdir
In [318]: os.path.exists(os.listdir('.')[0])
Out[318]: False

# ni avec un unicode encodé en str avec l'encoding du filesystem
In [323]: os.path.exists(os.listdir(u'.')[0].encode(sys.getfilesystemencoding())
Out[323]: False

Donc je me retrouve dans un cas ou
- je ne peux pas utiliser le retour d'un listdir str
- je ne peux pas utiliser le retrour d'un listdir unicode encodé avec l'encodage du filesystem.

Dans le doute, j'ai aussi tenté de bruteforcer la chose, en tentant de normaliser la chaine unicode avec ce bout de code

import os
import sys
import unicodedata
from encodings import aliases # 

import zlib, binascii # for exceptions handling

# normalization forms
norm_form = ['NFC', 'NFD', 'NFKC', 'NFKD']

apath = os.listdir(u".")[0]
i = 0
for norm in norm_form: # loop over norm forms
    for k, v in aliases.aliases.iteritems(): # loop over availables encodings
        try:
            thepath = unicodedata.normalize(norm, apath).encode(v)
            # thepath = apath.encode(v) # also tested without normalization

            if os.path.exists(thepath):
                print "nice job", k, v
                i+=1
        except (UnicodeDecodeError, UnicodeEncodeError, LookupError, zlib.error, binascii.Error, TypeError):
            pass
print "%s / %s " % (i , len(aliases.aliases) )

Dans le cas de mon fichier accentué.ext crée localement, j'ai bien une output nice job, avec 144 success sur 307 combinaisons encodings / normalizations différentes.
Dans le cas de mon fichier dont le nom contient des caractères louches, je reste tristement a 0 succes.

Ce qui me parait vriment louche, c'est que même si le nom du fichier contient des caractères exotiques, la fonction os.path.exists arrive a trouver le fichier lorsqu'il est donné sous forme unicode, mais jamais sous une forme bytestring.

Donc, ma question : est-ce qu'il vous parait possible dans ce cas précis de récupérer le nom du fichier sous forme bytestring a partir de la chaine unicode, de manière a ce que python (windows ?) le retrouve sur le filesystem ?

Edit : malheureusement, je n'ai pas la possibilité d'altérer le fichier : le renommer en quelque chose de plus propre n'est pas une option.

Note : ici je prend l'exemple d'un os.path.exists, mais dans mon le cas d'utilisation le nom du fichier va être utilisé par une lib externe qui s'attend a du str pour faire un appel a subprocess.Popen, ce qui leveras potentiellement d'autre probleme étant donné que l'encodage de stdout est encore différent de celui du fs

demandé 24-Jun-2015 par jc (2,674 points)
edité 24-Jun-2015 par jc

1 Réponse

+6 votes
 
Meilleure réponse

Là tu as un problème de double encoding. En gros le logiciel d'archivage encodé son chaine une fois, puis a passé la chaine au système de fichier tel quel qu'il a encodé une seconde fois. Il te faut donc appeler decode() deux fois dessus, avec chaque fois le bon encoding, dans le bon ordre. Un problème difficile, qui n'a pas de solution propre.

répondu 26-Jun-2015 par Sam (4,974 points)
sélectionné 29-Jun-2015 par jc
...