Search code examples
pythonstreamstdoutstdin

How to instantiate an io.TextIOWrapper object with a name attribute?


import sys

print(sys.stdin)
print(type(sys.stdin))
print(sys.stdin.name)
print(sys.stdin.__dict__)

When the above is executed, the following is the output:

<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>
<class '_io.TextIOWrapper'>
<stdin>
{'mode': 'r'}

So from the above snippet and output, I can see that name is an attribute to the _io.TextIOWrapper instance representing sys.stdin. And from the documentation on io.TextIOWrapper (via $ pydoc io.TextIOWrapper for ex.), it does list name as a data descriptor. However for whatever reason, name doesn't show up as an item in its __dict__.

When I create an instance of io.TextIOWrapper manually using for example:

import io

a = io.TextIOWrapper(io.BytesIO())
print(a)
a.name

<_io.TextIOWrapper encoding='UTF-8'> is printed. But the a.name line throws the error: AttributeError: '_io.BytesIO' object has no attribute 'name'; the AttributeError I expected, but I didn't it expect to say it was an _io.BytesIO object.

I'd then tried creating a subclass and attaching a name attribute manually, like so:

import io


class NamedTextIOWrapper(io.TextIOWrapper):

    def __init__(self, buffer, name=None, **kwargs):
        self.name = name
        io.TextIOWrapper.__init__(self, buffer, **kwargs)


input = io.BytesIO('abc')
stdin = NamedTextIOWrapper(input, name='<stdin>', encoding='utf-8')

print(stdin.name)

However this runs into: AttributeError: attribute 'name' of '_io.TextIOWrapper' objects is not writable.

Ideally, I'd also like to be able to maintain the mode attribute seemingly available in the sys.stdin instance in a manually instantiated io.TextIOWrapper object as well. And also for the sys.stdout equivalent, which I assume would be just the same except the name should just be set to '<stdout>' and the mode to 'w'.


Solution

  • You can override the __getattribute__ method with one that returns the name key of the object's attribute dictionary when the name attribute is requested:

    class NamedTextIOWrapper(io.TextIOWrapper):
        def __init__(self, buffer, name=None, **kwargs):
            vars(self)['name'] = name
            super().__init__(buffer, **kwargs)
    
        def __getattribute__(self, name):
            if name == 'name':
                return vars(self)['name']
            return super().__getattribute__(name)
    

    so that:

    input = io.BytesIO(b'abc')
    stdin = NamedTextIOWrapper(input, name='<stdin>', encoding='utf-8')
    print(stdin.name)
    

    outputs:

    <stdin>