12

I tried typing.IO as suggested in Type hint for a file or file-like object?, but it doesn't work:

from __future__ import annotations
from tempfile import NamedTemporaryFile
from typing import IO

def example(tmp: IO) -> str:
    print(tmp.file)
    return tmp.name


print(example(NamedTemporaryFile()))

for this, mypy tells me:

test.py:6: error: "IO[Any]" has no attribute "file"; maybe "fileno"?

and Python runs fine. So the code is ok.

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
  • The problem is that the object returned by `NamedTemporaryFile` is not really _file-like_. It has only `close`, `delete`, `file` and `name` public methods, when `typing.IO` has `read`, 'write', 'seek' and others. – sanyassh Oct 19 '20 at 14:22
  • @sanyassh The documentation for `NamedTemporaryFile` says it returns a file-like object. Moreover, the object does have `read`, `write`, and `seek`. `NamedTemporaryFile` totally conforms to the `IO` protocol, and mypy even acknowledges this. The problem is that `NamedTemporaryFile` has at least one extra attribute (`file`) that isn't available on `IO`. – Brian McCutchon Feb 10 '22 at 18:04

2 Answers2

11

I don't think this can be easily type hinted.

If you check the definition of NamedTemporaryFile, you'll see that it's a function that ends in:

return _TemporaryFileWrapper(file, name, delete)

And _TemporaryFileWrapper is defined as:

class _TemporaryFileWrapper:

Which means there isn't a super-class that can be indicated, and _TemporaryFileWrapper is "module-private". It also doesn't look like it has any members that make it a part of an existing Protocol * (except for Iterable and ContextManager; but you aren't using those methods here).

I think you'll need to use _TemporaryFileWrapper and ignore the warnings:

from tempfile import _TemporaryFileWrapper  # Weak error

def example(tmp: _TemporaryFileWrapper) -> str:
    print(tmp.file)
    return tmp.name

If you really want a clean solution, you could implement your own Protocol that includes the attributes you need, and have it also inherit from Iterable and ContextManager. Then you can type-hint using your custom Protocol.


* It was later pointed out that it does fulfil IO, but the OP requires attributes that aren't in IO, so that can't be used.

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
1

Another approach would be to use:

import typing

def example(tmp: typing.IO[typing.Any]) -> str:
    print(tmp.file)  # type: ignore
    return tmp.name  # type: ignore

Admittedly, it's flawed in terms of strong typing, but this may be the best case approach?

Paco
  • 602
  • 1
  • 9
  • 19