8 ms·
They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy! I've felt weird using exceptions like that but I alway
by ziml77 5y ago
They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy!
I've felt weird using exceptions like that but I always assumed that CPython was optimized to minimize overhead of exceptions and exception handlers.
- zohch 5y ago> I've felt weird using exceptions like that How should they be used instead? Maybe I don't understand what you mean by "idiomatic control flow uses exceptions" - could you give an example. Maybe there is some use of exceptions that I'm not quite familiar with in Python.
- foobarbecue 5y agoIn python, it's normal to use exceptions in place of type checking, e.g. in polymorphic functions.
- zohch 5y agoBut that would not be for control flow.
- foobarbecue 5y agoOn a smaller scale, it is.
- zohch 5y agoTo me sending wrong arguments is an error condition, not control flow, see this for more info: https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why https://softwareengineering.stackexchange.com/questions/1892...
- ziml77 5y agoIt's not about sending the wrong arguments. In Python a function may support a variety of types in a single argument. But instead of querying the type and switching based on that, you often want to try to call a method and if you get an exception (because that method does not exist), try to call a different method that gets you what you need. The advantage to this over checking the type is that you are still use duck typing. If you check the type then you can only support a specific set of classes.
- deleted 5y ago[deleted]
- openasocket 5y agoWhen using a for-loop over an iterator, the iterator protocol in Python says to keep returning elements until you run out, at which point you throw an exception. So every loop over an iterator or iterable object in python throws an exception when it is done. https://docs.python.org/3/library/stdtypes.html#iterator.__next__ https://docs.python.org/3/library/stdtypes.html#iterator.__n...
- zohch 5y agoTIL, thanks for explaining, indeed would have expected exceptions to be pretty cheap also if I knew this.
- ynik 5y agoWhile that's true at the Python language level, there are already special optimizations for this in CPython: `tp_iternext` is not required to set an exception. If it returns NULL without setting an exception, that's taken to be the end of iteration. If you call `next()` in Python, this special case is translated to a `StopIteration` exception. But if you use a for-loop, it can directly stop iterating without ever materializing the `StopIteration` exception. So the overhead of Python raise/try-except is already irrelevant for the for-loop.
- ehvatum 5y agohttps://www.educba.com/python-stopiteration/ https://www.educba.com/python-stopiteration/
- ubercore 5y agoThe first that came to mind is how `get()` is handled in Django's ORM. The idiomatic way to look for a single object is to use `get`, then catch a `DoesNotExist` exception: From https://docs.djangoproject.com/en/3.2/ref/models/querysets/#get https://docs.djangoproject.com/en/3.2/ref/models/querysets/#... from django.core.exceptions import ObjectDoesNotExist try: blog = Blog.objects.get(id=1) entry = Entry.objects.get(blog=blog, entry_number=1) except ObjectDoesNotExist: print("Either the blog or entry doesn't exist.")
- brianwawok 5y agoRight, but the better way to actually write this is something like entry = Entry.objects.filter(blog__id=1, entry_number=1).first() if entry is None: # deal with does not exist Maybe it's my scala/Java background shining through, but we are big Django users and we ban the "catch exceptions as standard" workflow, because there is almost always a cleaner way...
- zohch 5y ago> Right, but the better way to actually write this is something like Maybe for some cases, but it does not do the exact same thing, see the comment here: https://stackoverflow.com/a/29455777/1598080 https://stackoverflow.com/a/29455777/1598080 And if we are talking about idiomatic, I think it is maybe a stretch to count this as idiomatic for Python, but given it is documented for Django I think it is fair to call it idiomatic for Django.
- ziml77 5y agoThat is the sort of method that I prefer if it's available. I'm a C# dev so this way also feels far cleaner to me.
- _bohm 5y agoThis fails to raise an error if there is more than one object matching the given filters
- 5y ago
- brandmeyer 5y agoNearly all loops are terminated by raising an exception. https://docs.python.org/3/library/exceptions.html#StopIteration https://docs.python.org/3/library/exceptions.html#StopIterat...
- ghshephard 5y agoThis covers it very well: https://devblogs.microsoft.com/python/idiomatic-python-eafp-versus-lbyl/ https://devblogs.microsoft.com/python/idiomatic-python-eafp-... In particular, this is not idiomatic python: if "key" in dict_: value += dict_["key"] But this is: try: value += dict_["key"] except KeyError: pass I too hate using the exception handling in this way, and if you aren't careful, you end up papering over other unexpected exceptions in your code, so you have to be (A) very specific in the exception you catch, and (B) keep it in as small a portion of code as possible. I just think it makes for clumsy code - which of the two look better: try: value = dict_["key"] except KeyError: pass else: do_something(value) OR if "key" in dict_: do_something(dict["key"]) But it might just be me.
- zohch 5y ago> In particular, this is not idiomatic python According to this article, I think his case is rather weak. The conclusion does not seem to follow from the premise to me.
- mywittyname 5y agoIf you don't care about a key existing, then this works. do_something(dict_.get("key", None)) I use that a lot for data parsing. Passing the exception is not very clean IMHO. I stick to d[key] nomenclature when I need assurance that all the keys are present in the dictionary, and .get(key,None) when I don't.
- stevesimmons 5y ago.get("key") is enough, as the default is already None. And if you care about the default value being a particular type, when there may also be None in the input stream, do something like: x.get("key") or [] or str(x.get("key") or "") # Guarantee strings and avoid "None"!
- stevesimmons 5y agoThe difference in your "which of the two look better" example is: (a) has one dict operation plus an exception which rarely occurs and is nearly zero cost if it doesn't. versus (b) has nearly always two dict operations, plus a possibly incorrect assumption that the dict will not be mutated between the "if key in dict" and "dict[key]" operations.
- masklinn 5y ago> In a language where idiomatic control flow uses exceptions? That's crazy! Seems like the opposite: if exceptions are extremely rare then you want to optimise the case where they’re not raised, at the cost of the other one. If exceptions are common then it matters a lot less, you may even want to avoid 0ce depending on the impact on the raised case.
- alex_smart 5y agoI don't think python has the mindset of "exceptions are extremely rare". That is probably what the OC meant by python being "a language where idiomatic control flow uses exceptions". As an example, every iterator in python signals its end by throwing a StopIteration exception. So, every "for x in iter" has the interpreter throwing and catching an exception.
- masklinn 5y ago> I don't think python has the mindset of "exceptions are extremely rare". No it does not, that's my point: when exceptions are extremely rare, as in C++ or Java, having the "no exception" case be free at additional expense in the "exception" case is an excellent tradeoff, because the latter should happen extremely rarely so even if the costs are ridiculous you'll have more than made them by all the cases where you got the "no exception" run for free. In Python where exceptions are common however, making one case free at the expense of the other is less interesting a proposition, and is more of a balancing act: you don't want to overly penalise exceptions-heavy code as that is considered normal and idiomatic.
- pca006132 5y agoZero cost refers to the cost when no exception is thrown, not the overhead of exceptions. It may be more expensive throwing an exception under "zero cost" exception model, as throwing an exception may require parsing some data in the executable. (I'm not sure about the implementation, so this is just a may...)
- SideburnsOfDoom 5y ago> Zero cost refers to the cost when no exception is thrown, not the overhead of exceptions There are recent benchmarks where .NET "zero cost when an try-catch block is present and exception not thrown" turned out to be significantly slower then the alternative without such a block. It turns out that the try-catch block is a barrier across which some optimisations and re-organisations (e.g. method inlining) cannot happen, and that the compiler & JIT normally do a lot of these. So it might be "zero cost" but it might also prevent wins. Edit: see here https://blogs.msmvps.com/peterritchie/2007/06/22/performance-implications-of-try-catch-finally/ https://blogs.msmvps.com/peterritchie/2007/06/22/performance... https://stackoverflow.com/questions/1308432/do-try-catch-blocks-hurt-performance-when-exceptions-are-not-thrown https://stackoverflow.com/questions/1308432/do-try-catch-blo...
- masklinn 5y agoMakes sense. Basically it’s zero-cost for exceptions-unaware code. I don’t know if it’s been changed with more modern jits but used to be chrome was unable to jit functions with try statements (similar to functions using `arguments` or `with` I think).
- deleted 5y ago[deleted]
- bregma 5y agoExceptions always have a cost. What happened with many C++ runtimes is that they moved the execution cost of exceptions almost entirely into the exception raising mechanism so that the path of execution that does not raise exceptions has no overhead due to exceptions. This was not the case with the Python runtime. The "zero cost" implementation for C++ exceptions in the case of ELF binaries means storing a bunch of static (compile-time generated) data regarding CPU register state changes for each function in a table indexed by function address offset within the DSO. When a "throw" expression is executed, the execution stack is crawled and that table evaluated for each function encountered until an appropriate "landing pad" (eg. a matching "catch" clause) is found, and then the stack is unwound by interpreting the function table contents to step through the various CPU states in reverse order, executing destructors as required. It's just like sufficiently advanced technology. In the case of no "throw" expression, there is no extra CPU cycles spent. This trades off execution time for extra memory storage in the no-exception case and more CPU cycles in the exception case. As I understand it, the Python "compiler" just generates a bunch of straight-line code and decision statements so that when a function returns, its result is checked and if the result is that an exception was thrown it jumps to the local handling code otherwise it jumps to the local non-exception code. The price for handling exceptions is always paid all the way up the stack for every function call made. Because exceptions are a fundamental control-flow mechanism in Python (unlike in C++, where they should only be used for exceptional control flow), I'm not sure if there will be a net benefit to "zero cost" exceptions. I guess they should try it and measure the difference under various scenarios and make an informed decision based on evidence.
- coldtea 5y ago>Because exceptions are a fundamental control-flow mechanism in Python (unlike in C++, where they should only be used for exceptional control flow), I'm not sure if there will be a net benefit to "zero cost" exceptions. Well, they might be "a fundamental control-flow mechanism" but for every StopIteration (for an example of a control flow exception use), there are multiple (up to millions) of traversed elements that didn't throw an exception.
- mplanchard 5y agoThis was really informative, thanks! Also “it’s just like sufficiently advanced technology” made my morning.
- jerf 5y agoThis is on a different dimension. Up to this point, in Python, exceptions were always checked, so they were "free" in the sense you were always paying for them anyhow. I remember many C++ programmers migrating to Python back in the ol' comp.lang.python days having this described to them. You don't have to worry about exception-heavy code, because since you're always paying for them anyhow, you might as well use them. A lot of making CPython code run well works like that. You want to use as much of the stuff you're already always paying for anyhow, rather than reimplementing any of it in your own code.