6

Is it possible to check if some class exists? I have class names in a json config file. I know that I can simply try to create an object by class name string but that is actually a bad idea because the class constructor can do some unexpected stuff while at this point in time I just want to check if my config is valid and all mentioned classes are available.

Is there any way to do it?

EDIT: Also I do understand that u can get all the methods from some module, in my case I am not sure and don't actually care from what module comes the method. It can be from any import statement and I probably don't know where exactly from.

Alexandr
  • 150
  • 2
  • 12
  • Do you mean if a class is properly imported and available in the namespace or if there is an instance of a class with a particular name created? – kylieCatt May 27 '15 at 12:48
  • 1
    If you have already imported all your objetcs availible you can simply check it. You could write a `try except NameError` block. For further information we need some effort from your side. – wenzul May 27 '15 at 12:49
  • You could try `if locals().get('ClassName', False): ...` – kylieCatt May 27 '15 at 12:53
  • possible duplicate of [How can I get a list of all classes within current module in Python?](http://stackoverflow.com/questions/1796180/how-can-i-get-a-list-of-all-classes-within-current-module-in-python) – Ami Tavory May 27 '15 at 12:55
  • Isn't this only half of the work? If the class exists how is it instantiated? – Wolf May 27 '15 at 13:14
  • I mean if class is properly imported and available. I don't know for sure in which module it is defined so its like: "will I be able to create an object of this class in next row, if I want too?". – Alexandr May 27 '15 at 13:17
  • This question has the potential to get a good one, If there would be a little more information about what you are actually trying to achieve. – Wolf May 27 '15 at 15:14

3 Answers3

3

You can parse the source to get all the class names:

from ast import ClassDef, parse
import importlib
import inspect

mod = "test"
mod = importlib.import_module(mod)
p = parse(inspect.getsource(mod))
names = [kls.name for kls in p.body if isinstance(kls, ClassDef)]

Input:

class Foo(object):
   pass

class Bar(object):
    pass

Output:

['Foo', 'Bar']

Just compare the class names from the config to the names returned.

{set of names in config}.difference(names)

If you want to include imported names you can parse the module it was imported from but depending on how it was imported you can still find cases that won't work:

from ast import ClassDef, parse, ImportFrom
import importlib
import inspect

mod = "test"
mod = importlib.import_module(mod)
p = parse(inspect.getsource(mod))
names = []
for node in p.body:
    if isinstance(node, ClassDef):
        names.append(node.name)
    elif isinstance(node, ImportFrom):
        names.extend(imp.name for imp in node.names)

print(names)

Input:

from test2 import Foobar, Barbar, foo  

class Foo(object):
   pass

class Bar(object):
    pass

test2:

foo = 123

class Foobar(object):
    pass

class Barbar(object):
    pass

Output:

['Foobar', 'Barbar', 'Foo', 'Bar']
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
3

Using eval() leaves the door open for arbitrary code execution, for security's sake it should be avoided.

Especially if you ask for a solution for such a problem here. Then we can assume that you do not know these risks sufficiently.

import sys

def str_to_class(str):
    return reduce(getattr, str.split("."), sys.modules[__name__])

try:
    cls = str_to_class(<json-fragment-here>)
except AttributeError:
    cls = None

if cls:
    obj = cls(...)
else:
    # fight against this

This avoids using eval and is approved by several SO users. Solution is similar to Convert string to Python class object?.

Community
  • 1
  • 1
wenzul
  • 3,948
  • 2
  • 21
  • 33
  • As I pointed out in my answer (I now have to study this). But maybe your solution is more general than the question was? The additional information you asked for hasn't arrived meanwhile... – Wolf May 27 '15 at 15:08
  • Because str_to_class method in my project defined in some utils file I modified it a little to check classes for existance on top level (where it is called from). So now it looks like: def str_to_class(str): frame = inspect.stack()[-1] module = inspect.getmodule(frame[0]) return reduce(getattr, str.split("."), sys.modules[module.__name__]) It would be awesome to also check what method exactly called it and get module name from that method... But for current task it is fine... – Alexandr May 29 '15 at 07:53
2

I tried the built-in type function, which worked for me, but there is maybe a more pythonic way to test for the existence of a class:

import types

def class_exist(className):
    result = False
    try:
        result = (eval("type("+className+")") == types.ClassType)
    except NameError:
        pass
    return result

# this is a test class, it's only purpose is pure existence:
class X: 
    pass

print class_exist('X')
print class_exist('Y')

The output is

True
False

Of course, this is a basic solution which should be used only with well-known input: the eval function can be a great a back door opener. There is a more reliable (but also compact) solution by wenzul.

Community
  • 1
  • 1
Wolf
  • 9,679
  • 7
  • 62
  • 108
  • Thank you! That worked. And its much easier than iterating through modules looking for available classes. U saved my day :) – Alexandr May 27 '15 at 13:30
  • Just one comment to anyone who tries this: Make sure u imported class itself and not only the module containing it. Otherwise if u define class X in module Y check will fail. I should probably try class_exist('Y.X'), but that not so important. If u do "from Y import X" ur class became available and test will go just fine – Alexandr May 27 '15 at 13:34
  • 1
    Using `eval()` leaves the door open for arbitrary code execution, for security's sake it should be avoided. – wenzul May 27 '15 at 14:45
  • @Alexandr Try it with an own print-function `injectme()` and `class_exist("injectme()")`. Of course, the attacker could not create a function object easily. But this should point out that unintentional code could be executed. – wenzul May 27 '15 at 14:49
  • 1
    @wenzul Yes I was aware of this, that's why I asked. If I'd use this approach, I'd strictly avoid user input to be `eval`uated. I added disclaimer with a link to your solution. – Wolf May 27 '15 at 15:07