-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Conversation
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. |
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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]]]] ? |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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/ |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
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. |
What's the status on this? Type annotations for Pillow would be great! |
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. |
Fixes #2687
mypy -2 --ignore-missing-imports src/PIL/Image.py
works