Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continuation of WIP: Type Annotations #2941

Closed
wants to merge 84 commits into from

Conversation

wiredfool
Copy link
Member

Fixes #2687

  • make typecheck, i.e. mypy -2 --ignore-missing-imports src/PIL/Image.py works

@aclark4life
Copy link
Member

With all the work done by you and @neiljp (and others?) and at least one other comment indicating this is something people want, I'd say merge-away for April 1 release.

@hugovk
Copy link
Member

hugovk commented Mar 24, 2018

I've not used typing in Python before, but no objections.

Of course, in the future we'll need to make sure new/changed code updates the types. Some instructions in CONTRIBUTING.md would be helpful, and we'll need to make sure contributors' PRs keep things updated, or update them ourselves.

I think it would be useful to check the typing on the CI. I'd suggest a new Travis job that just does the type checking, and no other tests. That way it would run and pass or fail quickly, and wouldn't muddy the waters of whether the actual unit tests pass or fail.

@@ -24,6 +24,22 @@
# See the README file for information on usage and redistribution.
#

if False:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this if False: going to remain? Does it need to?

A comment to explain why would help.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this format, these are read by mypy, but don't introduce the dependencies of inclusion at runtime.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be this:

import typing

if typing.TYPE_CHECKING:
    ...

src/PIL/Image.py Outdated
@@ -65,7 +82,7 @@ def __getattr__(self, id):
PILLOW_VERSION))

except ImportError as v:
core = _imaging_not_installed()
core = _imaging_not_installed() # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pep8: two spaces before inline comment, here and elsewhere

@@ -706,21 +743,23 @@ def tobytes(self, encoder_name="raw", *args):

:param encoder_name: What encoder to use. The default is to
use the standard "raw" encoder.
:param args: Extra arguments to the encoder.
:param *args: Extra arguments to the encoder. May be one tuple,
or individual arguments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End sentence with . for consistency

src/PIL/Image.py Outdated

if data is None:
raise ValueError("missing method data")

im = new(self.mode, size, fillcolor)
if method == MESH:
# list of quads
for box, quad in data:
data_mesh = None # type: List[Tuple[LURD,LURD]]
data_mesh = data # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here, setting data_mesh twice?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as I mentioned above. If you wish to maintain the previous comment of what it "should" be, ideally, then perhaps just put it as the 'post-ignore' comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, the first sets the type, and the second actually sets the variable, but we're forcing the typechecker to accept that we know that data is the correct type. This sort of idiom is required(?) when we're unpacking a datastructure that's goat a bunch of type overloads.

Copy link
Contributor

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great followup to our earlier work, and thanks to @wiredfool for persevering! However this is now a very big PR and I partly wonder if it would be good to split it if possible, or squash some commits together. That said, that may be in part due to the size of Image.py.

One thing I am really intrigued by, is the experience of anyone involved of the process - have you identified any latent issues in doing this? is this primarily good for documentation? pushing you towards simpler/better implementations? ...or just an interesting experiment that may as well get merged at this point?

@@ -24,6 +24,22 @@
# See the README file for information on usage and redistribution.
#

if False:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this format, these are read by mypy, but don't introduce the dependencies of inclusion at runtime.

@@ -42,6 +58,7 @@ class DecompressionBombError(Exception):
class _imaging_not_installed(object):
# module placeholder
def __getattr__(self, id):
# type: (str) -> Any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type of this can be NoReturn; this may require the mypy_extensions module based on my experience in Zulip, though it is part of PEP484?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not enclined to worry about the exact type here, as this is just a placeholder object that emits errors when we don't have the c module.

Image.__init__(self)
self.tile = []
self.tile = [] # type: List[Tuple] ## FIXME Should tile be in __init__ & _new?
# FIXME TYPING: ^ Maybe Optional[List[Tuple[Text, LURD, int, Optional[Tuple]]]] ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been a while since I looked at this, but do these FIXME TYPING comments still apply, eg here?

