Identifying that a variable is a new-style class in Python?
Asked Answered
K

4

14

I'm using Python 2.x and I'm wondering if there's a way to tell if a variable is a new-style class? I know that if it's an old-style class that I can do the following to find out.

import types

class oldclass:
  pass

def test():
  o = oldclass()
  if type(o) is types.InstanceType:
    print 'Is old-style'
  else:
    print 'Is NOT old-style'

But I haven't been able to find anything that works for new-style classes. I found this question, but the proposed solutions don't seem to work as expected, because simple values as are identified as classes.

import inspect

def newclass(object):
  pass

def test():
  n = newclass()
  if inspect.isclass(n):
    print 'Is class'
  else:
    print 'Is NOT class'
  if inspect.isclass(type(n)):
    print 'Is class'
  else:
    print 'Is NOT class'
  if inspect.isclass(type(1)):
    print 'Is class'
  else:
    print 'Is NOT class'
  if isinstance(n, object):
    print 'Is class'
  else:
    print 'Is NOT class'
  if isinstance(1, object):
    print 'Is class'
  else:
    print 'Is NOT class'

So is there anyway to do something like this? Or is everything in Python just a class and there's no way to get around that?

Karalee answered 16/4, 2010 at 16:33 Comment(4)
I was working on a class that serialized its children recursively and I wanted to be able to tell if the child was a class and handle it by also serializing its children recursively.Karalee
your previous comment seems like it should be the actual question, and the original question looks like a (possibly erroneous) step you took towards the answer to your problem. Check this: catb.org/~esr/faqs/smart-questions.html#goalLeafage
You might want to give us an example of a class having another class as a child; also, make sure you're not talking about instances. It is much more common for an instance to have children instances that you want to serialize.Leafage
I apologize, because I meant an instance and not the class itself.Karalee
I
8

I think what you are asking is: "Can I test if a class was defined in Python code as a new-style class?". Technically simple types such as int are new-style classes, but it is still possible to distinguish classes written in Python from the built-in types.

Here's something that works, although it's a bit of a hack:

def is_new_style(cls):
    return hasattr(cls, '__class__') \
           and \
           ('__dict__' in dir(cls) or hasattr(cls, '__slots__'))


class new_style(object):
    pass

class old_style():
    pass

print is_new_style(int)
print is_new_style(new_style)
print is_new_style(old_style)

Output from Python 2.6:

False
True
False

Here's a different way to do it:

def is_new_style(cls):
    return str(cls).startswith('<class ')
Invar answered 16/4, 2010 at 17:1 Comment(6)
Also, based on the response from ~unutbu, the can be used to identify old-style classes: def is_old_style(cls): return not hasattr(cls, 'init')Karalee
Sorry for the misinformation, but the above is_old_style(cls) doesn't work if the old-style class has an explicitly defined _init_ method, but type(cls) is types.InstanceType does work.Karalee
Daniel, the first version of is_new_style needs improvement: it won't consider as new-style this class: class NewStyle(object): __slots__= "attribute",.Leafage
@ΤΖΩΤΖΙΟΥ, thanks for catching that. I've updated the code to handle that case.Invar
Both the ways look hacky, but I couldn't find a better way to do this either. Between the two, I'm sold to the simplicity of the second one.Peek
I definitely wouldn't trust the last one. Any class that overrides __str__ will fail that test.Rebarebah
L
2

I believe this suffices:

def is_new_style_class(klass):
    return issubclass(klass, object)

def is_new_style_class_instance(instance):
    return issubclass(instance.__class__, object)

Typically, you only need the is_new_style_class function for your purposes. Everything not a class will throw a TypeError, so you might want to update it to:

def is_new_style_class(klass):
    try:
        return issubclass(klass, object)
    except TypeError:
        return False

Examples:

>>> class New(object): pass
... 
>>> is_new_style_class(New)
True
>>> class Old: pass
... 
>>> is_new_style_class(Old)
False
>>> is_new_style_class(1)
False
>>> is_new_style_class(int)
True

