4

I have an existing python (python v2.7) application that imports external py files on the fly which contain specifically named classes to processes data. The external py file loaded is chosen based on the type of post-processing of the data that is needed.

So I have this collection of classes, each in their own file. The files are named in a specific fashion based on the type of processing so that the main program knows what file to import from the upstream request.

Keep in mind that I and others are always tweaking these class files, but we can not change the code on the main application.

What I would like to do is to import a "template" of the common functions into the class scope which can provide the standard set of controls that the main program expects without needing to copy/paste them into each file. I hate it when I find a bug and make a correction in one of these main class i/o function which I then have to replicate in thirty-some other files.

Now, I understand from googling that my import here is bad... I get the message:

TestClassFile.py:5: SyntaxWarning: import * only allowed at module level

But this method is the only way I have found to import the functions so that they come into the namespace of the class itself. I have an example below...

What method (if any) is the appropriate way to do this in Python?

Example

main.py

import TestClassFile
print "New TestClass - Init"
oTest = TestClassFile.TestClass("foo")

print "Should run...  Function A"
oTest.funcA()

print "Should run...  Function b"
oTest.funcB()

TestClassFile.py

class TestClass:

    from TestClassImport import *

    def __init__(self, str):
        print "INIT! and be ... ", str

    def funcA(self):
        print "Function A"

TestClassImport.py

def funcB(self):
    print "Function B"

Much appreciated!


Update

Many thanks to everyone for the contributions. From researching MixIns, these appear to be the proper python way to extend a class.

TestClassImport.py

class ImportClass:
    def funcB(self):
        print "Function B"

TestClassFile.py

from TestClassImport import ImportClass
class TestClass(ImportClass):

    def __init__(self, str):
        print "INIT! and be ... ", str

    def funcA(self):
        print "Function A"
Peter B
  • 199
  • 1
  • 11
  • 2
    I think what you're looking for are partial classes. Unfortunately, achieving this structure in python isn't straightforward. Start with [this](https://stackoverflow.com/questions/9638446/is-there-any-python-equivalent-to-partial-classes) thread. Alternatively, you could use inheritance to achieve your desired result or something similar. – stelioslogothetis Jun 23 '17 at 21:48
  • Have you tried replacing the "*" with "funcB"? In other words, the error message tells you that * is the problem. You can't use *. Instead, replace * with the things you want * to represent. – Alan Leuthard Jun 23 '17 at 21:52

4 Answers4

4

It sounds like you should make the imported functions into mixins, which you can inherit from. So:

TestClassImport.py

class ClassB(object):
    def funcB(self):
        print "Function B"

TestClassFile.py

from TestClassImport import ClassB
from OtherClassImport import ClassX

class TestClass(ClassB, ClassX):
    ...
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Thank you to everyone for your comments. I had not heard of Mixins before, but after researching them this appears to be the most appropriate "python" way to do so. I will update my question with the working test code. – Peter B Jun 28 '17 at 23:49
0

I don't know if this is by any means a good solution, but you can write a function to construct a metaclass to dynamically add properties to your classes.

def make_meta(*import_classes):
    class TestMeta(type):
        def __new__(meta, name, bases, dct):
            new_class = super(TestMeta, meta).__new__(meta, name, bases, dct)
            for import_class in import_classes:
                for name in vars(import_class):
                    if not name.startswith('__'):
                        prop = getattr(import_class, name)
                        setattr(new_class, name, prop)
            return new_class
    return TestMeta


class TestClass:

    import TestClassImport
    __metaclass__ = make_meta(TestClassImport)

    # other functions defined as normal...

This will add everything in the global scope of TestClassImport.py that doesn't start with '__' as a property on TestClass.

Or, you can use a class decorator to add properties dynamically in the same fashion.

def add_imports(*import_classes):
    def augment_class(cls):
        for import_class in import_classes:
            for name in vars(import_class):
                if not name.startswith('__'):
                    prop = getattr(import_class, name)
                    setattr(cls, name, prop)
            return cls
    return augment_class


import TestClassImport

@add_imports(TestClassImport)
class TestClass:
    # normal class body

But mixins do seem like a better approach.

Jared Goguen
  • 8,772
  • 2
  • 18
  • 36
0

This appears to work:

import types

from TestClassImport import funcB

class TestClass:

    def __init__(self, str):
        print "INIT! and be ... ", str
        setattr(self, 'funcB', types.MethodType(funcB, self, TestClass))

    def funcA(self):
        print "Function A"

When I run it I get the following output:

INIT! and be ...  foo
Should run...  Function A
Function A
Should run...  Function b
Function B
ngoldbaum
  • 5,430
  • 3
  • 28
  • 35
0

You can use importlib for this, e.g.:

import importlib


class TestClass:
    def __init__(self, module_name):
        _tmp = importlib.import_module(module_name)
        for elem in _tmp.__dir__():
            if not elem.startswith('_'):
                prop = getattr(_tmp, elem)
                setattr(self, elem, prop)
    def funcA(self):
        print("function A")

tc = TestClass('some_module')
tc.funcB()
>>> prints "function B"

With this approach, you can create function load_module(module_name) instead of __init__() to load modules independently of each other (e.g. to prevent names collision).