【问题标题】:Best way to decode command line inputs to Unicode Python 2.7 scripts将命令行输入解码为 Unicode Python 2.7 脚本的最佳方法
【发布时间】:2015-11-19 18:42:26
【问题描述】:

我的所有脚本自始至终都使用 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 是否提供了一些内置工具来完成我错过的这一点?

【问题讨论】:

  • unicode_literals 导入与用于命令行参数的字符编码无关。
  • @J.F.Sebastian:怎么会这样?使用unicode_literals 意味着我的代码使用Unicode 文字,因此任何命令行字符串都会被解码。这就是为什么我需要知道编码;否则我会得到例外。
  • 命令行不是 Python 代码的一部分。你了解“字面”这个词吗?例如,some_python_name 不是字符串文字,无论 some_python_name 具有何种类型。 Python 源代码中的 "abc" 是字符串文字(没有 unicode_literals 它是 Python 2 上的字节字符串)。 sys.argv[i] 不是文字:无论您是否使用unicode_literals,它的值都不会改变(print sys.argv 自己看看)。
  • @J.F.Sebastian:我想你不明白这个问题。
  • 我正在进一步调查这个问题,因为似乎有关于它的相互冲突的引用。还有bugs,所以你可能想提到你的平台/操作系统。

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


【解决方案1】:

我认为getfilesystemencoding 不一定会为 shell 获得正确的编码,它取决于 shell(并且可以由 shell 自定义,独立于文件系统)。文件系统编码只关心如何存储非 ascii 文件名。

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

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

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)

演示:

$ 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)

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


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

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

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

#!/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)

然后我将我的shell编码更改为ISO/IEC 8859-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')

如您所见,命令行参数以 latin-1 编码,因此第二个命令行参数(使用 sys.getfilesystemencoding)无法解码。第三个命令行参数(使用sys.stdin.encoding)正确解码。

【讨论】:

  • 这很聪明。你能多谈谈foo 的工作原理吗?那里有一些魔法。
  • 这不是很神奇,关键字参数type 应该只是一个可调用的,它从传入的字节返回转换为python 的对象。如果转换失败,它应该引发argparse.ArgumentTypeError
  • 会不会出现命令行提供 Unicode 的情况(因此根本不应该调用 decode)?
  • @J.F.Sebastian:你能记录下来吗?网络上的那一品脱啤酒显然令人困惑。
  • 看来我发布的反例证明 (b) 是错误的。
【解决方案2】:

sys.getfilesystemencoding() 是文件名、环境变量和命令行参数等操作系统数据的正确(但请参见示例)编码。

您可以看到选择背后的逻辑:sys.argv[0] 可能是脚本的路径(文件名),因此很自然地假设它使用与其他文件名相同的编码以及@987654326 中的其他项目@list 使用与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] 脚本文件名,无论您可能有其他命令行参数。

“最佳方式” 取决于您的特定用例,例如,在 Windows 上,您可能应该 use Unicode API directly (CommandLineToArgvW())。在 POSIX 上,如果您只需将一些 argv 项传递回 OS 函数(例如 os.listdir()),那么您可以将它们保留为字节 - 命令行参数可以是 任意 字节序列,见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'

【讨论】:

  • 我的命令行参数不是文件名。我的用例是作为脚本参数输入的文本。
  • @raxacoricofallapatorius: 和?
  • 查看其他 cmets:您能否证明 sys.getfilesystemencoding() 而不是 sys.stdin.encoding 是命令行参数的正确编码(假设它们不是文件名)?
  • 阅读第一段答案。
  • 抱歉,我的浏览器一定不能显示那里的链接。
猜你喜欢
  • 2020-08-03
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-31
  • 2014-10-27
  • 2018-05-08
  • 2015-07-26
相关资源
最近更新 更多