src/PIL/Image.py Outdated
@@ -1261,11 +1326,12 @@ def getpalette(self):
if bytes is str:
return [i8(c) for c in self.im.getpalette()]
else:
return list(self.im.getpalette())
return list(self.im.getpalette()) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using 'type: ignore', good style appears to be to put a reason afterwards, eg. it could be that types aren't available, are doing dynamic 'patching' which won't ever work, or is eg. a mypy bug! The format used in Zulip is:

some_code  # type: ignore # some explanatory text or link

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several ignores here that may be mypy bugs. I did put those in when I'd spent enough time on it, and it's clear what types we had but not clear how to force mypy to deal with it.

return self.im.histogram(extrema)
return self.im.histogram()

def offset(self, xoffset, yoffset=None):
# type: (Any, Any) -> Any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this has been removed, but this could potentially be eg. (int, Optional[int]) -> NoReturn for completeness?

src/PIL/Image.py Outdated
else:
# constant alpha
try:
self.im.fillband(band, alpha)
alpha_int = None # type: int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here as above - this line is superfluous?

src/PIL/Image.py Outdated
@@ -1723,12 +1832,15 @@ def resize(self, size, resample=NEAREST, box=None):
):
raise ValueError("unknown resampling filter")

size = tuple(size)
size = tuple(size) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re the following 'Typing note', the parameter could actually be typed as Iterable (or Sequence, if that's plausible too) instead. However that's for backwards compatibility rather than 'what you should pass in', right?

src/PIL/Image.py Outdated

if data is None:
raise ValueError("missing method data")

im = new(self.mode, size, fillcolor)
if method == MESH:
# list of quads
for box, quad in data:
data_mesh = None # type: List[Tuple[LURD,LURD]]
data_mesh = data # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as I mentioned above. If you wish to maintain the previous comment of what it "should" be, ideally, then perhaps just put it as the 'post-ignore' comment?

pass

def point(self, s):
# type (T) -> T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is T here? This doesn't look familiar from when I was working on this. My instinct is a TypeVar, but T is not very descriptive?

src/PIL/Image.py Outdated
# and obj_bytes, if it exists, is a bytes exporting a buffer interface.
# Unfortunately at this point, there's no protocol for the buffer interface
# and due to it's c-level implementation, I don't see how to make one.
return frombuffer(mode, size, obj_bytes or obj, "raw", rawmode, 0, 1) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe file an issue in typeshed re this, if there's not one already? If typeshed is the best place - maybe even python/typing? (for more general language/typing queries)

@@ -78,3 +78,4 @@ Tests/images/README.md
Tests/images/msp
Tests/images/picins
Tests/images/sunraster
/.mypy_cache/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section of .gitignore is for "Extra test images installed from pillow-depends/test_images". Better place is "Unit test / coverage reports"

pass

def filter(self, image):
return image.im
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image argument is already an image core object.

@wiredfool
Copy link
Member Author

One thing I am really intrigued by, is the experience of anyone involved of the process - have you identified any latent issues in doing this? is this primarily good for documentation? pushing you towards simpler/better implementations? ...or just an interesting experiment that may as well get merged at this point?

I wouldn't say that it's pushing things to be a simpler implementation, because in many cases we're stuck with the interface we have, and that interface was not designed to be type safe. There are a few idioms, like a single type static assignment that work, and are at least one way to do things. But they aren't simple, they don't seem pythonic, and they obscure the flow of what's going on.

I could see where this might guide IDEs to make better suggestions. I can't recall if this process actually uncovered any bugs in Pillow, but my feeling at the end of it was more that this was an interesting exercise than a leap forward in code quality.

@radarhere radarhere modified the milestones: 5.1.0, 5.2.0 Apr 10, 2018
@radarhere radarhere removed this from the 5.2.0 milestone Jul 2, 2018
@python-pillow python-pillow deleted a comment from codecov bot Aug 27, 2019
@Stadly
Copy link

Stadly commented Oct 3, 2020

What's the status on this? Type annotations for Pillow would be great!

@hugovk
Copy link
Member

hugovk commented Oct 16, 2023

This has conflicts and a lot has changed in typing since this was opened. Let's close in favour of a more incremental approach along the lines of #2625 (comment). Thanks anyway for all your work here.

@hugovk hugovk closed this Oct 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants