Use codec error handler for print() in Python3?
Question: It is well known that the `encode()` has a `error` param for codec error
handling, for example:

    
    
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    # "发" and "财" are not available in 'big5' encoding
    text = "发财了".encode('big5', errors='replace')
    

However, there is not an `errors` param for `print()`, and if we simply write:

    
    
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    print("发财了")
    

A `UnicodeEncodeError` exception is raised if it's run in a command prompt
with big5 encoding (for example, in a Traditional Chinese version Windows).

Is there a way to make `print()` act like `encode()`, which accepts more
handlers such as `replace`, `backslashreplace`, or `xmlcharrefreplace`, so
that the string can be printed safely without raising an exception?


Answer: Here is my current solution:

    
    
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    import sys
    
    def safeprint(*args, errors='backslashreplace', **kargs):
        """
        Print safely and skips error decode.
    
        Acts like print() with an additional "errors" argument to determine the
        error handler for codec errors and accepts non-str-or-None types for the
        "sep" and "end" arguments.
        """
        e = (kargs['file'] if 'file' in kargs else sys.stdout).encoding
        args = [str(x) for x in args]
        sep = str(kargs['sep']) if 'sep' in kargs and kargs['sep'] is not None else " "
        end = str(kargs['end']) if 'end' in kargs and kargs['end'] is not None else "\n"
        text = sep.join(args) + end
        kargs['sep'] = ""
        kargs['end'] = ""
        print(text.encode(e, errors).decode(e, errors), **kargs)
    
    if __name__ == "__main__":
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड")
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", errors="ignore")
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", errors="replace")
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", errors="backslashreplace")
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", errors="xmlcharrefreplace")
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", sep=None, end=str)
        safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", sep=" -发- ", end=" -财- \n")
        with open("safeprint_big5.log", "w", encoding="big5") as f:
            safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", file=f)
        with open("safeprint_gbk.log", "w", encoding="gbk") as f:
            safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", file=f)
        with open("safeprint_utf8.log", "w", encoding="utf-8") as f:
            safeprint("Hello World!", "你好世界！", "ハローワールド", "हैलो वर्ल्ड", file=f)
    

This approach makes a custom function `safeprint()` that acts like native
`print()` with these differences:

  1. Has an additional `errors` argument to determine how to handle codec errors (`backslashreplace` as default).

  2. The `sep` and `end` arguments accept types other than str or None.

`safeprint()` checks the encoding of the ouput file that native `print()` are
supposed to write into and encodes all text arguments beforewards, and
therefore all printable chars are printed as-is and all unprintable chars are
printed as transformed.

Although encoding and decoding beforewards for all texts being printed seems
inefficient, native `encode()` and `decode()` are C based and are running very
fast. In a test I print some articles with a utf8-compatible plain text for
5000 times in a utf8 console, the native `print()` takes `0:00:02.366799` and
`safeprint()` takes `0:00:02.915871`. It proves that the performance drop is
almost negligible.

The above script can be saved as a module script, say `safeprint.py`. Other
scripts can use `from safeprint import safeprint` and use `safeprint()`, or
can even use `from safeprint import safeprint as print` to overwrite the
native `print()` so that `print()` will work just like `safeprint()` does.

