Sometimes the golden rule in Python of "explicit is better than implicit" is so cheap that it can be thrown away for the trouble of typing an empty tuple.
Today when I am explaining that in Python 3, there are two ways to raise exceptions:
raise Exception
raise Exception()
and that the first one is the same as the second one, as Python will add the missing pair of parenthesis.
I felt their pain as they gasped. Before that, I have already explained to them this piece of code:
try: raise SomeException() except SomeException: print('Got an exception here')
by saying that the except-clause will match anything that belong to the SomeException class.
Without knowing this secrete piece of information (that a pair of parenthesis is automatically provided), the following code would be hard to understand:
try: raise SomeException except SomeException: print('Got an exception here')
because the class object SomeException is not an instance of itself, so a not-so-crooked coder will not consider a match here.
So, the explicit is better than implicit rule is thrown out of the window so cheaply, that it literally worth less than an empty tuple!
> Sometimes the golden rule in Python of > "explicit is better than implicit" is > so cheap that it can be thrown away > for the trouble of typing an empty tuple.
I'm not sure that there *are* any golden rules. The "Zen of Python" is intended to be guidelines, not rigid rules intended to constrain your behavior but advice to help you write better code.
> Today when I am explaining that in Python 3, > there are two ways to raise exceptions:
> raise Exception
> raise Exception()
> and that the first one is the same > as the second one, as Python will add the > missing pair of parenthesis.
In fact this is noting to do with Python 3 - the same is true of Python 2, so this isn't new:
Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01) [GCC 4.3.4 20090804 (release) 1] on cygwin Type "help", "copyright", "credits" or "license" for more information.
>>> raise KeyError
Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError
>>> raise KeyError("Here is the message")
Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'Here is the message'
> I felt their pain as they gasped. > Before that, I have already explained > to them this piece of code:
> by saying that the except-clause > will match anything > that belong to the SomeException class.
Or any of its subclasses ...
> Without knowing this secrete > piece of information (that a > pair of parenthesis is automatically > provided), the following code > would be hard to understand:
> because the class object SomeException > is not an instance of itself, so > a not-so-crooked coder will not > consider a match here.
It's a matter of understanding correctly how the interpreter operates (and the interactive interpreter session is the ideal place to investigate this). The 2.7 documentation for the raise statement says (and this is not new):
""" If the first object is an instance, the type of the exception is the class of the instance, the instance itself is the value, and the second object must be None.
If the first object is a class, it becomes the type of the exception. The second object is used to determine the exception value: If it is an instance of the class, the instance becomes the exception value. If the second object is a tuple, it is used as the argument list for the class constructor; if it is None, an empty argument list is used, and any other object is treated as a single argument to the constructor. The instance so created by calling the constructor is used as the exception value. """
So the interpreter doesn't really "automatically provide a pair of parentheses", but examines the exception object and instantiates it if it is a class.
> So, the explicit is better than > implicit rule is thrown out of > the window so cheaply, > that it literally worth less > than an empty tuple!
Surely an exaggeration. In fact current best practice (which you should inform yourself of as best you can to help you in your teaching work - so you are to be congratulated for bringing this question to the list) is to always use explicit calls, with arguments specifying a tailored message.
> > Sometimes the golden rule in Python of > > "explicit is better than implicit" is > > so cheap that it can be thrown away > > for the trouble of typing an empty tuple.
> I'm not sure that there *are* any golden rules. The "Zen of > Python" is > intended to be guidelines, not rigid rules intended to > constrain your > behavior but advice to help you write better code.
> Surely an exaggeration. In fact current best practice > (which you should > inform yourself of as best you can to help you in your > teaching work - > so you are to be congratulated for bringing this question > to the list) > is to always use explicit calls, with arguments specifying > a tailored > message.
> regards > Steve
A very cogent message -- the end echos the start. :) I must say that I learned from you a new angle to think about this issue. On the other hand, I still feel that when allowing both ways colliding into the simpleness and bueaty of the language, we should consider to make a decision.
Sure, this introduced quite a lot of complexity when the doc has to give a very long explanation of what is happening in order to justify it.
As I am thinking about it, it seems two conflicting intuition of code comprehension are at work here:
Intuition #1: as if you raise an exception type, and then match that type. It seems that no instances are involved here (Intuitively). See an example code here:
try: raise KeyError except KeyError: pass
Intuition #2: you raise an exception instance, and then match an instance by its type. See an example code here:
try: raise KeyError() except KeyError as ke: pass
Those two comprehensions are not compatible, and thus the one that promotes correct understanding should be encouraged, while the other should be discouraged, and maybe even be made iliegal.
> As I am thinking about it, it seems two > conflicting intuition of code comprehension > are at work here:
> Intuition #1: as if you raise an exception > type, and then match that type. > It seems that no instances > are involved here (Intuitively). > See an example code here:
> try: raise KeyError > except KeyError: pass
> Intuition #2: you raise an exception > instance, and then match an instance by > its type. See an example code here:
> try: raise KeyError() > except KeyError as ke: pass
> Those two comprehensions are not compatible, > and thus the one that promotes correct > understanding should be encouraged, > while the other should be discouraged, > and maybe even be made iliegal.
I prefer to treat those two cases as unified, by observing that if what is raised in an exception class than an instance is created by calling it with no arguments. So matching is always by the instance's type - it's just that the instance creation can be implicit. I agree with you that explicit is better.
Most of the syntactic variation you dislike is to allow existing code to continue to work. Some of it is removed in Python 3, when backwards compatibility could be ignored.
> Sometimes the golden rule in Python of > "explicit is better than implicit" is > so cheap that it can be thrown away > for the trouble of typing an empty tuple.
> Today when I am explaining that in Python 3, > there are two ways to raise exceptions:
> raise Exception > raise Exception()
I agree with you that this is annoying. I think it is a holdover from the past. In Python 1/2, raise 'some string' also works, but that was disallowed in Py 3. A lot of things were cleaned up in Py 3, but not everything.
On Thu, 25 Nov 2010 08:15:21 -0800, Yingjie Lan wrote: > Intuition #1: as if you raise an exception type, and then match that > type. > It seems that no instances > are involved here (Intuitively).
Your intuition is not my intuition, nor does it match what Python actually does. You can only go so far on *guessing* what a programming statement does, sometimes you need to actually read the Fine Manual.
> See an example code here:
> try: raise KeyError > except KeyError: pass
As the documentation states, this creates an instance of KeyError.
raise evaluates the first expression as the exception object. It must be either a subclass or an instance of BaseException. If it is a class, the exception instance will be obtained when needed by instantiating the class with no arguments.
So there is no semantic difference between "raise KeyError" and "raise KeyError()". Why should there be? What practical difference would you expect?
If you do this:
try: raise KeyError except KeyError as ke: print(ke)
you will see that the value caught is an instance, not the class.
> Intuition #2: you raise an exception > instance, and then match an instance by its type. See an example code > here:
> try: raise KeyError() > except KeyError as ke: pass
Your intuition is wrong. Exceptions aren't matched by type, they are *always* matched by an isinstance() check, and that includes subclasses.
>>> try: raise KeyError # with or without parentheses makes no difference
... except Exception as e: print(type(e)) ... <class 'KeyError'>
> Those two comprehensions are not compatible, and thus the one that > promotes correct understanding should be encouraged, > while the other should be discouraged, and maybe even be made iliegal.
You seem to have misunderstood both forms of the raise statement. Should we make exceptions illegal because you can't correctly guess what they do?
--- On Fri, 11/26/10, Steven D'Aprano <steve+comp.lang.pyt...@pearwood.info> wrote:
> From: Steven D'Aprano <steve+comp.lang.pyt...@pearwood.info> > Subject: Re: what a cheap rule > To: python-l...@python.org > Date: Friday, November 26, 2010, 5:10 AM > On Thu, 25 Nov 2010 08:15:21 -0800, > Yingjie Lan wrote:
> You seem to have misunderstood both forms of the raise > statement. Should > we make exceptions illegal because you can't correctly > guess what they do?
Though what you said about Python is right, I think somehow you missed my point a bit.
Ideally, the language could be so 'natural' that it means what meets the eyes.
+comp.lang.pyt...@pearwood.info> wrote: > On Thu, 25 Nov 2010 08:15:21 -0800, Yingjie Lan wrote: > > Intuition #1: as if you raise an exception type, and then match that > > type. > > It seems that no instances > > are involved here (Intuitively).
> Your intuition is not my intuition, nor does it match what Python > actually does. You can only go so far on *guessing* what a programming > statement does, sometimes you need to actually read the Fine Manual.
> > See an example code here:
> > try: raise KeyError > > except KeyError: pass
> As the documentation states, this creates an instance of KeyError.
> raise evaluates the first expression as the exception object. > It must be either a subclass or an instance of BaseException. > If it is a class, the exception instance will be obtained when > needed by instantiating the class with no arguments.
> So there is no semantic difference between "raise KeyError" and > "raise KeyError()". Why should there be? What practical difference would > you expect?
There *can* be a difference though.
>>> raise UnicodeDecodeError
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: function takes exactly 5 arguments (0 given)
:-)
> You seem to have misunderstood both forms of the raise statement. Should > we make exceptions illegal because you can't correctly guess what they do?
Sometimes people not being able to understand them is a good reason for making things illegal (or rather for not making them legal in the first place). I don't think it applies to this particular case though.