Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In python 3 when can type() return anything other than a class?

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?

like image 600
Billy Timimi Avatar asked Jan 03 '23 02:01

Billy Timimi


1 Answers

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.

like image 193
abarnert Avatar answered Jan 30 '23 10:01

abarnert