On 2021-10-01 8:26 a.m., Steven D'Aprano wrote: > On Thu, Sep 30, 2021 at 01:25:42PM -0300, Soni L. wrote: > > > def foo(): > > raise Bar > > def baz() with Bar: > > foo() > > baz() > > > > would make a RuntimeError, because foo raised a Bar and the VM sees that > > Bar is in baz's with. > > So the syntax "with SomeException" in a function declaration would mean > that the function can not raise SomeException? > > Wouldn't "without SomeException" be a less misleading name then? > > > > *except "raise" opcodes SKIP checking these (within the context of the > > function), so the following: > > > > def baz() with Bar: > > raise Bar > > baz() > > > > raises a Bar, not a RuntimeError from a Bar. > > Every time I think I understand your proposal, I read some more about > it, and get more confused. > > So the "with SomeException" syntax guarantees that the function *won't* > raise SomeException (raising RuntimeError instead), unless it does raise > SomeException, which under some unspecified conditions it will allow it > through. > > And this syntax can only apply to an entire function at a time, > you can't apply it to a block of code like a with-statement unless you > move the entire block into its own function. > > > > You can then document your exceptions as you normally do: "This raises > > Bar when so and so are invalid", and then add "with Bar" to your > > function. the VM then makes sure that Bar is only raised when so and so > > are invalid, but under no other circumstances. > > Wait. This is even more magical. So if I document "this only raises Bar > if P == NP" the compiler will solve the P versus NP problem? > > https://en.wikipedia.org/wiki/P_versus_NP_problem > > Okay, I jest a bit. A more realistic example: > > > def myfunc(s:str) with ValueError: > """This raises ValueError only if s is a palindrome.""" > assert isinstance(s, str) > do_stuff() # Guard: ValueError --> RuntimeError > if s == s[::-1]: > raise ValueError("spam") # No guard. > n = math.sqrt(float(s)) # Guard: ValueError --> RuntimeError > ... # more code here is still guarded > > > Surely you aren't suggesting that the compiler infers meaning from the > natural language docstring, but if not, how does it know which parts of > the function to guard and which parts not to? > > How does the VM know that ValueError("spam") is okay but > ValueError("math domain error") is not? What is the actual criteria used > to allow some ValueErrors through and not others?
It's not the ValueError(...). It's the location of the "raise". > > > e.g. someone > > monkeypatched your function to try and raise a Bar outside of the place > > it was meant to raise a Bar? it'll raise a RuntimeError instead. > > So if my function is this: > > def function(arg) with ValueError: > """Raises ValueError if arg == 0.""" > if arg = 0: > raise ValueError > return arg + 1 > > you are saying that if I monkeypatch it to this: > > def patched(arg): > if arg == 0: > return 1 > raise ValueError > > function = patched > function(99) > > that somehow it will raise RuntimeError? I don't want to believe that > this is what you actually mean, but if it isn't this, then I don't know > what you do mean. > > I wish your proposals and ideas would be more *precise* in their > specifications. This is not the first time that I have found it very > hard to work out precisely what you are suggesting and what you are not. > > We did specify it: "raise" in body raises anything, regardless of the value matching something in the "with". deeper exceptions that match those in "with" get caught and wrapped in a RuntimeError. We're saying if your function is this: def function(arg) with ValueError: if arg == 0: raise ValueError return foo(arg) + 1 and someone monkeypatches it: def foo(arg): raise ValueError then your function will raise a RuntimeError. Could this be added to context managers instead? Could "raise" within "with" call the context manager with the exception? Probably! with foo: raise Bar # becomes semantically equivalent to with foo: exception = Bar # break out of the context without calling __exit__, and then... foo.__raise__(exception) (the default __raise__ would just call __exit__ with the exception and pretend like everything is normal.) This would be both more efficient and more accurate than doing runtime reflection to figure out where the exception came from. Specifically, this allows re-raising, whereas runtime reflection doesn't. But with generators having an implicit "with StopIteration", it seems to make more sense to generalize that to other exceptions than to tweak context managers to be able to optionally (not) intercept "raise" statements. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/IWX6456U6RCGREDCFLBSKWLAMQGLCDRF/ Code of Conduct: http://python.org/psf/codeofconduct/