Python dynamically import classes an call their functions - python

I have one Abstract Animal class.
from abc import ABC, abstractmethod
class Animal(ABC): # Inherit from ABC(Abstract base class)
#abstractmethod # Decorator to define an abstract method
def feed(self):
pass
And three classes which Implements it
from Animal import Animal
class Lion(Animal):
def feed(self):
print("Feeding a lion with raw meat!")
from Animal import Animal
class Panda(Animal):
def feed(self):
print("Feeding a panda with some tasty bamboo!")
from Animal import Animal
class Snake(Animal):
def feed(self):
print("Feeding a snake with mice!")
Now I just want to import all classes, which are in the project folder and call the feed function of all classes, when there is a feed function.
from glob import glob
import os
if __name__ == '__main__':
for file in glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), "*.py")):
name = os.path.splitext(os.path.basename(file))[0]
# add package prefix to name, if required
module = __import__(name)
try:
module.feed()
except Exception as e:
print(e)
My Problem is now, that I get the errors:
module 'Animal' has no attribute 'feed'
module 'Lion' has no attribute 'feed'
module 'main' has no attribute 'feed'
module 'Panda' has no attribute 'feed'
module 'Snake' has no attribute 'feed'
Can someone help me with this?

I take it your files are called Snake.py, Panda.py etc. If so then you are invoking feed() on the files not the classes. You need to get the modules (which you've done), then get the class and then call the method:
from glob import glob
import os
if __name__ == '__main__':
for file in glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), "*.py")):
name = os.path.splitext(os.path.basename(file))[0]
if name == "Animal" or name == "main": # avoid main.py and Animal.py
continue
# add package prefix to name, if required
module = __import__(name)
try:
# get the class
cls = getattr(module, name)
# instantiate the class and call the method
cls().feed()
except Exception as e:
print(e)
Ive also included a safety check to exclude main.py and Animal.py

Related

Register classes in different files to a Class factory