int, being a type, is by definition a new-style class (see Unifying types and classes in Python 2.2 ), or —if you prefer— new-style classes are by definition types.

Leafage answered 16/4, 2010 at 19:33 Comment(7)
The problem with that is that everything (even old-style classes and basic types like int) will return true.Karalee
The problems with your comment are: old-style classes return False, and int (being a type, by definition) is a new-style class.Leafage
The issue is that int doesn't have a _dict_ attribute and therefore violates one of the "rules" of new-style classes.Karalee
@Dave: but is it a rule or a misconception of yours? This is a new-style class: class NewStyle(object): __slots__= "attribute", and its instances do not have a __dict__ attribute.Leafage
The instance test is not correct: being an object does not mean having a new-style class. Example: the code for the standard HTMLParser module shows that HTMLParser.HTMLParser is an old-style class; however, is_new_style_class_instance(HTMLParser()) incorrectly returns True.Maemaeander
@EOL: thanks for the pointer; I corrected the code. I'll look into it why the previous version produced the incorrect result.Leafage
@tzot: Thanks! Downvote removed. It all boils down to the fact that the type of an object and its class are two different things, with old-style classes. Reference: #9700091Maemaeander
M
1

It's not that "everything is a class": what you're bumping into is that "everything is an object" (that is, every (new-style) thing descends from "object").

But new-style classes are a "type" themselves (actually, the were introduced to bring classes and types together). So you can try checking for

import types

type(o) == types.TypeType

Does that solve your problem?

Manteau answered 16/4, 2010 at 16:57 Comment(8)
That returns True for int as wellInvar
No, that expression for both old-style and new-style classes evaluates to false. However, this does evaluate to true for new-style classes but not old-style classes: type(n.__class__) == types.TypeType But, then it also evaluates to true for basic types like 'int', so it's not exactly what I was looking for.Karalee
That's correct, an int is an instance of a new-style class. That's the point of the unification.Clishmaclaver
But an int doesn't have a _dict_ attribute, which I assumed all classes had.Karalee
@Dave Johansen: you might consider correcting your assumptions.Leafage
The issue is that the documentation (and a few of the responses here) states that the _dict_ attribute is part of new-style classes, so I don't understand what assumption should be changed.Karalee
@Dave: I answered with an example of a new-style class without a __dict__ attribute in both the accepted and my answer. If the documentation states that a __dict__ attribute is a sine-qua-non for a new-style class, then it needs to be fixed. Please give me the related URL in the documentation, I'll supply the patch.Leafage
The first paragraph in the section about classes at docs.python.org/reference/… states (or at least strongly implies) that the dictionary exists. Yes, it does state that there are other methods for accessing the variables, but it doesn't state that the _dict_ does not exist until you go down to the slots section of the same page. Basically, someone like me who's not familiar with the inner workings on new-style classes would not be able to gather all the necessary information since there's no link to slots in the Classes section.Karalee
S
-1

Checking for old-style classes is really easy. Just check type(cls) is types.ClassType. Checking for new-style classes is also easy, isinstance(cls, type). Note that the built-in types are also new-style classes.

There seems to be no trivial way to distinguish built-ins from classes written in Python. New-style classes with __slots__ also don't have __dict__, just like int or str. Checking if str(cls) matches the expected pattern fails if the classes metaclass overrides the __str__ method. Some other ways that also don't work:

  • cls.__module__ == '__builtin__' (you can reassign __module__ on classes)
  • not any(value is cls for value in vars(__builtins__).values()) (you can add stuff to the __builtin__ module).

The fact that unification of builtin and userdefined types is so good that distinguishing them is non-trivial problem should imply to you the underlying point. You really shouldn't have to distinguish between them. It doesn't matter what the object is if it implements the expected protocol.

Stamey answered 16/4, 2010 at 20:38 Comment(1)
types.ClassType is Python 2-specific, rendering this answer obsolete. Moreover, there is a reliable means of distinguishing builtin from user-defined classes under CPython.Prim

© 2022 - 2024 — McMap. All rights reserved.