So I was going through the source code for the copy
library and I found this:
cls = type(x)
copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
try:
issc = issubclass(cls, type)
except TypeError: # cls is not a class
issc = False
The important lines being cls = type(x)
and except TypeError: # cls is not a class
. (I left in the lines inbetween in case they help). The except
clause leads me to believe that there is some x
for which type(x)
would not return a class, however I can not think of an example where this would be the case. (I tried adding a print
statement to the clause and seeing if I could trigger it to no avail). Could you give me an example where type()
does not return a class?
If you're asking about the check in copy.copy
, that's actually not necessary. It's a longstanding bug, but a trivially unimportant one that nobody bothered to fix for years. But, coincidentally, it was fixed just 11 days ago as part of issue 11572, an umbrella bug that aims to finish code coverage of the copy.py
module for 3.8.
The root cause here goes back to a real issue that existed in Python 2.2 until somewhere later in the 2.x branch (I'm guessing until 2.5, inclusive, but that's a guess)—but the fix wasn't added until 3.4, long after the problem had ceased to exist.
This check does not exist in Python 2.x. As hunted down by user2357112, it was added as part of a fix for issue 11480 in Python 3.4. copy.copy
on a class with a custom metaclass was broken, and a fix was necessary, which was copied from copy.deepcopy
, but included a change that wasn't needed anymore. Which was later removed from deepcopy
, but sat around in copy
until this month.
As found by tripleee, the original bug was #502085.
In fact, in 2.2, it was not only possible to have issubclass(type(x), type)
raise a TypeError
, it was actually happening in the wild.
In Python 2.1 and earlier (before PEP 252, extension types could basically stick anything they wanted in their type slot. In 2.2+, this would break isinstance
and issubclass
. They were made to raise a TypeError
instead of segfaulting, and that was good enough, because who's ever going to stick a non-type in a type slot, right? Well, the original version of boost::python
did exactly that for you.
The next version didn't cause that problem, presumably not everyone upgraded right away (it required rewriting part of your code, it couldn't generate modules compatible with Python 2.1, and a 2MB download, seriously? do you think I'm on 224Kbps dual-ISDN or something?), so those exceptions were a real thing to worry about for a while.
(This is only tangentially related to old- vs. new-style classes—extension types were effectively always "new-style" even before that was a thing, and I'm pretty sure the check for their type being a type was added before 3.0.)
While we're at it, technically, the word class
is ambiguous in the Python docs. Sometimes a class
is something created by executing a class
statement or by calling the type
constructor (or calling a subclass of type
). Sometimes a class
is an instance of type
or a subclass of type
, in which case builtin types are classes.
By the former definition, x = 1
is an example where type(x)
is not a class. But by the latter definition, it is—and that's clearly the definition used by issubclass
. So, this is mostly irrelevant, unless you're looking for a gotcha question to annoy someone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With