我的所有脚本都使用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
是否提供了一些内置设施来完成我错过的内容? 答案 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)
:
我打电话给剧本:
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]
脚本文件名,无论您拥有其他命令行参数。
"最佳方式" 取决于您的具体用例,例如,在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
用于解释命令行参数:
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'