python / typeshed

Collection of library stubs for Python, with static types
Other
4.32k stars 1.73k forks source link

Tkinter `_Image` hack #11721

Open srittau opened 6 months ago

srittau commented 6 months ago

Currently, tkinter uses a hack to mark classes compatible with the image parameter:

https://github.com/python/typeshed/blob/130a04905c0cab48604ac7be1f3d18ce96567c68/stdlib/tkinter/__init__.pyi#L3276-L3280

Classes implementing this interface are expected to derive from this stub-only class (or one of its sub-classes). Our Pillow stubs did so. Unfortunately, now that Pillow has its own type annotations, I think it's unreasonable for them to do so at runtime. I think our best bet is to change change _Image to Any here, as this interface can't be represented in our type system.

Cc @Akuli

Akuli commented 6 months ago

This seems reasonable. Basically, this type alias would be changed to Any:

https://github.com/python/typeshed/blob/130a04905c0cab48604ac7be1f3d18ce96567c68/stdlib/tkinter/__init__.pyi#L181

The most common use cases for tkinter images are setting a window icon with wm_iconphoto() and displaying images in the UI with various image=... keyword arguments. These would become basically Any-typed. IMO that's not a huge problem, because if you do these things wrong, you typically get an error when the program starts, so at least it fails loudly and early.

Another option, if it works, is to just import Pillow's type in tkinter stubs with a # type: ignore. Mypy will treat it as Any when pillow is not installed, but I'm not sure what other type checkers would do.

Akuli commented 6 months ago

One more option would be to make a protocol that matches the width, height and paste methods of PIL.ImageTk.PhotoImage. Something like this:

class _PillowPhotoImage:
    def width(self) -> int: ...
    def height(self) -> int: ...
    def paste(self, im: Any, /) -> None: ...

It's unlikely that anything matches this accidentally, and autocompletions will show a somewhat useful _PillowPhotoImage.