-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Dynamically wrapped composed methods have no type hints #3023
Comments
I've low-key hated those dynamically allocated methods for a variety of reasons, they're tough to document, auto-completion in editors doesn't work and of course type annotations don't work either. Of the suggestions proposed, I would imagine the That said, I'm curious how matplotlib handles this, I think they started type-annotating their code, and the same methods are used in a variety of objects. |
I agree that calling a property feels weird, even if it is the most straight forward. I can't find an instance of Matplotlib copying a function definition, but it's a dense library, so I may have missed it. It does seem like they use a docstring decorator to copy over function docstrings, but nothing about type hints. I know Plotly uses a lot of copied definitions between functions, but I think they mostly use
Here's a thread I opened about a similar issue in Xarray (but hasn't received any new comments): pydata/xarray#8136. One approach they suggest is to add a I messed around with the signature copier decorator, but I didn't have much luck getting it to work. Maybe using |
Copying my reply from discord here (note have not fully read this thread yet): Yeah... in general dynamic behavior is not conducive to static type checking... In mpl, we do have some aliasing that is handled by a class decorator, such as set_ha which is the short form of set_horizontalalignment (among many others). We have opted to not type hint those things that are aliased dynamically like that. In our case the official guidance is to use the canonical name in the type checked setting, which is type hinted. In mpl's case, we technically have the ability to put the type hints in the .pyi files, but I chose not to since it seems silly to expand out the class decorator, and it would make it harder for us to inline type hints should we ever choose to do so (no active plans though on that front) As for CI, we have a file with specific ignores (for stubtest, which is mypy's program to validate .pyi files against implementation), which actually includes almost everything except the aliases and such, because we generate the most common cases with a script, so that individual PR authors don't have to think about the stubtest exceptions as often:
In this case, I think perhaps rewriting these so that they are more static friendly may be possible... Not finding the obvious solution immediately, but feels possible, I think one step would be unrolling the loop, which I think will help static checkers... In mpl we have a similar pass through implented as _axis_method_wrapper for various things that are wrapped in Axes that are actually canonically held in the x/y Axis. However in this case I just typed them in pyi |
Fwiw I have actually found dynamically generating .pyi files is not as bad as it seems: |
oh wow sorry i was just doing more dynamic typing stuff and came back and realized i really misread what was going on. i thought the problem was for general dynamically created methods, not for the case in OP where the methods are known beforehand. Not sure if y'all were looking to like actually change the dynamically assigned attrs and handle that in a different way, but here's another option that involves no changes to the runtime code. It's a little uh verbose (dynamically assigning in a loop doesn't work for the same reason pyright can't infer from the from typing import TYPE_CHECKING
# ...
class GraphicsLayoutWidget(GraphicsView):
# ...
def __init__(self, ...):
# ...
if TYPE_CHECKING:
nextRow = GraphicsLayout.nextRow
nextCol = GraphicsLayout.nextCol
nextColumn = GraphicsLayout.nextColumn
addPlot = GraphicsLayout.addPlot
addViewBox = GraphicsLayout.addViewBox
addItem = GraphicsLayout.addItem
getItem = GraphicsLayout.getItem
addLayout = GraphicsLayout.addLayout
addLabel = GraphicsLayout.addLabel
removeItem = GraphicsLayout.removeItem
itemIndex = GraphicsLayout.itemIndex
clear = GraphicsLayout.clear
|
Short description
Starting in pyqtgraph 0.13.5, dynamically wrapped class properties/methods raise errors in type checkers such as Pylance/Pyright. Examples of this include
GraphicsLayoutWidget
,ImageView
, andPlotItem
.Code to reproduce
Behavior
nextRow()
is not originally defined inGraphicsLayoutWidget
, but is rather copied over from the internalGraphicsLayout
at runtime.pyqtgraph/pyqtgraph/widgets/GraphicsLayoutWidget.py
Lines 52 to 54 in 3d9831a
In the past, I suspect Pylance never compained about this issue because it was unable to make type inferences without the Qt type hints.
I suspect that this is with the addition of Qt type hints (a much appreciated addition), Pylance is properly trace type inheritance, and can figure out that the classes appear to be missing these methods.
Suggestions
I tried adding type hinted versions of
__getattr__()
to proxy specific methods, but I didn't have any luck. It seems that once a class defines a__getattr__()
method without type hints (or use easily inferable types), most type checkers "give up" and say it returnsUnknown
orAny
.I have a few suggestions for potential ways forward:
Manually redefine each method with arguments that pass to the composed object. This seems like a lot of work and adds another layer of maintenance/point of failure.
A slightly better implementation of the method 1, could be to use
*args
and**kwargs
in method redefinitions and use a decorator to copy over the type signature with something like this: Type hints and tooltips for decorators/wrappers/proxy objects microsoft/pyright#4426 (reply in thread)Use
@property
on methods for pass-through. This does screw up syntax highlighting for certain color schemes a bit, as the methods are seen as callable properties instead of actual methods.__getattr__()
to returnAny
. It loses some type checking utility, but at least it doesn't show errors.Tested environment(s)
The text was updated successfully, but these errors were encountered: