-
-
Notifications
You must be signed in to change notification settings - Fork 143
GH1226 Add Styler.map to the stubs #1228
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
Conversation
pandas-stubs/io/formats/style.pyi
Outdated
@@ -71,6 +71,9 @@ class Styler(StylerRenderer): | |||
formatter: ExtFormatter | None = ..., | |||
) -> None: ... | |||
def concat(self, other: Styler) -> Styler: ... | |||
def map( | |||
self, func: Callable, subset: Subset | None = ..., **kwargs: 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.
self, func: Callable, subset: Subset | None = ..., **kwargs: Any | |
self, func: Callable[[Scalar], str], subset: Subset | None = ..., **kwargs: dict[str, Any] |
docs indicate what the callable should look like, and kwargs has to be a dict
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 think this is a little tricky since the example on the docs mention that you can pass kwargs to the function so the signature would be something like, Callable[[Scalar, kwargs], str | None].
I will type it in as Callable[[Scalar], str | None] since the docs itself mention the return type None and then we can see if users complain. What do you think?
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 think this is a little tricky since the example on the docs mention that you can pass kwargs to the function so the signature would be something like, Callable[[Scalar, kwargs], str | None]. I will type it in as Callable[[Scalar], str | None] since the docs itself mention the return type None and then we can see if users complain. What do you think?
Can we do this with an overload? If kwargs
is not specified, then we use a single argument in the Callable
. If kwargs
is specified as a dict
, then a different Callable
signature is needed.
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.
kwargs has to be a dict
As far as I know kwargs are annotated with the type of the values so if it can be only ints, then it's kwargs: int
, if it can be anything then it's kwargs: Any
I am pretty sure we don't expect only dictionaries to be passed in kwargs here so Any
seems correct
https://peps.python.org/pep-0484/#arbitrary-argument-lists-and-default-argument-values
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 example in the docs passes a str and not a dict so indeed we may be a bit lax.
Co-authored-by: Irv Lustig <irv@princeton.com>
pandas-stubs/io/formats/style.pyi
Outdated
@overload | ||
def map( | ||
self, | ||
func: Callable[..., str | None], |
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.
@Dr-Irv I could not find how to type a function that takes Scalar and some other stuff, I tried [Scalar, ...]
but it was not happy, feel free to recommend something better.
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 think this is usually done using Protocol
, I tried writing this:
class _MapCallable(Protocol):
def __call__(self, first_arg: Scalar, /, *args: Any, **kwargs: Any) -> str | None: ...
def map(
self,
func: _MapCallable,
subset: Subset | None = ...,
**kwargs: Any,
) -> Styler: ...
but it fails the test
tests/test_styler.py:256: error: Expression is of type "Any", not "Styler" [assert-type]
tests/test_styler.py:257: error: No overload variant of "map" of "Styler" matches argument types "Callable[[str | bytes | date | datetime | timedelta | <7 more items> | complex | integer[Any] | floating[Any] | complexfloating[Any, Any], str], str | None]", "str" [call-overload]
tests/test_styler.py:257: note: Possible overload variants:
tests/test_styler.py:257: note: def map(self, func: Callable[[str | bytes | date | datetime | timedelta | <7 more items> | complex | integer[Any] | floating[Any] | complexfloating[Any, Any]], str | None], subset: _IndexSlice | slice[Any, Any, Any] | tuple[slice[Any, Any, Any], ...] | list[Any] | Index[Any] | None = ...) -> Styler
tests/test_styler.py:257: note: def map(self, func: _MapCallable, subset: _IndexSlice | slice[Any, Any, Any] | tuple[slice[Any, Any, Any], ...] | list[Any] | Index[Any] | None = ..., **kwargs: Any) -> Styler
Maybe this will be helpful to figure something out.
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 idea of using the Protocol
works if we change the test. Instead of
def color_negative(v: Scalar, color: str) -> str | None:
if we use
def color_negative(v: Scalar, /, color: str) -> str | None:
then it is fine. Note that pyright
accepts the func without the /
defined, but mypy
doesn't, so this is a workaround to make mypy
happy.
@loicdiridollou change the test that way and put the _MapCallable
near the other Protocol
definition in that file and we should be good to go.
@Molkree Thanks for the suggestion!
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.
Note that
pyright
accepts the func without the/
defined, butmypy
doesn't, so this is a workaround to makemypy
happy.
I had to double check that and I agree that this is mypy bug. According to these typing rules callable with standard argument of matching type should be assignable to the positional-only argument of matching type, same for kwargs but because we typed them as Any
everything should be assignable to everything.
I think it's this bug specifically: python/mypy#12525 (or a variation of it, error messages are not the same)
You learn something new everyday!
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.
Thanks for all the feedback here, indeed I learn about those intricacies and mypy issues!
My only fear is that it would force people to have the argument-based function with the /
, but I think it is worth trying out and seeing if we get complaints from the user base.
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.
Thanks for all the feedback here, indeed I learn about those intricacies and mypy issues! My only fear is that it would force people to have the argument-based function with the
/
, but I think it is worth trying out and seeing if we get complaints from the user base.
It only forces people who use mypy
to include /
. If you use pyright
, it isn't required.
pandas-stubs/io/formats/style.pyi
Outdated
@overload | ||
def map( | ||
self, | ||
func: Callable[..., str | None], |
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 idea of using the Protocol
works if we change the test. Instead of
def color_negative(v: Scalar, color: str) -> str | None:
if we use
def color_negative(v: Scalar, /, color: str) -> str | None:
then it is fine. Note that pyright
accepts the func without the /
defined, but mypy
doesn't, so this is a workaround to make mypy
happy.
@loicdiridollou change the test that way and put the _MapCallable
near the other Protocol
definition in that file and we should be good to go.
@Molkree Thanks for the suggestion!
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.
thanks @loicdiridollou
pandas.io.formats.style.Styler.map
is missing #1226assert_type()
to assert the type of any return value