2

Suppose I have a large number of classes defined by an import of a large library codebase, which I don't want to hack around with for reasons of maintainability. They all inherit from BaseClass, and BaseClass contains a method which I want to augment. I think the following is a workable solution

class MyMixin(object):
   def method( self, args):  
      ... # 1. a few lines of code copied from BaseClass's def of method
      ... # 2. some lines of my code that can't go before or after the copied code
      ... # 3. and the rest of the copied code

class MyAbcClass( MyMixin, AbcClass): 
   pass
# many similar lines
class MyZzzClass( MyMixin, ZzzClass): 
   pass

The question. Is there a way to take, say, a list of ("MyXxxClass", XxxClass) tuples, and write code that defines the MyXxxClasses? And is it sufficiently comprehensible that it beats the repetition in the above?

nigel222
  • 7,582
  • 1
  • 14
  • 22

1 Answers1

1

Use three-arg type to define the classes, then set them on the module's global dictionary:

todefine = [('MyAbcClass', AbcClass), ...]
for name, base in todefine:
    globals()[name] = type(name, (MyMixin, base), {})

If the names to define follow the fixed pattern you gave (`"My" + base class name), you can repeat yourself even less by dynamically constructing the name to define:

todefine = [AbcClass, ...]
for base in todefine:
    name = "My" + base.__name__
    globals()[name] = type(name, (MyMixin, base), {})

And if you are trying to wrap all the classes from a given module, you can avoid even explicitly listing the classes by introspecting the module to generate todefine programmatically (if you know the module has or lacks __all__ you can just use the appropriate approach instead of trying one and defaulting to the other):

import inspect
try:
    # For modules that define __all__, we want all exported classes
    # even if they weren't originally defined in the module
    todefine = filter(inspect.isclass, (getattr(somemodule, name) for name in somemodule.__all__))
except AttributeError:
    # If __all__ not defined, heuristic approach; exclude private names
    # defined with leading underscore, and objects that were imported from
    # other modules (so if the module does from itertools import chain,
    # we don't wrap chain)
    todefine = (obj for name, obj in vars(somemodule).items() if not name.startswith('_') and inspect.isclass(obj) and inspect.getmodule(obj) is somemodule)
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • It gets better: `"My"+AbcClass.__name__` is the string `"MyAbcClass" so I can input just a list of classes to derive Myclasses from. – nigel222 Sep 17 '15 at 14:08
  • 1
    I was considering mentioning that; I wasn't sure if the names were really as a simple as `"My" + base.__name__` though. If you're wrapping every class from another given module, you could even dynamically pull them, by defining `todefine = (obj for name, obj in vars(somemodule).items() if not name.startswith('_') and inspect.isclass(obj) and inspect.getmodule(obj) is somemodule)`. – ShadowRanger Sep 17 '15 at 14:23
  • @nigel222: I added some info to the answer on this. – ShadowRanger Sep 17 '15 at 14:52