将命令行输入解码为 Unicode Python 2.7 脚本的最佳方式
Best way to decode command line inputs to Unicode Python 2.7 scripts
我所有的脚本自始至终都使用 Unicode 文字,
from __future__ import unicode_literals
但是当有可能使用字节串调用函数时,这会产生一个问题,我想知道处理这个问题并产生明显有用错误的最佳方法是什么。
我 我采用的一种常见方法是在它发生时简单地说明这一点,例如
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
是否提供了一些我错过的内置工具来完成此操作?
我不认为 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 whilst leaving the file system encoding as utf-8. For my tests I use the 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
)正确解码。
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]
是 脚本文件名,无论您可能有其他命令行参数。
"best way" 取决于您的具体用例,例如,在 Windows 上,您可能应该 use Unicode API directly (CommandLineToArgvW()
). On POSIX, if all you need is to pass some argv
items to OS functions back (such as os.listdir()
) then you could leave them as bytes -- command-line argument can be arbitrary byte sequence, see 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
可能使用不符合任何标准编码的字符编码进行编码,甚至可能包含 P[=73= 上的任意(零字节除外)二进制数据]IX(无字符编码)。在 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'
我所有的脚本自始至终都使用 Unicode 文字,
from __future__ import unicode_literals
但是当有可能使用字节串调用函数时,这会产生一个问题,我想知道处理这个问题并产生明显有用错误的最佳方法是什么。
我
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
是否提供了一些我错过的内置工具来完成此操作?
我不认为 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 whilst leaving the file system encoding as utf-8. For my tests I use the 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
)正确解码。
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]
是 脚本文件名,无论您可能有其他命令行参数。
"best way" 取决于您的具体用例,例如,在 Windows 上,您可能应该 use Unicode API directly (CommandLineToArgvW()
). On POSIX, if all you need is to pass some argv
items to OS functions back (such as os.listdir()
) then you could leave them as bytes -- command-line argument can be arbitrary byte sequence, see 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
可能使用不符合任何标准编码的字符编码进行编码,甚至可能包含 P[=73= 上的任意(零字节除外)二进制数据]IX(无字符编码)。在 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'