使用python3编码问题并单击包

Rob*_*rto 11 encoding command-line-interface python-3.x python-click

当lib click检测到运行时是python3但编码是ASCII时,它会突然结束python程序:

RuntimeError: Click will abort further execution because Python 3 was configured to use ASCII as encoding for the environment. Either switch to Python 2 or consult http://click.pocoo.org/python3/ for mitigation steps.
Run Code Online (Sandbox Code Playgroud)

在我的情况下,我发现了这个问题的原因,当我从Mac连接到我的Linux主机时,Terminal.app将SSH会话区域设置为我的Mac区域设置(es_ES.UTF-8)但是我的Linux主机没有安装这样的语言环境(仅限en_US.utf-8).

我应用了一个初步的解决方法来解决它(但它有很多问题,请参阅已接受的答案):

import locale, codecs
# locale.getpreferredencoding() == 'ANSI_X3.4-1968'
if codecs.lookup(locale.getpreferredencoding()).name == 'ascii':
    os.environ['LANG'] = 'en_US.utf-8'
Run Code Online (Sandbox Code Playgroud)

编辑:要获得更好的补丁,请参阅我接受的答案.

我的所有linux主机都安装了'en_US.utf-8'作为语言环境(Fedora默认使用它).

我的问题是:在python3脚本中选择/强制语言环境是否有更好(更强大)的方法?例如,在系统中设置一个可用的语言环境.

也许有一种不同的方法来解决这个问题,但我没有找到它.

Rob*_*rto 5

如果您的 python 版本 >= 3.7,那么您不需要执行任何操作。如果您有 python 3.6,请参阅原始解决方案。

编辑2017-12-08

我已经看到 py3.7 有一个 PEP 538,它将改变启动期间 python3 编码管理的整个行为,我认为新方法将解决原始问题:https ://www.python.org/开发/peps/pep-0538/

恕我直言,针对 python 3.7 编码问题的更改应该在几年前就计划好了,但我想迟到总比不到好。

编辑2015-09-01

有一个开放的问题(增强),http://bugs.python.org/issue15216,它将允许轻松更改创建的(未使用的)流中的编码(sys.std*)。但针对的是 python 3.7 所以,我们还得等一段时间。

针对 python 版本 3.6 的原始解决方案

注意:运行 python 版本 >= 3.7 的任何人都不需要此解决方案,请参阅 PEP 538

好吧,我最初的解决方法有很多缺陷,我必须通过click有关编码的库检查,但编码本身并未修复,因此当输入参数或输出具有非 ascii 字符时我会遇到异常。

我必须实现一个更复杂的方法,有 3 个步骤:设置区域设置、在 std 输入/输出中正确编码并重新编码命令行参数,此外,如果第一次尝试设置区域设置,我还添加了一个“友好”退出没有按预期工作:

def prevent_ascii_env():
    """
    To avoid issues reading unicode chars from stdin or writing to stdout, we need to ensure that the 
    python3 runtime is correctly configured, if not, we try to force to utf-8, 
    but It isn't possible then we exit with a more friendly message that the original one.
    """
    import locale, codecs, os, sys
    # locale.getpreferredencoding() == 'ANSI_X3.4-1968'
    if codecs.lookup(locale.getpreferredencoding()).name == 'ascii':
        os.environ['LANG'] = 'en_US.utf-8'
        if codecs.lookup(locale.getpreferredencoding()).name == 'ascii':
            print("The current locale is not correctly configured in your system")
            print("Please set the LANG env variable to the proper value before to call this script")
            sys.exit(-1)
        #Once we have the proper locale.getpreferredencoding() We can change current stdin/out streams
        _, encoding = locale.getdefaultlocale()
        import io
        sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding=encoding, errors="replace", line_buffering=True)
        sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding=encoding, errors="replace", line_buffering=True)
        sys.stdin = io.TextIOWrapper(sys.stdin.detach(), encoding=encoding, errors="replace", line_buffering=True)
        # And finally we need to re-encode the input parameters
        for i, p in enumerate(sys.argv):
            sys.argv[i] = os.fsencode(p).decode() 
Run Code Online (Sandbox Code Playgroud)

这个补丁解决了几乎所有问题,但是它有一个警告,该方法shutils.get_terminal_size()引发了一个问题,ValueError因为它sys.__stdout__已被分离, lib 使用该方法来打印帮助,要修复它,我必须在libclick上应用猴子补丁click

def wrapper_get_terminal_size():
    """
    Replace the original function termui.get_terminal_size (click lib) by a new one 
    that uses a fallback if ValueError exception has been raised
    """
    from click import termui, formatting
    
    old_get_term_size = termui.get_terminal_size
    def _wrapped_get_terminal_size():
        try:
            return old_get_term_size()
        except ValueError:
            import os
            sz = os.get_terminal_size()
            return sz.columns, sz.lines
    termui.get_terminal_size = _wrapped_get_terminal_size
    formatting.get_terminal_size = _wrapped_get_terminal_size
Run Code Online (Sandbox Code Playgroud)

通过此更改,当环境配置了错误的语言环境但系统支持 en_US.utf-8 (这是 Fedora 默认语言环境)时,我的所有脚本现在都可以正常工作。

如果您发现此方法有任何问题或有更好的解决方案,请添加新答案。