| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Thank you for trying to fix this. I fully agree that if we could get this working, we should merge it. |
Sorry, something went wrong.
CodSpeed Performance ReportMerging #910 will not alter performanceComparing LennartGevers:clean-decorator-behavior (e98f882) with main (355584e) Summary✅ 6 untouched |
Sorry, something went wrong.
✅ PR Title Formatted CorrectlyThe title of this PR has been updated to match the correct format. Thank you! |
Sorry, something went wrong.
|
Hey @basnijholt, this branch is ~90% there, but I think that the current solution is inferior to an alternative but more intricate design that’s worth discussing. The main issue is that once you start using renames, defaults, or bound, the function’s signature changes at runtime and Python’s ParamSpec can’t express that. So we can’t properly expose the updated signature to type checkers or IDEs. Example: @pipefunc("f", update_defaults={"b": 0.5})
def f(a: int, b: float = 2) -> int:
return a**b
f(4) # 16
f.run(4) # 2This works, but it feels weird that f and f.run behave differently. The concept of having a Pipeline context behavior and "normal" behavior does not feel intuitive to me. But..., as soon as signature-modifying args are involved, we lose the ability to type things statically, meaning that this initially look like the only option. I think the key insight is that there are really two kinds of attributes: Right now they’re all handled in the same class, which complicates typing and behavior. So my idea is to split them: @func would be the minimal decorator that keeps IDE hovers and types perfect, while @pipefunc(...) or PipeFunc(...) gives you the full power and mutability. As soon as a Func is passed to a Pipeline it is promoted to a PipeFunc so that it can respond to Pipeline.add_mapspec_axis and so on. We could alternatively overload pipefunc so it returns a Func when no signature-modifying args are given, and a PipeFunc otherwise. @overload
def pipefunc(..., renames=None, defaults=None, bound=None) -> Func: ...
@overload
def pipefunc(..., renames=..., defaults=..., bound=...) -> PipeFunc: ...But to be really honest, I think that a function which effectively has stateful behaviour is an anti-pattern. This should be reserved exclusively for OOP. So maybe we should only advocate instantiating PipeFuncs with its constructor. I think the sum of all of these behaviours (side effects, mutability, no signature and docs, ...) is why people often prefer to instantiate a PipeFunc with its constructor anyways. Differentiating the Func and PipeFunc layer would separate concerns nicely, simplify the internals, and avoid weird side effects from mutating a live PipeFunc, like @pipefunc("f",)
def f(a: int, b: float = 2) -> int:
return a**b
h = f
h.update_defaults({"b": 0.5})
f(4) #2Would do you think about that? @basnijholt |
Sorry, something went wrong.
…960) * ENH: Preserve signature and docs of functions wrapped by `@pipefunc` - `PipeFunc` is now `Generic[P, R]` (ParamSpec), so type checkers and IDEs see the wrapped function's parameters and return type on `__call__`, while `PipeFunc` methods like `update_renames` remain fully typed. - The wrapped function's `__doc__` is exposed on the instance for `help()` and Jupyter `?` introspection (skipping class docstrings of callable instances). - Added a `__wrapped__` property following the `functools.wraps` convention; `__signature__` (which reflects renames) still takes precedence for `inspect.signature`. - Set `__test__ = False` so pytest does not collect `PipeFunc` objects wrapping functions named `test*` (pytest follows `__wrapped__`). Note: calls using renamed/scoped parameters are now flagged by type checkers since the static signature is the original one; tests covering that dynamic behavior gained targeted `type: ignore` comments. Continues the work from #910 by @LennartGevers; addresses part of #902. Co-authored-by: Lennart Gevers <lgevers@uni-muenster.de> * DOC: Document static typing and IDE support of `@pipefunc` * DOC: Renamed-parameter calls are flagged even on unannotated functions --------- Co-authored-by: Lennart Gevers <lgevers@uni-muenster.de>
|
@LennartGevers with #960 merged, the typing part of your #910 is now in main (you're credited as co-author). Thanks for the groundwork, what landed is basically your ParamSpec approach:
Good news on the tradeoff you described in the PR text: with current pyright, hovering the name shows the docstring even when the decorator returns PipeFunc[P, R], so we get docs and working methods. Might be worth re-checking your demo.py, this seems to have improved on their side since October. About the Func/PipeFunc split: I see the appeal but I'd rather not maintain and explain two public concepts when the problem only affects directly calling a renamed PipeFunc. Your other point about __call__ semantics and mutability is a real one, but it's a breaking change, so I'd park it with the 1.0 ideas in #513. Closing #910 in favor of this. Thanks again! Drafted with the help of Claude (Fable 5). |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Hi @basnijholt,
I gave it a shot to make the @pipefunc decorator less invasive, so that the annotations and docs are not removed, as mentioned in #902.
Exposing the annotations is quite easy if done correctly with ParamSpec for the input annotations and a normal TypeVar for the output annotation.
Exposing the documentation is a bit subtle and I currently see two different approaches here.
In the case of
if the documentation should be readable when hovering the mouse over "function" on the last line, then the output annotation of the pipefunc function needs to be
but in that case you will get a type checking error for things like
on the other hand, if we use the annotation
then
but then the documentation will only appear when the cursor is within the brackets (idk how to explain this, just check the demo.py file I pushed). I think that this would always be the standard behavior for PipeFuncs instantiated with PipeFunc.__init__.
I just wanted to push this draft for a quick review before diving into the subsequent changes that would still be needed so that this PipeFunc implementation also works within a Pipeline.