Decorator to avoid UnicodeEncodeError in Python 3 print ()

I wrote it after practicing the decorator. It has been confirmed to work with Python 3.3.0.

Since it is a mundane content, I will omit the explanation, but by default, it is

It is designed to do.

There seems to be room for reconsideration about the specifications, so if you have any good ideas, please let us know in the comments. We also welcome any bugs or bad points.

fixprint.py


#!/usr/bin/env python3
# vim:fileencoding=utf-8

# Copyright (c) 2013 Masami HIRATA <[email protected]>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     1. Redistributions of source code must retain the above copyright notice,
#        this list of conditions and the following disclaimer.
#
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

__all__ = ["fixprint"]

import codecs
from collections import namedtuple
from functools import partial, wraps
import sys

STREAMS = ("stdin", "stdout", "stderr")
ARGUMENTS = ("encoding", "errors")
ENCODING_DEFAULT = 'utf-8'
ERRORS_DEFAULT = 'backslashreplace'
ENCODING_ASCII = 'ascii'
ENCODING_UTF8 = 'utf-8'
ANY = None


def fixprint(function=None, encoding=None, *, errors=None):
    """This decorator changes the 'encoding' and 'errors' of stdin/out/err

        >>> import sys
        >>> @fixprint(encoding='latin-1', errors='replace')
        ... def spam():
        ...     print("sys.stdout.encoding in spam() is '{}'".format(
        ...           sys.stdout.encoding))
        ...
        >>> @fixprint('utf-8')
        ... def ham():
        ...     spam()
        ...     print("sys.stdout.encoding in ham() is '{}'".format(
        ...           sys.stdout.encoding))
        ...
        >>> ham()  # doctest: +SKIP
        sys.stdout.encoding in spam() is 'latin-1'
        sys.stdout.encoding in ham() is 'utf-8'
        >>>
    """

    if not callable(function):
        if function is not None:
            if encoding is not None:
                # @fixprint("utf-8", encoding="ascii")  # WRONG
                raise TypeError("fixprint() takes 1 positional argument " +
                                "but 2 were given")
            else:
                # @fixprint("utf-8")  # CORRECT
                encoding = function
        return partial(fixprint, encoding=encoding, errors=errors)

    def _setarg(base, stream, argument, value):
        if stream is ANY:
            streams = STREAMS
        else:
            streams = (stream,)

        if argument is ANY:
            arguments = ARGUMENTS
        else:
            arguments = (argument,)

        for a_stream in streams:
            if type(getattr(base, a_stream)) is property:
                setattr(base, a_stream, namedtuple("_Stream", ARGUMENTS))

            for an_argument in arguments:
                setattr(getattr(base, a_stream), an_argument, value)

    def _reopen_stream(old_stream, new_stream):
        old_stream.flush()
        return open(old_stream.fileno(),
                    old_stream.mode,
                    encoding=new_stream.encoding,
                    errors=new_stream.errors,
                    closefd=False)

    @wraps(function)
    def _fixprint(*args, **kwargs):
        saved = namedtuple("_Saved", STREAMS)
        fixed = namedtuple("_Fixed", STREAMS)
        for stream in STREAMS:
            for argument in ARGUMENTS:
                value = getattr(getattr(sys, stream), argument)
                _setarg(saved, stream, argument, value)
                _setarg(fixed, stream, argument, value)

        normalize = lambda encoding: codecs.lookup(encoding).name

        if encoding is not None:
            _setarg(fixed, ANY, "encoding", encoding)
        elif normalize(saved.stdout.encoding) == normalize(ENCODING_ASCII):
            fixed.stdin.encoding = ENCODING_DEFAULT
            fixed.stdout.encoding = fixed.stderr.encoding = ENCODING_DEFAULT

        if errors is not None:
            _setarg(fixed, ANY, "errors", errors)
        elif normalize(fixed.stdout.encoding) != normalize(ENCODING_UTF8):
            _setarg(fixed, ANY, "errors", ERRORS_DEFAULT)

        sys.stdin = _reopen_stream(sys.stdin, fixed.stdin)
        sys.stdout = _reopen_stream(sys.stdout, fixed.stdout)
        sys.stderr = _reopen_stream(sys.stderr, fixed.stderr)

        try:
            result = function(*args, **kwargs)
        finally:
            sys.stdin = _reopen_stream(sys.stdin, saved.stdin)
            sys.stdout = _reopen_stream(sys.stdout, saved.stdout)
            sys.stderr = _reopen_stream(sys.stderr, saved.stderr)

        return result

    return _fixprint

Recommended Posts

Decorator to avoid UnicodeEncodeError in Python 3 print ()
python decorator to retry
To flush stdout in Python
Login to website in Python
Speech to speech in python [text to speech]
Avoid KeyError in python dictionary
Avoid multiple loops in Python
How to develop in Python
Post to Slack in Python
A simple way to avoid multiple for loops in Python
Display numbers and letters assigned to variables in python print
[Python] How to do PCA in Python
Convert markdown to PDF in Python
How to collect images in Python
Python is UnicodeEncodeError in CodeBox docker
How to use SQLite in Python
In the python command python points to python3.8
Try to calculate Trace in Python
How to use Mysql in python
How to wrap C in Python
How to use ChemSpider in Python
6 ways to string objects in Python
How to use PubChem in Python
How to handle Japanese in Python
An alternative to `pause` in Python
[Introduction to Python] How to output a character string in a Print statement
[Introduction to Python] How to use class in Python?
Use print in a Python2 lambda expression
Avoid nested loops in PHP and Python
Install Pyaudio to play wave in python
How to access environment variables in Python
I tried to implement permutation in Python
Method to build Python environment in Xcode 6
I want to print in a comprehension
How to dynamically define variables in Python
How to do R chartr () in Python
Pin current directory to script directory in Python
[Itertools.permutations] How to put permutations in Python
PUT gzip directly to S3 in Python
Just print Python elapsed time in seconds
Send email to multiple recipients in Python (Python 3)
Convert psd file to png in Python
Sample script to trap signals in Python
I tried to implement PLSA in Python 2
To set default encoding to utf-8 in python
How to work with BigQuery in Python
Learn the design pattern "Decorator" in Python
Log in to Slack using requests in Python
How to get a stacktrace in python
How to display multiplication table in python
Easy way to use Wikipedia in Python
How to extract polygon area in Python
How to check opencv version in python
I tried to implement ADALINE in Python
[Python] Pandas to fully understand in 10 minutes
Throw Incoming Webhooks to Mattermost in Python
Module to generate word N-gram in Python
To reference environment variables in Python in Blender
I wanted to solve ABC159 in Python
I tried to implement PPO in Python
How to switch python versions in cloud9