是否有条件地替换sys.stdout以避免Unicode输出实用?

时间:2016-01-29 18:20:28

标签: python-3.x unicode

我正在为编程语言编写一个命令行解释器,并且根据解释器的性质,有许多纯粹的化妆品UTF-8字符要打印到屏幕上。

我想到,也许我应该容纳那些终端(行式打印机?)不喜欢/支持Unicode的人,或那些字体没有某些字符的字形的人。

我认为我实现这一点而不重写大量现有打印代码的方式是添加一个命令行标志(例如,--no-unicode-out),然后执行以下操作:

import sys
from unicodedata import normalize

class myStdout(object):
    def __init__(self):
        pass

    def write(self, *args, **kwds):
        return sys.__stdout__.write(
            "".join(" ".join(args).replace("µ", "micro"))
        )

    def flush(self, *args, **kwds):
        return sys.__stdout__.flush()

NO_UNICODE_OUT = bool(len(sys.argv) - 1)

if NO_UNICODE_OUT:
    print("stdout switcheroo")
    sys.stdout = s = myStdout()

print(input("> "))

这感觉有点乱,有点hacky。现在,这并不总是坏事,但这种解决方案是否有任何意义,如果没有,那么什么是更好的解决方案呢?

如果有人想挑剔,那么“实际”我的意思是感性的,有效的,可读的,惯用的等等。

3 个答案:

答案 0 :(得分:1)

很多人都读到了我的一句话评论,只发表了OP的一半。

子类化的优势(我很少有机会想到)是它允许特定的方法覆盖,同时带来所有其他方法。我不认为这里存在争议。

但是,我同意这些评论,即改变一个众所周知的全球范围对象是一件坏事。我在想的是(这只是伪代码):

class MyConsole(_io.TextIoWrapper):
    def __init__(self):
        super.__init__()
        # attach self to the same fd as sys.stdout

    def write(self, message):
        self.fd.write(self._asciify(message))

    def print(self, …):
        # optional convenience method
        print(…, file=self.output)

if interactive_console:
    output = sys.stdout 
    if ascii_only:
        output = MyConsole()

    output.print(prompt)
    read_eval_print_loop(sys.stdin, output, …)

提倡的是sys.stdout = anything,正如评论者指出的那样,有可能接近1.0的意外副作用。没错,我的简单评论根本没有解决OP的这个方面。

我没有看到其他地方提到的unidecode包,它可能对我所知道的一切都很完美。这可能已经完全重写了那个轮子,或者模块对于任务来说可能是过度的。

答案 1 :(得分:1)

.replace("µ", "micro")不实用。它不处理所有其他Unicode字符。假设没有代码可以打印不可打印的Unicode字符是无法管理的。

如果已经打印Unicode(默认设置),则无需更改代码:不要在脚本中硬编码环境的字符编码。有多种方法可以支持Unicode不足的环境,例如设置PYTHONIOENCODING=:backslashreplace envvar和/或您可以设置sys.displayhook来格式化输出,就像IPython一样(注意:it might create issues with doctest and other similar modules)。

如果您以独立于解释器其余部分的方式扩展功能,则替换sys.stdout是有意义的(例如,您不应该将那些知道解释器提示的逻辑放在那里)。 win-unicode-console package是替换标准流可能合理的示例(它可以打印任何Unicode字符。虽然它不能修复在默认Windows控制台中显示非BMP字符,但自然相应的字体必须支持所需的字符太)。

实际的解决方案可以使用多种方法的组合,具体取决于哪个对象最适合在给定的抽象级别管理信息,例如,查看IPython如何实现彩色打印(pyreadline),请参阅{ {3}}

  

问题是如果有人的终端无法呈现我强行喂它的东西,那就清理我自己的烂摊子。

即使您只需要支持您生成的文本;你不应该把.replace("µ", "micro")放在sys.stdout对象里面。相反,请将.replace("µ", "micro")放在生成µ的位置,即生成micro

答案 2 :(得分:-2)

首先,正确的方法是使用在撰写本文时仅在8天前更新的Unidecode

其次,为了回答关于代码的“惯用性”的问题,我发现覆盖系统级别的函数和对象是最好的解决方案。在代码中引入太多魔法使得它更难以阅读并因此更难调试(想象另一个人在阅读代码时不知道你的hack以及当print内置不起作用时他所遇到的头痛现在想象一下,这位开发人员将来就是你,你不记得这样做,你甚至不能问你过去的自己做了什么。)这违反了PEP 20 - The Zen of Python的第2点(明确比隐含更好)。

当我发现自己想要覆盖系统函数时,我通常将它们放在一个小包装器中,如下所示:

def _p(obj):
    #some logic on the object
    print(obj)

使用这种方法,代码更具可读性,当有人看到_p函数时,他们知道它必须在某处定义,因为它不是内置函数。