I am trying to register classes that are in different files to the factory class. The factory class has a dictionary called "registry" which hold/maps the a user defined name to the registering class. My issue is that if my factory class and registering classes are in the same .py file everything works as expected but the moment I move the registering classes into their own .py files and import the factory class to apply the register decorator (as described in the question & article below) the "registry" dictionary stays empty, which means that the classes are not getting registered.
They way I am registering these classes is via a decorator. My code looks very much like what we see here:
Registering classes to factory with classes in different files (my question is a duplicate of this, but bumping this question to the top)
https://medium.com/#geoffreykoh/implementing-the-factory-pattern-via-dynamic-registry-and-python-decorators-479fc1537bbe
I would like to know:
What why keeping them in the same file work while splitting them out doest
How can I make the separate file approach work ?
Hopefully the code samples in the articles clarify what I am trying to do and struggling with.
I'm currently exploring a similar problem, and I think I may have found a solution. It is a bit of a 'hack' though, so take it with a grain of salt.
What why keeping them in the same file work while splitting them out doest
In order to make your classes self-register in the factory while keeping their definition in single .py files, we have to somehow force the loading of the classes in the .py files.
How can I make the separate file approach work?
In my case, I've came across this problem when trying to implement a 'Simple Factory', with self-registering subclasses to avoid having to modify the typical 'if/else' idiom in the factory's get() method.
I'll use a simple example, starting with the decorator method you've mentioned.
Example with decorators
Let's say we have a ShoeFactory as shown below, in which we register different 'classes' of shoes:
# file shoe.py
class ShoeFactory:
_shoe_classes = {}
#classmethod
def get(cls, shoe_type:str):
try:
return cls._shoe_classes[shoe_type]()
except KeyError:
raise ValueError(f"unknown product type : {shoe_type}")
#classmethod
def register(cls, shoe_type:str):
def inner_wrapper(wrapped_class):
cls._shoe_classes[shoe_type] = wrapped_class
return wrapped_class
return inner_wrapper
Examples of shoe classes:
# file sandal.py
from shoe import ShoeFactory
#ShoeFactory.register('Sandal')
class Sandal:
def __init__(self):
print("i'm a sandal")
# file croc.py
from shoe import ShoeFactory
#ShoeFactory.register('Croc')
class Croc:
def __init__(self):
print("i'm a croc")
In order to make Sandal self-register in the ShoeFactory while keeping its definition in a single .py file, we have to somehow force the loading of the Sandal class in .py file.
I've done this in 3 steps:
Keeping all class implementations in a specific folder, e.g., structuring the files as follows:
.
└- shoe.py # file with the ShoeFactory class
└─ shoes/
└- __init__.py
└- croc.py
└- sandal.py
Adding the following statement to the end of the shoe.py file, which will take care of loading and registering each individual class:
from shoes import *
Add a piece of code like the snippet below to your __init__.py within the shoes/ foder, so that to dynamically load all classes [1]:
from inspect import isclass
from pkgutil import iter_modules
from pathlib import Path
from importlib import import_module
# iterate through the modules in the current package
package_dir = Path(__file__).resolve().parent
for (_, module_name, _) in iter_modules([package_dir]):
# import the module and iterate through its attributes
module = import_module(f"{__name__}.{module_name}")
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if isclass(attribute):
# Add the class to this package's variables
globals()[attribute_name] = attribute
If we follow this approach, I get the following results when running some test code as follows:
# file shoe_test.py
from shoe import ShoeFactory
if __name__ == "__main__":
croc = ShoeFactory.get('Croc')
sandal = ShoeFactory.get('Sandal')
$ python shoe_test.py
i'm a croc
i'm a sandal
Example with __init_subclass__()
I've personally followed a slighly different approach for my simple factory design, which does not use decorators.
I've defined a RegistrableShoe base class, and then used a __init_subclass__() approach to do the self-registering ([1] item 49, [2]).
I think the idea is that when Python finds the definition of a subclass of RegistrableShoe, the __init_subclass__() method is ran, which in turn registers the subclass in the factory.
This approach requires the following changes when compared to the example above:
Added a RegistrableShoe base class to the shoe.py file, and re-factored ShoeFactory a bit:
# file shoe.py
class RegistrableShoe():
def __init_subclass__(cls, shoe_type:str):
ShoeFactory.register(shoe_type, shoe_class=cls)
class ShoeFactory:
_shoe_classes = {}
#classmethod
def get(cls, shoe_type:str):
try:
return cls._shoe_classes[shoe_type]()
except KeyError:
raise ValueError(f"unknown product type : {shoe_type}")
#classmethod
def register(cls, shoe_type:str, shoe_class:RegistrableShoe):
cls._shoe_classes[shoe_type] = shoe_class
from shoes import *
Changed the concrete shoe classes to derive from the RegistrableShoe base class and pass a shoe_type parameter:
# file croc.py
from shoe import RegistrableShoe
class Croc(RegistrableShoe, shoe_type='Croc'):
def __init__(self):
print("i'm a croc")
# file sandal.py
from shoe import RegistrableShoe
class Sandal(RegistrableShoe, shoe_type='Sandal'):
def __init__(self):
print("i'm a sandal")

Importing a class by referring to it with a variable?

Edit: This question was on the assumption that I could import parts of modules without importing the whole module. It turns out that isn't the case so I decided to just import the whole module with from ex45_extras import * anyway. That makes this question pointless, but I decided to not delete this question so that some other beginner with the same question can come here and find out the sad truth: You can't import modules as parts anyways
The following is the original question:
I'm a beginner so sry for the noob question. I want to call specific classes from a module with a variable. I don't want to call the whole module. And I need to do this using this class Nav() to control which classes are being imported. Is there a way to do this? Or is there a better solution?
class Nav():
def __init__(self):
print("This is class Nav")
def play(self):
current_scene_name = "A()" # this is where the variable is defined
from ex45_extras import current_scene_name # <- this is the one
x = Nav()
x.play()
Currently it's raising this error:
This is class Nav
Traceback (most recent call last):
File "D:\Programming\Python\LPTHW_Exs\ex45\test.py", line 11, in <module>
x.play()
File "D:\Programming\Python\LPTHW_Exs\ex45\test.py", line 7, in play
from ex45_extras import current_scene_name
ImportError: cannot import name 'current_scene_name' from 'ex45_extras' (D:\Programming\Python\LPTHW_Exs\ex45\ex45_extras.py)
Class names don't have a trailing () suffix — that's how you create an instance of one (i.e. by calling it).
Anyhow, if ex45_extras.py defines a class named A:
class A:
pass
Then you could import the class via a string containing its name, and then create an instance of it as shown below:
class Nav():
def __init__(self):
print("This is class Nav")
def play(self):
import ex45_extras
current_scene_name = 'A' # this is where the variable is defined
class_ = getattr(ex45_extras, current_scene_name) # Get class.
instance = class_() # Create instance of class.
print(f'{instance=}') # -> instance=<ex45_extras.A object at 0x010D4838>
x = Nav()
x.play()
guess it's because you're trying to import an string "A()" instead of a class A()

