将命令行输入解码为Unicode Python 2.7脚本的最佳方法

时间:2015-11-19 18:42:27

标签: python-2.7 unicode command-line-arguments

我的所有脚本都使用Unicode文字,

from __future__ import unicode_literals

但是当有可能用字节串调用函数时会产生问题,我想知道处理这个问题的最佳方法是什么,并产生明显有用的错误。

gather我采用的一种常见方法就是在发生这种情况时简单地说明这一点,例如

def my_func(somearg):
    """The 'somearg' argument must be Unicode."""
    if not isinstance(arg, unicode):
        raise TypeError("Parameter 'somearg' should be a Unicode")
    # ...

对于需要为Unicode的所有参数(可能是字节串)。但是,即使我这样做,如果提供的参数对应于这样的参数,我会遇到argparse命令行脚本的问题,我想知道这里最好的方法是什么。似乎我可以简单地检查这些参数的编码,并使用该编码解码它们,例如

if __name__ == '__main__':
    parser = argparse.ArgumentParser(...)
    parser.add_argument('somearg', ...)
    # ...

    args = parser.parse_args()
    some_arg = args.somearg
    if not isinstance(config_arg, unicode):
        some_arg = some_arg.decode(sys.getfilesystemencoding())

    #...
    my_func(some_arg, ...)

这种方法的组合是可以接收字节串输入的Unicode模块的通用设计模式吗?具体地,

  • 我可以用这种方式可靠地解码命令行参数,并且
  • sys.getfilesystemencoding()给我正确的命令行参数编码;或
  • argparse是否提供了一些内置设施来完成我错过的内容?

2 个答案:

答案 0 :(得分:1)

我不认为char*必须为shell获得正确的编码,它取决于shell(并且可以由shell自定义,独立于文件系统)。文件系统编码仅涉及非ascii文件名的存储方式。

相反,您可能应该查看getfilesystemencoding,它将为您提供标准输入的编码。

此外,添加参数时可以考虑使用type关键字参数:

sys.stdin.encoding

演示:

import sys
import argparse as ap

def foo(str_, encoding=sys.stdin.encoding):
    return str_.decode(encoding)

parser = ap.ArgumentParser()
parser.add_argument('my_int', type=int)
parser.add_argument('my_arg', type=foo)
args = parser.parse_args()

print repr(args)

如果你必须经常使用非ascii数据,我强烈建议升级到python3。那里的一切都比较容易,例如,解析后的参数已经是python3上的unicode。

由于存在关于命令行参数编码的冲突信息,我决定通过将我的shell编码更改为latin-1来测试它,同时将文件系统编码保留为utf-8。对于我的测试,我使用的c-cedilla character在这两个中有不同的编码:

$ python spam.py abc hello
usage: spam.py [-h] my_int my_arg
spam.py: error: argument my_int: invalid int value: 'abc'
$ python spam.py 123 hello
Namespace(my_arg=u'hello', my_int=123)
$ python spam.py 123 ollǝɥ
Namespace(my_arg=u'oll\u01dd\u0265', my_int=123)

现在我创建一个示例脚本:

>>> u'Ç'.encode('ISO8859-1')
'\xc7'
>>> u'Ç'.encode('utf-8')
'\xc3\x87'

然后我将我的shell编码更改为#!/usr/bin/python2.7 import argparse as ap import sys print 'sys.stdin.encoding is ', sys.stdin.encoding print 'sys.getfilesystemencoding() is', sys.getfilesystemencoding() def encoded(s): print 'encoded', repr(s) return s def decoded_filesystemencoding(s): try: s = s.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: s = 'failed!' return s def decoded_stdinputencoding(s): try: s = s.decode(sys.stdin.encoding) except UnicodeDecodeError: s = 'failed!' return s parser = ap.ArgumentParser() parser.add_argument('first', type=encoded) parser.add_argument('second', type=decoded_filesystemencoding) parser.add_argument('third', type=decoded_stdinputencoding) args = parser.parse_args() print repr(args)

enter image description here

我打电话给剧本:

ISO/IEC 8859-1

如您所见,命令行参数在latin-1中进行编码,因此第二个命令行参数(使用wim-macbook:tmp wim$ ./spam.py Ç Ç Ç sys.stdin.encoding is ISO8859-1 sys.getfilesystemencoding() is utf-8 encoded '\xc7' Namespace(first='\xc7', second='failed!', third=u'\xc7') )无法解码。第三个命令行参数(使用sys.getfilesystemencoding)正确解码。

答案 1 :(得分:0)

sys.getfilesystemencoding()是OS数据(如文件名,环境变量和命令行参数)的正确(但请参见示例)编码。

您可以看到选择背后的逻辑:sys.argv[0]可能是脚本的路径(文件名),因此很自然地假设它使用与其他文件名相同的编码以及其他项目argv列表使用与sys.argv[0]相同的字符编码。 os.environ['PATH']包含路径,因此环境变量使用相同的编码也很自然:

$ echo 'import sys; print(sys.argv)' >print_argv.py
$ python print_argv.py
['print_argv.py']

注意:sys.argv[0] 脚本文件名,无论您拥有其他命令行参数。

&#34;最佳方式&#34; 取决于您的具体用例,例如,在Windows上,您应该use Unicode API directly (CommandLineToArgvW())。在POSIX上,如果您只需要将一些argv项传递回OS函数(例如os.listdir()),那么您可以将它们保留为字节 - 命令行参数可以任意< / em>字节序列,请参阅PEP 0383 -- Non-decodable Bytes in System Character Interfaces

import os, sys

os.execl(sys.executable, sys.executable, '-c', 'import sys; print(sys.argv)',
         bytes(bytearray(range(1, 0x100))))

正如您所见,POSIX允许传递任何字节(零除外)。

显然,您也可能错误配置您的环境:

$ LANG=C PYTHONIOENCODING=latin-1 python -c'import sys;
>   print(sys.argv, sys.stdin.encoding, sys.getfilesystemencoding())' €
(['-c', '\xe2\x82\xac'], 'latin-1', 'ANSI_X3.4-1968') # Linux output

输出显示使用utf-8编码,但区域设置和PYTHONIOENCODING的配置不同。

这些示例表明sys.argv可以使用与任何标准编码不对应的字符编码进行编码,或者甚至可以在POSIX上包含任意(除了零字节)二进制数据(无字符编码)。在Windows上,我想,您可以粘贴一个无法使用ANSI或OEM Windows编码进行编码的Unicode字符串,但无论如何您都可以使用Unicode API获取正确的值(Python 2可能会在此处删除数据)。

Python 3使用Unicode sys.argv,因此它不应该在Windows上丢失数据(使用Unicode API),它可以证明使用了sys.getfilesystemencoding()(不是sys.stdin.encoding )解码Linux上的sys.argv(其中sys.getfilesystemencoding()派生自区域设置):

$ LANG=C.UTF-8 PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
'-c' '\xb5'
$ LANG=C PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
'-c' '\udcc2\udcb5'
$ LANG=en_US.ISO-8859-15 PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
'-c' '\xc2\xb5'

输出显示在这种情况下定义语言环境的LANG用于解释命令行参数:

在Linux上定义sys.getfilesystemencoding()
$ python3
>>> print(ascii(b'\xc2\xb5'.decode('utf-8')))
'\xb5'
>>> print(ascii(b'\xc2\xb5'.decode('ascii', 'surrogateescape')))
'\udcc2\udcb5'
>>> print(ascii(b'\xc2\xb5'.decode('iso-8859-15')))
'\xc2\xb5'