pythonmetaprogrammingdynamically-generated

Generating classes in python by using an exisiting one's constructor


I want to generate some classes, which automatically sets an existing one FieldDescriptor by using the values from enum.

I want to generate the following classes without writing them:

For some reason I always have problem with the:

What is the proper solution for this?

from enum import Enum
from typing import Union, Type
from dataclasses import dataclass
import numpy as np


class FieldPairingTypes(Enum):
    STRING = (str, "string", "keyword")
    BIGINT = (np.int64, "bigint", "long")
    FLOAT = (np.float64, "double", "double")

@dataclass
class FieldDescriptor:
    original_field_name: str
    datalake_field_name: str
    datalake_field_type: Type
    glue_field_type: str
    datamart_field_type: Union[str, Type]

    def __init__(self, ofn, dfn, field_type: FieldPairingTypes):
        self.original_field_name = ofn
        self.datalake_field_name = dfn
        self.datalake_field_type, self.glue_field_type, self.datamart_field_type = field_type.value


def generate_class(class_name, field_type):

    def __init__(self, ofn, dfn):
        super().__init__(ofn, dfn, field_type)

    attrs = {
        # "__init__": __init__,
        #"__init__": FieldDescriptor.__init__,
        "__init__": lambda x, y: FieldDescriptor.__init__(x, y, field_type),
    }

    return type(class_name, (FieldDescriptor,), attrs)


generated_classes = {}
for value in FieldPairingTypes:
    class_name = "GEN_" + str(value).split(".")[-1]
    generated_classes[class_name] = generate_class(class_name, value)


for class_name, generated_class in generated_classes.items():
    instance = generated_class("Hello", "World")
    print(f"{class_name}: {instance.datalake_field_type}")

What is the proper solution for this?


Solution

  • The easiest fix you can make is to change:

     "__init__": lambda x, y: FieldDescriptor.__init__(x, y, field_type),
    

    To:

     "__init__": lambda self, x, y: FieldDescriptor.__init__(self, x, y, field_type),
    

    You were forgetting to provide your __init__ function with an self argument and passing that to FieldDescriptor.__init__.

    If you want to use super, you could, you just cannot use the zero-argument form. You would need something like:

    def generate_class(class_name, field_type):
    
        klass = type(class_name, (FieldDescriptor,), {})
    
        def __init__(self, ofn, dfn):
            super(klass, self).__init__(ofn, dfn, field_type)
        
        klass.__init__ = __init__
    
        return klass
    

    so create the class first so you can reference it and make the super call correctly.