Error when pickling dynamically defined extension of an abstract base class

I would like to persist an object that is created from a dynamically defined class that extends an abstract base class. I have tried to achieve this by pickling the object. I am using Python 3.6.8
The error message suggests that pickle is trying to look for the class I have created within the abc module and is unable to find it there. I have tried to pickle by creating the class within the main module (by defining class_factory inside test.py) but I get the same error message.
objmodel.py
from abc import ABC, abstractmethod
class mtype(ABC):
def __init__(self):
super().__init__()
#abstractmethod
def set_someattrib(self, attrval):
pass
#classmethod
def set_attrib1(cls, aval):
cls.attrib = aval
utils.py
from objmodel import *
def class_factory(mname, aval):
c1 = type(mname, (mtype,), {"set_someattrib": set_attrib1})
c1.set_someattrib(aval)
return c1
test.py
from utils import class_factory
import pickle
c1 = class_factory('C', 1)
print(c1.attrib)
m = c1()
fh = open('objtest', 'wb')
pickle.dump(m, fh)
I get the following error:
_pickle.PicklingError: Can't pickle : attribute lookup C on abc failed

Use only class name without namespace in isinstance

This works in a script to recognise if a is of class myproject.aa.RefClass
isinstance(a, myproject.aa.RefClass)
But how could I do it so I do not have to specify the full namespace ? I would like to be able to type:
isinstance(a, RefClass)
How is this done in Python ?
EDIT: let me give more details.
In module aa.referencedatatable.py:
class ReferenceDataTable(object):
def __init__(self, name):
self.name = name
def __call__(self, f):
self._myfn = f
return self
def referencedatatable_from_tag(tag):
import definitions
defn_lst = [definitions]
for defn in defn_lst:
referencedatatable_instance_lst = [getattr(defn, a) for a in dir(defn) if isinstance(getattr(defn, a), ReferenceDataTable)]
for referencedatatable_instance in referencedatatable_instance_lst
if referencedatatable_instance.name == tag
return referencedatatable_instance
raise("could not find")
def main()
referencedata_from_tag("Example")
In module aa.definitions.py:
from aa.referencedatatable import ReferenceDataTable
#ReferenceDataTable("Example")
def EXAMPLE():
raise NotImplementedError("not written")
For some reason calling the main from aa.referencedatatable.py will throw as it will not be able to recognise the instance of the class. But if I copy this main in another module it will work:
import aa.referencedatatable
a = aa.referencedatatable.referencedatatable_from_tag("Example")
print a
This second example works, for some reason calling this function inside the same module where the class is declared does not.
The 'namespace' is just a module object, and so is the class. You can always assign the class to a different name:
RefClass = myproject.aa.RefClass
or better yet, import it directly into your own namespace:
from myproject.aa import RefClass
Either way, now you have a global name RefClass that references the class object, so you can do:
isinstance(a, RefClass)

Python import a module into one class only

I am trying to get a module to import, but only if an object of a specific class is called. For example:
class One(object):
try:
import OneHelper
except ImportError:
pass
def __init__(self):
# this function doesn't use OneHelper
...
def blah(self):
# this function does
OneHelper.blah()
This causes a NameError: global name 'OneHelper' is not defined when the One.blah() function is called. So far the only thing I have found that works is importing the module into the actual functions that use it. So:
class One(object):
def __init__(self):
# this function doesn't use OneHelper
...
def blah(self):
try:
import OneHelper
except ImportError:
pass
# this function does
OneHelper.blah()
But I don't want to have to import the module in each function I want to use it in, I want it to be available to the whole class, but only if an instance of that class is instantiated. Apologies if I'm not being clear enough...
The import OneHelper works fine in the class, making it a class attribute. You can verify this with dir(One) after defining your class -- there's your OneHelper attribute. One.OneHelper is a reference to the module. In an instance, of course, you may access it as self.OneHelper from your methods. (You could also continue to access it as One.OneHelper.)
Import it on __init__ and attribute to some property:
class One(object):
def __init__(self):
try:
import OneHelper
except ImportError:
self.OneHelper = None
else:
self.OneHelper = OneHelper
def blah(self):
if self.OneHelper:
self.OneHelper.blah()
Your example looks funny because if the module fails to import what is the point of calling it later?
You might also consider using global OneHelper before importing the module. This adds the OneHelper to the global namespace.

Categories

Resources