 So this is my talk, Exception Groups and why we need them. I'm gonna tell you about a new feature in Python 3.11 that's gonna come out in October. And it's called Exception Groups, a little bit about me. Like he said, my name is Orhan. I come from Israel. I work at the security and I've been doing Python since the military days six years ago. And then I continue to do it in the next six years in my last three jobs. And the weird thing about me is that I love exceptions. Now that might be weird to you because you link exceptions immediately to the problems that you had. Exceptions are a tool essentially that tells you when a problem has happened and it lets you both handle that problem and debug it more easily. So other languages might not have the robustness that Python has when it comes to exceptions. Python actually gives you a lot of information when it raises an exception. As you can see here, you can see the type of the exception. You can see the traceback and which line it happened. You can see the message of the exception and you can even see the local variables in each frame of the stack where it was raised from. So you can even access that Europe Python variable when you raise an exception that has nothing to do with it. So it gives you a lot of information that you can use. So what do exception groups add to that equation? Why do we need to group these exceptions? Why is one not enough? Let's see the PEP for a second and what it says about exception groups. So as you can see here, it tells you that the exception groups allow us to raise and handle multiple unrelated exceptions simultaneously. You can see a little bit here how they're raised and how they're handled with this except star. These are basically the two main additions to the Python language, the exception group and the except star and we're gonna see how they interact exactly. All right, so let's dig into it. First of all, let's see how we can create an exception group and so you can see here that we create an exception group that we need to pass it two parameters, right? The first one is the L message and the second one is a list of exceptions that we will raise. Okay, you can see that we can even pass exception groups as part of these, right? As part of the list of exceptions. That is because exception groups themselves are an exception. They're an instance of exception, which means you can do anything that you can do with a regular exception with them. You can raise them, you can get their message, you can print their trace tag and you have some additional things too. You can see the list of exceptions. It returns the tuple of exceptions and it has some additional methods. For example, this subgroup method will return to you all of the different value errors that were in the exception group or in nested exception groups as well. Okay, this is not supposed to happen. Okay, so now we have exception groups but I'm gonna show you why it's not enough, okay? If we will say, okay, we have some case where we need multiple exceptions, let's assume so. For example, you have this classic function, Smerson Tuesday and if it's Tuesday, it will raise an exception group and otherwise it will raise a normal exception. Now when I handle exceptions, I need to know every time is it an exception group or is it a regular exception that adds this is instance to my code everywhere and it's a bit messy. That's not what we really want to do. We don't want our code to look like that. What do we do? We have accept star. Accept star, we look inside that exception group and it will extract part of the exceptions that match on what we give it. So this accept star memory arrow will only catch the memory arrows out of the group. The rest of them will continue to be raised. But since we have two accept star clauses here, both the accept star memory arrow and the accept star connection arrow, they will all be caught right here and as you can see, we actually enter both of the clauses. This is different from the regular accept where you can only enter one of them. Here you can enter multiple of them and that means a couple of things. First of all, you can't return or break or continue while you're inside an accept star clause because that might hurt the other exceptions that are being raised. So there's a limitation on that and let's see what's more. When we raise a regular exception, it will also be caught in accept star clauses. There is one difference though than a regular accept and it is that this exception will be actually inserted into an exception group. As you can see, accept star actually returns an exception group with all the exceptions that matched. So here you will still get an exception group even though you raise the normal exception. Now you don't need to check in accept star clauses if it's a regular one or if it's a group. It's always a group. Okay. Let's see some use cases. I mean, it's nice, sure exceptions in a group but I guess you've all been coding for some time and you never used it and why do you need it actually? Let's see some use cases. The first one is retries. Do you have some retries in your code who here has retries in their code? Okay, that's a lot of people. So right here we're gonna see an example. Let's assume you have some retry that is from a library. Okay, so you can't actually touch the exception handling yourself and it wants to get some URL. It gets the number of retries that it wants to do and then it will try to get a URL every time that it fails. It tries again until it gets to the third time and in this case and then it will raise the last exception. Okay, so what is the problem here? The problem is that we don't see the former exceptions so we won't be able to handle them in case we want to. For example here, let's assume that it returns us authorization error twice and then too many requests. Okay, I'm not sure why it would do that but okay. Now if we try to get that URL and then catch authorization error, we wouldn't catch it, right? We wouldn't know that we need to log in afterwards. But let's see, illustration, let's see how we can use exception groups to improve that. So we will now have a list of exceptions and each time that we do get an exception we will add it to that list and eventually we're gonna raise an exception group with all of these exceptions. Outside we have changed to accept star and then it will actually catch that authorization error when it happens. It will actually catch both of them, put them in a group and do the log in. Okay, the tome requests will continue to be raised. It's still an error that we need to catch but it does the log in like we wanted it to. The second use case is a bit more complicated. It has to do with the async.io and it's actually relevant in threads, in sub-processes but I've gone with async.io in my example. Who here knows async.io just a little bit? Okay, that's pretty good. I'm just gonna go one minute about what it is and how it works. Async.io lets you run a code concurrently. So it lets you run multiple functions at the same time. Basically you have coroutines. Coroutines are defined with that async keyword before the def and when the coroutines are called they are not immediately executed. When they are called they return an object and we can later await on that object for them to be called. Now we can inside these coroutines also await for some blocking operations for example and in the meantime other coroutines will be executed. Okay, so in this example we create two coroutines. We print it to running both of them. They're not immediately executed and then we await on async.io gather. Async.io gather is a function that lets you await on multiple coroutines basically. So we pass it both coroutines and then they will both be run. Okay, so let's see some example with async.io and let's see what is the problem that we currently have when we run this code. So we create coroutines. We then put them create tasks out of those coroutines. Tasks are like coroutines but they're higher level objects so we have an easier interface for interacting with them. For example, we have that exception method that returns us an exception if one occurred. And now after that we're gonna use async.io wait to wait on those tasks. It's kinda like async.io gather so we can wait on multiple tasks but it returns to us two tuples. The first one are the done tasks and the second one are the pending tasks. Okay, the ones that didn't complete yet. So the coroutines themselves what they're gonna do is they're gonna select one of these arrows based on the eye that you give them and then raise it eventually. And now assume we want to check if any exceptions happen and then handle all of these exceptions. So we now need to iterate over the tasks. We need to check if they had an exception. If they did we will raise that exception and only otherwise we'll handle the task. Now it's a lot of code. I mean all we wanna do is just handle some exceptions, right? Also it's pretty indented and that's kinda ugly. And also we have to raise them ourselves, right? We need to get the exception object and then do the raise ourselves. I don't wanna do that, I want Python or that function that I'm calling async.io wait or something like that to do it for me, right? But it gets actually even worse. Let's assume that you want to stop after the first exception and you would probably wanna do that because you don't wanna waste resources that the coroutines that are still running are wasting. What's gonna happen is that your pending tasks are still running, okay? So after you return from the exception that you got and you now need to go over the pending tasks and cancel them. After you cancel them, even that's not enough because you then need to do async.io wait for them to run for a little bit. Now we want them to run for a little bit because they might have some finally close. That finally close will only be called after that async.io wait is called, okay? I actually didn't know that and I had to fix some bug in my code when I first learned about it for this lecture. So that's all of the code that you need just to handle the exceptions. But I just want to handle the fun parts, right? I want to only see that happy flow, hopefully. Okay, so let's see how we can do that. Let's implement another version of wait ourselves that uses exception groups. Okay, so as you can see, I will now await on this wait instead of async.io wait. And in this wait, I basically implemented what I implemented before and I can use anywhere for my code, I can use this wait function. It will go over the exceptions and it will raise them as an exception group this time. This is the key difference here. Then this exception group can be caught with accept star like we've seen before. And then handled as we see fit. Now, there is one issue here that I have with this code and it's that I don't want to implement wait. I want something else to do it for me. I want it to be some part of a library, preferably the standard library. Well, we're in luck because the Python gods have been good to us and we have exception groups, task groups, sorry. So task groups are basically the new way to create and run tasks. What they do is we use a context manager async with the async.io task group and then we create tasks on the task group and when we exit the code block, all of the task group will run. After it runs, if there are any exceptions, it will raise an exception group for us. So eventually in the main function, I only need to call the run task group and handle the exceptions and the happy flow can all stay inside the run task group method. So that's much cleaner than the earlier iterations that we've seen of it. Okay, so we got to the third and final example. Errors in context managers. And let's see the problem. So we have the main function. In that main function, there is a context manager that we use, a time reporter. It's gonna report the time that this code block has taken and what it's gonna do inside, that's the business logic that we have and we're gonna divide one by zero. I'm really interested in the result and outside if I do have a zero division error, like I should have, I will print a business logic error. Okay, there is only one problem here and it's the time reporter itself, the context manager, also raises an exception when it exits. When it exits, it sends a report and this report doesn't have the connection to the log server or whatever it wants to report and it raises a connection error. So what will happen is that we don't actually raise the zero division error. We won't catch it because what we can raise is the connection error. You can see that it is chained here and there is that zero division error in the message in the trace back but you will actually get a connection error eventually. So let's see how we solve it with exception groups. So now the exit function will catch any exception that it gets and if it does get exceptions, it will raise a group with these exceptions which will contain the original error that we have and the new error and now we can actually catch both exceptions or part of them. So here I only catch the zero division error and eventually I'm printing out business logic error like I wanted to in the first place. Okay, so the other error of course is still raised but that's fine by us because we didn't catch it. And any questions? Any questions online? So we got one in the room, go ahead. Hello. So thank you for the introduction and I have questions you mentioned early and we also saw that in the last example that if you have an accept star you will basically have a fall through and all the other exceptions you're not catching will be re-raised, right? Like you are only catching the zero division error and then you are letting the exception group. The rest of the exception group will be continued to be raised. That's okay, this example will be applied but in the early example you had we are basically switching from a regular exception to a group and then we would not basically catch the other errors. In which previous example was it? Yeah, I think it was. I think I owe or? No, before that early slides. So you had like basically two accept star and so for the context. This one. Yes, yes, this one. And so basically what would happen here if we have like another type of exception that we don't see but we don't want to bury it up elsewhere, right? You don't want to propagate the rest of it? No, yeah. You can use accept star exception or even a base exception and then every other part of the exception group will go to that last exception. Can you also do an accept star on exception group to catch the overall group? Accept star and exception group I think it's this allowed because it's kind of confusing. But you can use, and you can't use accept with accept star. Yeah, you can combine the two in the same clause because it's also pretty confusing. So basically the way to make sure you will catch everything here would be to use a basic exception. Accept star exception. Okay. Okay. Thank you. Let me just check. Are there any questions online? Okay. Hi, it's actually first time I see this accept star and exception groups and I wonder if there is the same effect as sort of with async code now that wherever you have async code in library it sort of poisons everything up and I have a feeling especially the last slide that you showed now you actually have to have the accept star to handle this exception group that let's say this library codes introduce. So is this the life for us now? Do we have to always expect libraries to give exception groups to us? Like do we have to change our whole thing and always put stars everywhere or like how do you feel about this? Great question. So basically you don't have to use exception groups as long as you don't have exception groups you don't need to use accept star. But if there is a library that raises an exception group you can if you want just use the regular accept with accept exception group because exception group is a type of an exception and you don't have even to use accept star and does that answer your question? No, let's say that you are sloppy Python engineer and you just do accept exception not exception group and what if the library code actually throws an exception group? What happens then? Like exactly what you have at the top you do just normal accept exception, right? But let's say that the main is actually a library code that throws an exception group. What happens? So the accept exception will catch an exception group. Okay. Thank you. Thank you. Just a quick one. Do you think it makes sense to subclass the exception group to like define your own or is it more intended to be like... Definitely, definitely. Yeah. You can subclass exception group the same you would the exception and maybe you would want to do an exception group of a certain type and then you'd wanna catch that there specifically. For example, a value error exception group and then you can do a regular accept of value error and maybe you would add some attributes to value error you could definitely do that. Right. So for all intents and purposes you can just use them as you would use exceptions, except they have a place where you can just... Where you can go only to a part of the exception group or... Yeah. All right, thank you. Thank you. Thank you for the talk. Because this is going to require Python 3.11 specific syntax, the accept star. Do you see that as an hindrance for library maintainers to implement it because it means that code is going to work with 3.11 forward? I'm not sure I understand. I think it's an hindrance because by the time that the companies are gonna start using 3.11, 3.14 is already gonna be out. And that's the main thing. But what exactly did you ask? So say I'm writing requests and you had a great example with a re-choice there where instead of bubbling up the last exception you can bubble up all of them with the accept group. If I'm the maintainer of requests and I want to implement that for my re-try function, am I gonna be unable to do that until I drop support from Python 3.10, Python 3.9, Python 3.8? Yeah. Yeah. Okay. Basically, yeah. Thank you. Thanks. Okay, any other questions online? Arun, go ahead. Yeah, related to support, do you know if, or was it part of a PEP that exception group, the type of exception, will be back-ported and will be available from Futures, for instance? It might be, I'm not sure, actually. Because there is nothing against using that in common versions, right? It's just like a collection of exceptions. I guess it's possible, but not sure. Okay. Thank you. Oh, a teeny bit ahead of schedule. So if there's any last question either online or in the room, feel free. If not, let's thank Or again for a fantastic presentation. There was certainly no need to be nervous. Thanks.