Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you dynamically add class attributes/variables to a subclass in python?

I have a large amount of auto-generated classes in python that essentially represent enums for part of a communication protocol, they look like so

# definitions.py

class StatusCodes(ParamEnum):
    Success = 1
    Error = 2
    UnexpectedAlpaca = 3

class AlpacaType(ParamEnum):
    Fuzzy = 0
    ReallyMean = 1

# etc etc etc

Defining things this way makes it easy for the auto-generator and also for humans to modify. The ParamEnum class provides all the functionality, get/setting, comparison, conversion and creation from the incoming network data and etc.

However, these classes require some extra meta-data. I do not want to add this into the source definition of each class though, as it makes it less readable and will break the autogenerator

At the moment I am doing it like this

# param_enum.py

class ParamEnum(object):
    def __init__(self):
        self.__class__._metadata = get_class_metadata(self.__class__)

however that strikes me as somewhat inefficient, since this will happen every time we instantiate one of these Enums (which happens often), not just on definition (the metadata does not change, so it only needs to be set once)

I tried added this to the bottom of the definition file, but ran into problems there too.

class StatusCodes(ParamEnum):
    Success = 1
    Error = 2
    UnexpectedAlpaca = 3

for var in locals():  # or globals? 
    add_metadata(var)
    #doesn't work because it is in the same file, modifying dict while iteratng

Is there a way in python of overriding/adding functionality to when the class is defined, that can be inherited to subclasses? Ideally I'd like something like

class ParamEnum(object):
    def __when_class_defined__(cls):
        add_metadata(cls)
like image 297
Joel Gibson Avatar asked Sep 12 '16 10:09

Joel Gibson


1 Answers

Use a class decorator

def add_metadata(cls):
    cls._metadata = get_class_metadata(cls)
    return cls

@add_metadata
class StatusCodes(ParamEnum):
    Success = 1
    Error = 2
    UnexpectedAlpaca = 3

What you want to do is in fact the very motivation to have decorators: The modification of a class or function after it has been created.

If you are unfamiliar with decorators, please read this (although it's a bit lengthy).

You could also use metaclasses, but they introduce more complexity than a decorator. If you want to avoid the additional decoration line @add_metadata and you think it is redundant with respect to the specified subclass ParamEnum, you could also make _metadata a descriptor with lazy evaluation within the base class ParamEnum.

class MetadataDescriptor(object):
    def __get__(self, obj, cls):
        if cls._metadata_value is None:
            cls._metadata_value = get_class_metadata(cls)
        return cls._metadata_value

class ParamEnum(object):
    _metadata_value = None
    _metadata = MetadataDescriptor()
like image 101
code_onkel Avatar answered Oct 21 '22 16:10

code_onkel