Nombres de archivos Unicode en Windows con Python y subprocess.Popen ()

¿Por qué ocurre lo siguiente?

>>> u'\u0308'.encode('mbcs') #UMLAUT '\xa8' >>> u'\u041A'.encode('mbcs') #CYRILLIC CAPITAL LETTER KA '?' >>> 

Tengo una aplicación de Python que acepta nombres de archivos del sistema operativo. Funciona para algunos usuarios internacionales, pero no para otros.

Por ejemplo, este nombre de archivo Unicode: u ‘\ u041a \ u0433 \ u044b \ u044b \ u0448 \ u0444 \ u0442’

no se codificará con la encoding de Windows ‘mbcs’ (la utilizada por el sistema de archivos, devuelta por sys.getfilesystemencoding ()). Obtengo ‘???????’, indicando que el codificador falla en esos caracteres. Pero esto no tiene sentido, ya que el nombre del archivo provino del usuario, para empezar.

Actualización: Estos son los antecedentes de mis razones detrás de esto … Tengo un archivo en mi sistema con el nombre en cirílico. Quiero llamar a subprocess.Popen () con ese archivo como argumento. Popen no manejará Unicode. Normalmente, puedo codificar el argumento con el códec proporcionado por sys.getfilesystemencoding (). En este caso no funcionará.

En Py3K, al menos desde Python 3.2, subprocess.Popen y sys.argv funcionan de forma coherente con las cadenas (unicode predeterminado) en Windows. CreateProcessW y GetCommandLineW se utilizan obviamente.

En Python, hasta v2.7.2, al menos, subprocess.Popen tiene problemas con los argumentos de Unicode. Se adhiere a CreateProcessA (mientras que os.* Concuerda con Unicode). Y shlex.split crea tonterías adicionales.

Win32process.CreateProcess de win32process.CreateProcess tampoco cambia automáticamente a la versión W, ni existe un win32process.CreateProcessW . Lo mismo con GetCommandLine . Por ctypes.windll.kernel32.CreateProcessW... tanto, ctypes.windll.kernel32.CreateProcessW... debe ser utilizado. El módulo de subproceso tal vez debería arreglarse con respecto a este problema.

UTF8 en argv[1:] con aplicaciones privadas permanece torpe en un sistema operativo Unicode. Tales trucos pueden ser legales para los sistemas operativos de cadenas “Latin1” de 8 bits como Linux.

ACTUALIZACIÓN vaab ha creado una versión parcheada de Popen para Python 2.7 que soluciona el problema.
Consulte https://gist.github.com/vaab/2ad7051fc193167f15f85ef573e54eb9
Publicación en blog con explicaciones: http://vaab.blog.kal.fr/2017/03/16/fixing-windows-python-2-7-unicode-issue-with-subprocesss-popen/

DESCARGO DE RESPONSABILIDAD: Soy el autor de la revisión mencionada a continuación.

Para admitir la línea de comandos de Unicode en Windows con Python 2.7, puede usar este parche para subprocess.Popen(..)

La situación

La compatibilidad con Python 2 de la línea de comandos de Unicode en Windows es muy deficiente.

Están gravemente molestados:

  • emitiendo la línea de comando Unicode al sistema desde el lado del llamante (a través de subprocess.Popen(..) ),

  • y leyendo los argumentos de Unicode de la línea de comando actual desde el lado del usuario (a través de sys.argv ),

Se reconoce y no se corrige en Python 2. Se corrigen en Python 3.

Razones tecnicas

En Python 2, la implementación de subprocess.Popen(..) y sys.argv utilizan los sistemas de windows no listos para Unicode llamados CreateProcess(..) (vea el código de python y el documento MSDN de CreateProcess ) y no usa GetCommandLineW(..) para sys.argv .

En Python 3, la implementación de Windows de subprocess.Popen(..) hace uso de los sistemas de Windows correctos llamados CreateProcessW(..) partir de 3.0 (ver código en 3.0 ) y sys.argv usa GetCommandLineW(..) partir de 3.3 ( ver codigo en 3.3 ).

Como se arregla

El parche dado aprovechará el módulo ctypes para llamar al sistema Windows C CreateProcessW(..) directamente. Propone un nuevo objeto fijo de Popen al reemplazar el método privado Popen._execute_child(..) y la función privada _subprocess.CreateProcess(..) para configurar y usar CreateProcessW(..) desde el sistema de ventanas lib de una manera que imite lo más posible Cómo se hace en Python 3.6 .

Cómo usarlo

La explicación de la publicación del blog muestra cómo utilizar el parche dado. También muestra cómo leer los procesos actuales sys.argv con otra solución .

Los documentos para sys.getfilesystemencoding () dicen que para Windows NT y versiones posteriores, los nombres de archivos son de forma nativa Unicode. Si tiene un nombre de archivo Unicode válido, ¿por qué molestarse en codificarlo usando mbcs?

El módulo Docs for Codecs dice que mbcs se codifica utilizando la “página de códigos ANSI” (que variará según la configuración regional del usuario), por lo que si la configuración regional no utiliza caracteres cirílicos, splat.

Edición: Por lo tanto, su proceso está llamando a subprocess.Popen (). Si su proceso invocado está bajo su control, los dos procesos deberían poder aceptar el uso de UTF-8 como el Formato de transporte Unicode. De lo contrario, es posible que deba preguntar en la lista de correo de pywin32. En cualquier caso, edite su pregunta para indicar el grado de control que tiene sobre el proceso invocado.

Si necesita pasar el nombre de un archivo existente, es posible que tenga más posibilidades de éxito al pasar la versión 8.3 del nombre de archivo Unicode.

Necesitas tener el paquete pywin32 instalado, entonces puedes hacer:

 >>> import win32api >>> win32api.GetShortPathName(u"C:\\Program Files") 'C:\\PROGRA~1' 

Creo que estos nombres de archivo cortos solo usan caracteres ASCII, y por lo tanto, debería poder usarlos como argumentos para una línea de comando.

Si necesita especificar también los nombres de los archivos que se crearán, puede crearlos con un tamaño cero por adelantado desde Python utilizando nombres de archivos Unicode, y pasar el nombre corto del archivo como un argumento.

ACTUALIZACIÓN: el usuario bogdan dice correctamente que la generación del nombre de archivo 8.3 puede estar deshabilitada (también la tuve deshabilitada cuando tenía Windows XP en mi computadora portátil), por lo que no puede confiar en ellos. Entonces, como otro enfoque más inverosímil cuando se trabaja en volúmenes NTFS, uno puede vincular los nombres de archivo Unicode con los ASCII simples; pase los nombres de archivo ASCII a un comando externo y elimínelos después.

Con Python 3, simplemente no codifique la cadena. Los nombres de los archivos de Windows son Unicode de forma nativa, y todas las cadenas en Python 3 son Unicode, y Popen usa la versión Unicode de la función API de Windows CreateProcess .

Con Python 2.7, la solución más sencilla es usar el módulo de terceros https://pypi.org/project/subprocessww/ . No hay una solución “incorporada” para obtener el soporte completo de Unicode (independientemente de la configuración regional del sistema), y los mantenedores de Python 2.7 consideran esto como una solicitud de función en lugar de una corrección de errores, por lo que esto no va a cambiar.

Para obtener una explicación técnica detallada de por qué las cosas son como son, consulte las otras respuestas.