Python -- Check if object is instance of any class from a certain module
Asked Answered
D

3

36

Need a way to check if an object is an instance of any class in some particular module.

I know I can do it by explicitly importing every class from that module and checking with a tuple:

from my_module import ClassOne, ClassTwo

>>> isinstance(my_obj, (ClassOne, ClassTwo))
True

But in reality, the module I'm importing from has a TON of classes in it, and seems needlessly verbose to import them all explicitly, use them to build a huge tuple, and type check against it. I've tried a few things to avoid this:

import my_module

# Test my_obj against the module itself
>>> isinstance(my_obj, my_module)
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

# Try to test against a wildcard attribute on my_module
>>> isinstance(my_obj, my_module.*)
SyntaxError: invalid syntax

#Try to build a tuple of clases with iteration to check against
>>> for klass in my_module:
TypeError: 'module' object is not iterable

Is there a way to type check against ALL the classes in my_module, without explicitly naming them in a tuple?

Optional Background Info:
I may be overlooking a better way to approach my problem -- if you are wondering, here's the situation:

We are exporting data from a Google App Engine app to an app we have hosted on Rackspace. We are serializing the data with pickle and then sending it over to our Rackspace server with HTTP Requests.

Some of the data in the Google App Engine database is of GAE-specific data-types, imported from google.appengine.api.datastore_types. If any of these data types go across the wire to our Rackspace server, they will raise an error depickling, because our Rackspace app doesn't have the required GAE libraries. So, on the way out of GAE, I'm checking to see if any of the outgoing objects have a type from google.appengine.api.datastore_types. If they do, I either convert them to a builtin data-type or I delete the field off the object.

Dannielledannon answered 28/1, 2013 at 20:39 Comment(2)
As a side note -- this kind of checking seems quite unnecessary to me. I can't see a reason why you would want this ... While my solution below will work, I'd be interested to hear more about your use case -- There's probably a better way to accomplish whatever it is you're trying to do ...Culm
Edited the question to explain the situation.Dannielledannon
C
22

You can use inspect.getmembers to get all the classes in your module:

inspect.getmembers(my_module,inspect.isclass)

This will return a list of name-class pairs. You just want the classes:

my_module_classes = tuple(x[1] for x in inspect.getmembers(my_module,inspect.isclass))

One thing that I managed to overlook when I initially wrote this answer is the ability to check a class's __module__ attribute. You might be able to get away with checking if the __module__ is what you expect it to be:

from somewhere import module

if getattr(obj, '__module__', None) == module.__name__:
    # obj is from module.

This is likely to be cheaper than isinstance checking against a large list of class names.

Culm answered 28/1, 2013 at 20:41 Comment(7)
+1 I casually wonder if isinstance(my_obj, my_module_classes) is better than if any(isinstance(my_obj, x[1]) for x in inspect.getmembers(my_module, inspect.isclass))Polythene
@Polythene -- I would be shocked if the any solution is better than the straight isinstance solution. I wouldn't see any reason why isinstance wouldn't short-circuit and it can push all of the code into an optimized backend (C for cpython) whereas with the generator you have the overhead of creating a generator expression and then iterating in python ... Although, I've been wrong about this stuff before ;-)Culm
@mgilson: Well, he said "better", not "faster". I doubt the performance is really going to matter in any realistic code, especially code that's as dynamic as this presumably is. So the question is which is more readable. But… I think your version still wins, as long as you can count on your audience knowing that isinstance takes a tuple (which, given the OP's question, I think you can), because it allows you to give a name to the collection of classes.Cristoforo
Perhaps I'm not understanding what you mean by push all the code into an optimized backend, but I'm essentially wondering about the cost of creating the entire tuple of classes (which tuple() cast takes a generator expression argument itself, no?).Polythene
@Cristoforo -- I guess I was assuming my version was more easily read from the get-go. The one case where kojiro's solution is better is if people are monkey-patching stuff -- Then my static version might not cut it.Culm
@Polythene -- Ahh, I see what you're saying -- Since I'm creating a tuple, you don't get to short-circuit. That's true, but it's hard for me to concieve of a module with more than a handful of classes, so the 1-time investment to create the tuple of classes which you can then use for all your checking later seems well-worth it to me.Culm
@Culm yes, that's what I was saying. I realized just now that I assumed any() short-circuits, but that it wasn't clear that's what I was talking about. Anyway, nice answer.Polythene
U
3

The other solutions suggested testing getattr(obj, '__module__', None) == module.__name__ for boolean True or False. This only works if the object class is a direct member of the module. It will fail if a submodule is involved.

For instance, suppose you are doing calculations with experimental numerical data, interpolating measured points using scipy.interpolate.interp1d. You store your interpolation function in variable f. Then

> print(f.__module__)
scipy.interpolate._interpolate

The module is identified as the submodule scipy.interpolate, not simply scipy. If what you're trying to do is to check if your object class comes from scipy, the suggested test will fail. If f = scipy.interpolate.interp1d, then the result is

> print(getattr(f, '__module__', None) == scipy.__name__)
False

Instead, you want the pattern

module.__name__ in getattr(obj, '__module__', [])

e.g.

> print(scipy.__name__ in getattr(f, '__module__', []))
True

getattr should be given a default value [] rather than None in order for in to work successfully in the cases where this empty default value is used.

Unlash answered 31/5, 2023 at 16:6 Comment(0)
S
2

I have used @mgilson simple solution:

from somewhere import module

if getattr(obj, '__module__', None) == module.__name__:
    # obj is from module.

and noticed that if you have a tree of modules, and you want to make sure it comes from the base module, you have to do:

from somewhere import module

if getattr(obj, '__module__', None).split('.')[0] == module.__name__:
    # obj is from module.

But if your object is from a built-in it'll rise an exception, so I'd:

from somewhere import module

module_tree = getattr(obj, '__module__', None)
parent = module_tree.split('.')[0] if module_tree else None

if parent == module.__name__:
    # obj is from module.
Swedish answered 12/10, 2018 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.