0

At the end of a module, I want to get the classes defined in the module and add some attributes to the class. (The attribute values are calculated based on other class attributes; I want to avoid repeating code in every class definition.) I've found guidance here on how to get a list of classes defined in the module.

classmembers = inspect.getmembers(
    sys.modules[__name__], 
    lambda member: inspect.isclass(member) and member.__module__ == __name__
    )

But if I have defined an Enum subclass in the module, I want to filter that out. I tried the following, but it didn't work:

classmembers = inspect.getmembers(
    sys.modules[__name__], 
    lambda member: inspect.isclass(member) and member.__module__ == __name__
        and not isinstance(member,  Enum)
    )

I'm temporarily checking the resulting list with this

for c in classmembers:
    print(c)

and enums continue to appear.

...
('VarFWord', <class 'table_COLR_new.VarFWord'>)
('VarFixed', <class 'table_COLR_new.VarFixed'>)
('VarUFWord', <class 'table_COLR_new.VarUFWord'>)
('extend', <enum 'extend'>)

Why is that happening? What do I need to change to filter out the enum?

Peter Constable
  • 2,707
  • 10
  • 23
  • Whatever you are doing is completely crazy. I sincerely hope that you are a solo developer. Monkey patching is almost always the wrong thing to do when compared to repeating a bit of code – Iain Shelvington Jun 23 '20 at 02:59
  • I don't _think_ it's completely crazy. I'm writing font parsing code. OpenType has well over 100 different structs to implement. I'm working on generic functions that take a struct format declaration and can then parse. On 99% of those struct format declarations, I need to set attributes for the packed format (for use by struct.unpack), and use a function to calculate that from the field declarations. It's complete boilerplate repeated on nearly every one. Doesn't it seem reasonable to write statements to filter the applicable classes and set the attributes? – Peter Constable Jun 23 '20 at 03:16
  • Argh... but it doesn't work because `getmembers` is returning classes in alphabetic order, not in the order they occurred in the module — so dependency chains aren't upheld. (class bar has fields of type foo; foo was defined first in the module, and the attribute added in foo will be needed to compute the attribute value for bar.) – Peter Constable Jun 23 '20 at 03:26
  • It seems reasonable until you put yourself in the shoes of a developer that has to pick up your code when you are not around or have moved on. When that developer looks at one of the classes in the module they are probably not (because you are not going to comment every class in the module) going to know that it has been monkey patched. The classes in your module will not behave as expected and that in my opinion is terrible design – Iain Shelvington Jun 23 '20 at 03:26
  • Well, within the generic parsing functions there are calls to a function that validates all the struct class definitions. So, if they miss a step, they'd quickly hit an assert to tell them something wasn't right in the class definition. But I get your point. – Peter Constable Jun 23 '20 at 03:49
  • I almost considered not having class attr's for these and putting the calc'n in the generic parsing functions, but thought to put in the struct defs so these are calculated once when the module is loaded rather than many times — once for each instance of a struct in the font file. In case you're interested: – Peter Constable Jun 23 '20 at 04:14
  • To be fair, there are comments in the module that layout the monkey patching, I don't mean to be rude. I just think it's not that much more code to add a parent class that they all inherit from where you define [`__init_subclass__`](https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__) and set the attributes there. At least when someone looks at one of the classes in that module for where `class.PACKED_FORMAT` is defined they will know exactly where to look – Iain Shelvington Jun 23 '20 at 04:26
  • @PeterConstable: That was my first suggestion as well -- use subclasses if possible and put the boilerplate in once in the parent class. – Ethan Furman Jun 23 '20 at 04:32
  • Thanks. (And not rude.) Hadn't thought of that. I'm still early in learning Python, and this is one of my learning projects. So I definitely appreciate the tips. I haven't come across `__init_subclass__` yet, or gone into meta classes. The doc'n isn't entirely clear. ("Whenever a class inherits from another class..." Is that when the module is parsed by Python? Or when a subclass object is constructed?) I'll experiment to figure it out. There will be many more modules (many OT tables) that I'll want to use the same SC(s) for, so they'll be in a different module. – Peter Constable Jun 23 '20 at 05:24
  • Wrt the boilerplate in the parent class: the order in which things get evaluated is critical. This class-generic boilerplate makes a calculation using class-specific info. So the sub-class-specific details need to be evaluated first. And I'm trying to make it happen once when the modules are imported, not on every object creation. – Peter Constable Jun 23 '20 at 05:28
  • @IainShelvington: Thanks for your tip. I've set up a base class and made use of `__init_subclass__` achieve what I wanted, which was to consolidate redundant code if possible across class def's. I'm still making use of the code in the answer, but not to monkey patch but rather to run validations on all the class def's. – Peter Constable Jun 23 '20 at 06:28

1 Answers1

0

I figured out an answer. It's a bit unexpected: wouldn't have expected a type foo could be a subclass of bar but not be an instance of bar.

So, instead of

classmembers = inspect.getmembers(
    sys.modules[__name__], 
    lambda member: inspect.isclass(member) and member.__module__ == __name__
        and not isinstance(member,  Enum)
    )

I just needed to change to this:

classmembers = inspect.getmembers(
    sys.modules[__name__], 
    lambda member: (inspect.isclass(member) 
        and member.__module__ == __name__
        and not issubclass(member,  Enum))
    )
Peter Constable
  • 2,707
  • 10
  • 23