`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
Ram.
On 28 November 2010 22:37, cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
FWIW (which isn't much I guess) it annoys me that I have to protect calls to issubclass with if isinstance(obj, type).
It isn't a subclass, so a False would be fine... The advantage of type checking is earlier failures when you're doing something wrong. The disadvantage is, well, all the disadvantages of type checking...
All the best,
Michael Foord
Ram.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 28 November 2010 22:37, cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Maybe one to chalk up for Python 4...
Michael
Ram.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On Mon, Nov 29, 2010 at 1:21 AM, Michael Foord fuzzyman@voidspace.org.ukwrote:
On 28 November 2010 22:37, cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Who is doing that?! What's wrong with something like `isinstance(thing, (type, types.ClassType))`?
Ram.
On 28 November 2010 23:23, cool-RR cool-rr@cool-rr.com wrote:
On Mon, Nov 29, 2010 at 1:21 AM, Michael Foord fuzzyman@voidspace.org.ukwrote:
On 28 November 2010 22:37, cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Who is doing that?! What's wrong with something like `isinstance(thing, (type, types.ClassType))`?
It doesn't matter who is doing it (or why). If they are writing valid python code we shouldn't break it for them. That's only my opinion - personally I always write the guard and would *like* to see the change. I don't think that's sufficient to break backwards compatibility in this way though.
Michael
Ram.
On Mon, Nov 29, 2010 at 1:26 AM, Michael Foord fuzzyman@voidspace.org.ukwrote:
On 28 November 2010 23:23, cool-RR cool-rr@cool-rr.com wrote:
On Mon, Nov 29, 2010 at 1:21 AM, Michael Foord <fuzzyman@voidspace.org.uk
wrote:
On 28 November 2010 22:37, cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
Use case: I have an object which can be either a list, or a string, or a callable, or a type. And I want to check whether it's a sub-class of some base class.
So I don't think I should be taking extra precautions before using `issubclass`: If my object is not a subclass of the given base class, I should just get `False`.
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Who is doing that?! What's wrong with something like `isinstance(thing, (type, types.ClassType))`?
It doesn't matter who is doing it (or why). If they are writing valid python code we shouldn't break it for them. That's only my opinion - personally I always write the guard and would *like* to see the change. I don't think that's sufficient to break backwards compatibility in this way though.
Michael
I guess it depends on how many people do that. Doing `issubclass` and excepting `TypeError` is a very convoluted way of checking if something is a type. I think that the kind of person who would write that code is not the kind of person who would care to port their program in Python 3.x.
Also, isn't it possible to break backwards compatibility on such things by deprecating it in one release and changing it in the next?
Ram.
cool-RR wrote:
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Who is doing that?! What's wrong with something like `isinstance(thing, (type, types.ClassType))`?
Is that a serious question? Trying something, and catching the exception if it fails, is a general Python idiom. In many case, it's often considered more "pythonic" to write:
try: handle normal case (which might fail) except SomeException: handle exceptional case
than:
if operation will fail: handle exceptional case else: handle normal case (which hopefully won't fail)
since at least Python 1.5. It's been long considered "best practice" under many circumstances to catch exceptions rather than to try to preemptively guess what might go wrong.
On Sun, Nov 28, 2010 at 4:13 PM, Steven D'Aprano steve@pearwood.info wrote:
cool-RR wrote:
Unfortunately it would be a backwards incompatible change. Currently catching the TypeError from issubclass is a way of detecting that an object *isn't* a type.
Who is doing that?! What's wrong with something like `isinstance(thing, (type, types.ClassType))`?
Is that a serious question? Trying something, and catching the exception if it fails, is a general Python idiom. In many case, it's often considered more "pythonic" to write:
try: handle normal case (which might fail) except SomeException: handle exceptional case
than:
if operation will fail: handle exceptional case else: handle normal case (which hopefully won't fail)
since at least Python 1.5. It's been long considered "best practice" under many circumstances to catch exceptions rather than to try to preemptively guess what might go wrong.
Hm. The try/except version should IMO be used as a last resort only (similar to using eval() / exec()). It usually reads more clumsily than the if-based version, and often it is not clear to the reader what error condition is being tested. One of the most common things I see in Python code reviews at work is try/except clauses that catch an overly general exception and/or out a try/except around an overly long piece of code, masking potential errors and obscuring the meaning of the code. (Another common one is legitimate try/except clauses that are nevertheless lacking a comment explaining what error condition is being caught and why.)
That is not to say that try/except should not be used, but I think the question of what's wrong with an explicit test should not be cast off as rhetorical.
As to the original issue, Antoine got it: issubclass(X, Y) does not make sense if you're not even sure that X is a class.
Steven D'Aprano steve@pearwood.info wrote:
since at least Python 1.5. It's been long considered "best practice" under many circumstances to catch exceptions rather than to try to preemptively guess what might go wrong.
You're kidding, right? *Programming* is about preemptively guessing what might go wrong :-).
Bill
On Tue, Nov 30, 2010 at 3:35 AM, Bill Janssen janssen@parc.com wrote:
Steven D'Aprano steve@pearwood.info wrote:
since at least Python 1.5. It's been long considered "best practice" under many circumstances to catch exceptions rather than to try to preemptively guess what might go wrong.
You're kidding, right? *Programming* is about preemptively guessing what might go wrong :-).
LBYL (Look Before You Leap) and EAFPI (Easier to Ask Forgiveness than Permission) is a style argument that is never going to go away though, since the answer is "it depends".
My personal answer is usually "make the common case fast". That means defaulting to using a try-except block (with an else clause to prevent overreaching) since the overhead of a try-except that doesn't need to catch an exception is close to zero and the exception normally reflects the uncommon case. However, since raising and catching an exception is quite expensive, if it is a situation where average and/or worst-case performance matters more than common case performance, testing a condition first may start to appear more attractive if the preemptive check is faster than the exception handling would be.
One thing to keep in mind, though, is that some pre-emptive checks are using the moral equivalent of a try/except block under the covers (with hasattr() being the classic example of this), so the performance gain in the exceptional case actually isn't all that great. You really need to profile it to figure out which is faster.
You do need to take care that the try/except isn't covering too much though, and, depending on how obscure the exception is, potentially add a comment as to the error condition you're checking for.
As far as the original post's request goes, no. issubclass expects to be given two types. If you have an insanely polymorphic variable that can contain a wide variety of objects, only some of which are type instances, then either use isinstance to check first as you are already doing, or a try-except block to convert the TypeError to a False result (and bundle the combined test into a helper function if you're doing it a lot).
Cheers, Nick.
Nick Coghlan ncoghlan@gmail.com wrote:
As far as the original post's request goes, no. issubclass expects to be given two types. If you have an insanely polymorphic variable that can contain a wide variety of objects, only some of which are type instances, then either use isinstance to check first as you are already doing, or a try-except block to convert the TypeError to a False result (and bundle the combined test into a helper function if you're doing it a lot).
Right. Use isinstance instead; that's what it's there for.
But handling exceptions properly really requires thinking about which might occur, and what to do if they do occur. If you don't do that preemptive thinking about exceptionality, you might as well not bother catching them at all.
Bill
On 29Nov2010 18:22, Bill Janssen janssen@parc.com wrote: | Nick Coghlan ncoghlan@gmail.com wrote: | > As far as the original post's request goes, no. issubclass expects to | > be given two types. If you have an insanely polymorphic variable that | > can contain a wide variety of objects, only some of which are type | > instances, then either use isinstance to check first as you are | > already doing, or a try-except block to convert the TypeError to a | > False result (and bundle the combined test into a helper function if | > you're doing it a lot). | | Right. Use isinstance instead; that's what it's there for. | | But handling exceptions properly really requires thinking about which | might occur, and what to do if they do occur. If you don't do that | preemptive thinking about exceptionality, you might as well not bother | catching them at all.
My problem with try/except (which leads me to be reluctant to use it except around really really simple things) is that you don't know what threw the exception.
Consider:
if obj.foo == 0: # handle 0 else: x = y / obj.foo
The try/except verision goes like this:
try: x = y / obj.foo except ZeroDivisionError: # handle 0
Now, the reason I'm using "obj.foo" here is that obj.foo may be a property or otherwise inplemented by __getattr__; arbitrarily complex code may be happening to obtain the value it returns. The upshot of that is that even this very simple looking code may be an unhandled ZeroDivisionError from deeper in the call stack - I don't know it came from the division visible in the code example above.
For this reason, I will usually prefer to go the if/else route, and if I go the try/except route I will often have a lot of leading gumph to get everything into purely local variables just to avoid the risk shown above:
z = obj.foo # gumph try: x = y / z except ZeroDivisionError: # handle 0
The obfuscation from the leading gumph can often outweigh the brazen pleasures of the try/except leap into the unknown:-)
Cheers,
Cameron Simpson wrote:
The try/except verision goes like this:
try: x = y / obj.foo except ZeroDivisionError: # handle 0
Now, the reason I'm using "obj.foo" here is that obj.foo may be a property or otherwise inplemented by __getattr__; arbitrarily complex code may be happening to obtain the value it returns. The upshot of that is that even this very simple looking code may be an unhandled ZeroDivisionError from deeper in the call stack - I don't know it came from the division visible in the code example above.
That's true. But if obj.foo raises ZeroDivisionError, perhaps you should be treating this as equivalent to x/0 and continue, particularly if you have no control over the obj.foo. What else are you going to do (assuming that letting the exception propagate is not an option)?
In any case, if you want to guard against such bugs, you can inspect the traceback and decide what to do. This is particularly easy in Python3.x, but possible in 2.x as well:
class K:
... def __getattr__(self, name): ... raise ZeroDivisionError ...
try:
... 1/K().spam ... except ZeroDivisionError as e: ... if e.__traceback__.tb_next is None: ... print("handle division by zero") ... else: ... print("handle bug in __getattr__") ... handle bug in __getattr__
And it's not like the if test catches all possible errors. There are many potential error conditions that aren't caught by something like:
if obj.foo != 0: print(x/obj.foo) else: # handle 0
You still have no guarantee that the division will succeed. Perhaps one or other of x and obj.foo define __truediv__ or __rtruediv__ in such a way that it raises ZeroDivisionError even when obj.foo is not zero. That's not necessarily a bug -- one or the other could be an interval quantity that straddles zero, but isn't equal to zero.
If you fear that obj.foo could contain arbitrarily complex code that may accidentally raise ZeroDivisionError (or anything else!), the same holds for __ne__. Once you stop trusting your values, you can't trust *anything* -- maybe object.foo has the __eq__ and __ne__ reversed. Who knows? How defensively do you code?
Here's an error condition from the Bad Old Days before IEEE floats: perhaps division and equality are done to different precisions, such that y != 0 but x/y still attempts division by zero. (This really used to happen, on some mainframes.) Obviously this can't happen with Python floats -- or can it? are IEEE semantics guaranteed? -- but it could happen with some custom numeric type.
You're right that the try...except idiom is subject to false positives. It might, under some (presumably rare) circumstances, catch an exception which should be treated as a bug. But the if...else idiom is subject to both false positives and false negatives: your test may be too strict, or it may be not strict enough. Or both at the same time. Or the thing you are testing may be subject to race conditions:
if os.exists(pathname): # This is NOT safe! fp = open(pathname) else: print("no such file")
vs.
try: fp = open(pathname) except IOError: print("no such file")
It seems to me that the LBYL idiom is actually *less* safe than the exception handling idiom.
On 05Dec2010 12:16, Steven D'Aprano steve@pearwood.info wrote: | Cameron Simpson wrote: | >The try/except verision goes like this: | > | > try: | > x = y / obj.foo | > except ZeroDivisionError: | > # handle 0 | > | >Now, the reason I'm using "obj.foo" here is that obj.foo may be a property | >or otherwise inplemented by __getattr__; arbitrarily complex code may | >be happening to obtain the value it returns. The upshot of that is that | >even this very simple looking code may be an unhandled ZeroDivisionError | >from deeper in the call stack - I don't know it came from the division | >visible in the code example above. | | That's true. But if obj.foo raises ZeroDivisionError, perhaps you | should be treating this as equivalent to x/0 and continue, | particularly if you have no control over the obj.foo. What else are | you going to do (assuming that letting the exception propagate is | not an option)?
Well, that's the point: if the ZeroDivision came from my divide presumably I have a strategy to handle that because it is expected. But if it came from inside the implementation of obj.foo then more likely I should let the exception propagate, because it is _not_ the cricumstance for which my try/except was accomodating.
| In any case, if you want to guard against such bugs, you can inspect | the traceback and decide what to do. This is particularly easy in | Python3.x, but possible in 2.x as well: | | >>> class K: | ... def __getattr__(self, name): | ... raise ZeroDivisionError | ... | >>> | >>> try: | ... 1/K().spam | ... except ZeroDivisionError as e: | ... if e.__traceback__.tb_next is None: | ... print("handle division by zero") | ... else: | ... print("handle bug in __getattr__") | ... | handle bug in __getattr__
Interesting. But this is easier to read and maintain than the if/else form how?
| And it's not like the if test catches all possible errors. There are | many potential error conditions that aren't caught by something | like: | | if obj.foo != 0: | print(x/obj.foo) | else: | # handle 0
Of course there are; it's an example to illustrate the ambiguity of the received exception versus the concreteness of a failed if-test.
[...] | If you fear that obj.foo could contain arbitrarily complex code that | may accidentally raise ZeroDivisionError (or anything else!), the | same holds for __ne__. Once you stop trusting your values, you can't | trust *anything* -- maybe object.foo has the __eq__ and __ne__ | reversed. Who knows?
Sure, but this is the realm of the _unhandled_ circumstance. With the try/except I will easily try to handle that circumstance in the _mistaken_ belief that it is a specific failure I was ready for.
| How defensively do you code?
More defensively than many, in my experience.
In particular, I generally want to be sure that an "exceptional" circumstance I'm handling really is the anticipated circumstance, and not something totally else. With exceptions you don't know that without cumbersome inspection of the thrown exception. The point of the example is that one can write code for an anticipated circumstance, but that code should _not_ run for the unanticipated circumstance. This is easy with the if/else because the test is specific.
[...] | You're right that the try...except idiom is subject to false | positives. It might, under some (presumably rare) circumstances, | catch an exception which should be treated as a bug. But the | if...else idiom is subject to both false positives and false | negatives: your test may be too strict, or it may be not strict | enough.
My test may be accurate or not, sure. But inaccuracy is a coding bug on my part, not an exceptional circumstance that might legitimately happen at any runtime.
| Or both at the same time. Or the thing you are testing may | be subject to race conditions: | | if os.exists(pathname): | # This is NOT safe! | fp = open(pathname) | else: | print("no such file") | | vs. | | try: | fp = open(pathname) | except IOError: | print("no such file") | | It seems to me that the LBYL idiom is actually *less* safe than the | exception handling idiom.
Not as written. I take your point about the race and in fact _do_ go for try/except there because I'm after "did the open succeed?", something that can't be tested in advance without time travel.
But I would be writing it like this:
try: fp = open(pathname) except: # disaster!
and feeling awful about the bare except. But the fact of th matter is that an open may fail for many reasons, not just I/O. And in fact your example is a classic case of my concern with try/except - you're reporting "no such file" when in fact there may be other reasons eg a permission failure. You're doing just what I wish to avoid: _misdiagnosing_ an exception and acting on that diagnosis.
It sounds like we're both aware of the pros and cons but differ in what risks to accept.
Cheers,
On Mon, 29 Nov 2010 00:37:12 +0200 cool-RR cool-rr@cool-rr.com wrote:
`issubclass(1, list)` raises an Exception, complaining that `1` is not a class. This is wrong in my opinion. It should just return False.
It raises an exception for the same reason that 1 in 'a' raises an exception. Because there's no way that an int can belong in a string, or an int can be a subclass of anything; and so passing an int there is very likely to be a programming error.
Python is dynamically typed, it doesn't mean it is untyped (contrast with PHP). If you want arbitrary polymorphism, you have to write the supporting code yourself.
Regards
Antoine.