Reputation: 21503
I have a python class in a module, and I have a few methods within it that need to have a list of certain other classes within the same module. Here is how I'm doing it right now:
module.py
class Main:
@staticmethod
def meth1():
for c in classes:
#do something
@staticmethod
def meth2():
for c in classes:
#do something
class Class1:
pass
class Class2:
pass
class Class3:
pass
classes = [Class1, Class3]
A few things I would like to improve:
classes
list somewhere more prevalent. Ideally, either outside all classes, but at the top of the module file, or as a class attribute of Main
, but outside of either meth1
or meth2
. The purpose of that is to make it easier to find, if someone needs to add another class definition.dir()
or locals()
, but they also list imported classes, methods, and modules. Also, I would need some way to identify the classes I want. I can do that just with an attribute in the classes, but if there's some more elegant way, that would be nice.Is what I'm trying to do even possible?
Upvotes: 4
Views: 1304
Reputation: 57
In current versions of Python you can use:
from __future__ import annotations
Upvotes: 0
Reputation: 56230
Personally, I would use a decorator to mark the classes that are important. You can place the list that will hold them at the top of the file where it will be noticable.
Here's a simple example:
# Classes are added here if they are important, because...
important_classes = []
def important(cls):
important_classes.append(cls)
return cls
@important
class ClassA(object):
pass
class ClassB(object):
pass
@important
class ClassC(object):
pass
# Now you can use the important_classes list however you like.
print(important_classes)
# => [<class '__main__.ClassA'>, <class '__main__.ClassC'>]
Upvotes: 4
Reputation: 14644
You can use inspect.
First, get the list of local variables:
local_vars = locals().values()
Then we need to inspect each one:
import inspect
local_vars = [i for i in local_vars if inspect.isclass(i)]
To get only classes locally defined, check if cls.__module__ == __name__
as follows:
def get_classes():
global_vars = list(globals().values())
classes = [i for i in global_vars if inspect.isclass(i)]
return [i for i in classes if i.__module__ == __name__]
The overall idea is this: inspect allows you to inspect live objects, and iterating over all local variables allows you to inspect everything within your current namespace. The final part, which classes are defined locally, can be done by checking if the module name is the same as the current namespace, or cls.__module__ == __name__
.
Finally, for Python3 compatibility, I've added list(globals().values()
, since the dictionary size will change during list comprehension. For Python2, since dict.values()
returns a list, this can be omitted.
EDIT:
For further filtering, you can also use specific class attributes or other, as was mentioned in the comments. This is great if you are worried about restructuring your module into a package later.
def get_classes(name='target'):
global_vars = list(globals().values())
classes = [i for i in global_vars if inspect.isclass(i)]
return [i for i in classes if hasattr(i, name)]
Upvotes: 2
Reputation: 48649
Your list seems to be targeting a subset of the available classes in the module, so at some point you will have to specify the classes you are targeting.
import sys
target_classes = ["Class1", "Class3"]
class Main:
def __init__(self, classes):
self.target_classes = classes
def meth1(self):
for s in self.target_classes:
C = getattr(sys.modules[__name__], s)
C().speak()
def meth2(self):
for c in classes:
print c
#do something
class Class1:
def speak(self):
print "woof"
class Class2:
def speak(self):
print "squeak"
class Class3:
def speak(self):
print "meow"
Main(target_classes).meth1()
--output:--
woof
meow
Upvotes: 2
Reputation: 2280
I am not sure if this is the best practice, but this will do what you need:
class Main:
def __init__(self, locals):
self.classes = []
for (c, val) in locals.iteritems():
try:
if c[:5] == 'Class':
self.classes.append(val)
except:
pass
def meth1(self):
for c in self.classes:
pass
def meth2(self):
for c in self.classes:
pass
class Class1:
pass
class Class2:
pass
class Class3:
pass
main = Main(locals())
print main.classes
Upvotes: 1
Reputation: 5289
There may be better ways to achieve this, but I would make all of these subclasses of a holder, then use __subclasses__()
to pull them all:
class Main:
def meth1(self):
for c in Holder._subclasses__():
#do something
def meth2(self):
for c in Holder._subclasses__():
#do something
class Holder(object):
pass
class Class1(Holder):
pass
class Class2(Holder):
pass
class Class3(Holder):
pass
You could even make them subclasses of Main
if you wanted to, and then pull them with a classmethod:
class Main(object):
@classmethod
def meth1(cls):
for c in cls._subclasses__():
#do something
class Class1(Main): pass
You do need to inherit from object
with Python 2 for this to work.
Upvotes: 2