-
Notifications
You must be signed in to change notification settings - Fork 784
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
__anext__
should be able to return &PyAny
#3190
Comments
While I am sympathetic to the simplicity of that approach, the resulting backwards incompatibility (silently compiling existing code to do something different) and the inconsistency between handling of As an alternative, what do you think of adding a third enum variant to |
Agreed there is a backwards compatibility hazard here. It should be noted that the design of I think if we wanted to expand to allow |
(It's worth noting in the past these methods were limited by the design of the |
While technically possible, I think this might end up hard to explain, especially if we use it to keep the I guess in the end this is also a conflict between making the handling of |
I agree that existing code should work identically as much as possible. The consistency, however, is nothing if it makes everyone to work around the quote-unquote consistent interface. I was surprised to learn that PyO3 does have a test for If we can agree that this is a real issue and desirable to fix, I believe my initial suggestion to use an autoref specialization is indeed fully backward compatible:
In comparison this is technically a breaking change because
Isn't this already true for
If a specialization magic is not your taste (I can relate), you can also specially treat |
Yes, this is breaking a change. The enum is not just publicly documented but also not marked as
I am at a loss to which behaviour you refer to exactly? Do you mean If so, then my problem is not just that the existing protocol is complicated, but that I think we are discussing to add an additional twist on top of that. The current contract is:
But now we extend the contract of
Personally, I would prefer to avoid adding another layer to this altogether, hence the suggestion of the option to add (If we do add the third option, using autoref specialization is fine IMHO and I would actually prefer that to matching on type names.) ((If we are back to the drawing board, I could even imagine copying Python's way of doing things, i.e. we always return |
(Of course, adding |
To add some context as to why I think specialization (whatever mechanism is used to implement it) is difficult to explain here: I think that specialization should be used when it does not need explanation at all, i.e. when the special case it captures is only special w.r.t. the quality of implementation (e.g. efficiency) but not w.r.t. the observable effects. |
Oops, yes, I meant to say
Ah, that makes sense. So you want to make the result of specialization visible from the type, without necessary removing that specialization. I'm okay with that. (I'm also not particularly into specialization, but I do feel proc-macros are already quite magical and specialization doesn't change that much ;-)
I think most workarounds (for So, to recap the interim conclusion as I understand:
I believe these are enough to pinpoint what to do next. Please let me know if not. |
While I understand that this might be frustrating, I think we are still exploring the design space for now. If we do decide to include specialization for |
Ah, I thought the design space is not very large given that we are dealing with concrete types and we have only a few ways to alter them (this would be not the case if public traits are involved), and thus those constraints are strong enough. Didn't meant to hurry, we have a lot of time to perfect this :-) |
Rereading PEP 492 it seems to be that This would also map to the Presumably this would be the eventual goal of what we'd want to get to, where PyO3 can package the future into an awaitable automatically. So, the question becomes how do we get there, and avoid breaking users along the way? If we required |
Indeed in that case our design is wrong. Additionally, the example collected in the OP would all fit this pattern. Does anybody know any example where the contract intended by our current API is actually used? If that is not the case, I would say that there is little point in caring about backwards compatibility, especially since If I understand this correctly, there would be no point in keeping (While we are here and discuss this, when |
It looks like the original optionality to
I mostly agree with the caveat that Agreed For |
I think with today's PyO3 I would expect
|
Adding in here from #3202 - would this approach still not conflict when users return error types though? If the value is caught within Py03 is blending two separate protocol implementations into one protocol, when they should be different:
The two conflict with eachother, and it looks like Py03 is currently merging the logic for them both which leads to inappropriate handling at runtime (#3202). As well, adding a convenience layer is useful, but as you alluded to this would obfuscate the actual procedure being implemented. From a user perspective, the |
Using the usual #[pyfn(m)]
fn foo(py: Python<'_>) -> PyResult<PyObject> {
let err = pyo3::exceptions::PyRuntimeError::new_err("foo");
Ok(err.into_py(py))
}
#[pyfn(m)]
fn bar() -> PyResult<PyObject> {
let err = pyo3::exceptions::PyRuntimeError::new_err("bar");
Err(err)
} will make test tests def test_foo():
err = foo()
assert type(err) == RuntimeError
assert "foo" in str(err)
def test_bar():
with pytest.raises(RuntimeError, match = "bar"):
bar() pass. |
As a note for discussion here, https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iternext |
Currently
__anext__
expectsIterANextOutput<_, _>
, which can be only converted fromOption<T1>
orIterANextOutput<T2, T3>
where allT
s implementIntoPy<PyObject>
. But__anext__
frequently returns an awaitable, and whether the result is going to beYield
orReturn
is up to the awaitable and not__anext__
itself.Searching for the existing
__anext__
implementations from GitHub confirms this issue, and everyone seems to come up with their own workaround (I also had one):Therefore ideally we should allow any
T
(that implementsIntoPy<PyObject>
as above), but that impl will conflict with both existing impls. While the impl forIterANextOutput<_, _>
can be merged intoIntoPyCallbackOutput
and so is redundant, the impl forOption<T>
cannot be removed because (Rust)None
should be converted toPyStopAsyncIteration
, not (Python)None
. So the only possibility would be an autoref specialization.On the other hand the value of forcing
IterANextOutput<_, _>
from__anext__
seems unclear. Assuming the last__anext__
did return some awaitable and it yielded some value, the awaitable might or might not know that the iteration has stopped---both possibilities are plausible. The current design is only suitable if the stop is already known. Probably__anext__
should not restrict return types and we should just makeIterANextOutput
implementIntoPy
, ditching theOption<T>
case (with a proper documentation of course).The text was updated successfully, but these errors were encountered: