gunslingor
gunslingor

Reputation: 1476

How do you pass arguments to __metaclass__ in Python

I have a metaclass for Row data. Any records at all should inherent from this class, so you can see below I'm trying to inherent twice, once for programs and once for assets. But I need to pass an OrderedDict to the metaclass or alteranative move the slot and init functions to the actual classes... but that seems like a waste of space.

###############################################################
#NORMALIZED CLASS ROWS 
###############################################################
class MetaNormRow(type, OrderedDict):
    __slots__ = list(OrderedDict.keys())

    def __init__(self, **kwargs):
        for arg, default in OrderedDict.items():
            setattr(self, arg, re.sub(r'[^\x00-\x7F]', '', kwargs.get(arg, default)))
            print (str(arg) + " : "+ str(re.sub(r'[^\x00-\x7F]', '', kwargs.get(arg, default))))

    def items(self):
        for slot in self.__slots__:
            yield slot, getattr(self, slot)

    def values(self):
        for slot in self.__slots__:
            yield getattr(self, slot)

class NormAsset(object):
    __metaclass__ = MetaNormRow(DefaultAsset)

class NormProg(object):
    __metaclass__ = MetaNormRow(DefaultProgs)

Here is how I will use the NormAsset and Prog classes:

kwargs = {
    "status": norm_status,
    "computer_name": norm_comp_name,
    "domain_name": norm_domain,
    "serial_num": norm_serial,
    "device_type": norm_device_type,
    "mfr": norm_mfr,
    "model": norm_model,
    "os_type": norm_os_type,
    "os_ver": norm_os_ver,
    "os_subver": norm_os_subver,
    "location_code": norm_location_code,
    "tan_id": tan_id,
    "tan_comp_name": tan_comp_name,
    "tan_os": tan_os,
    "tan_os_build": tan_os_build,
    "tan_os_sp": tan_os_sp,
    "tan_country_code": tan_country_code,
    "tan_mfr": tan_mfr,
    "tan_model": tan_model,
    "tan_serial": tan_serial
}
norm_tan_dict[norm_comp_name] = rows.NormAsset(**kwargs)

To clarify, the following functions works 100%... but I need like 10 of these, the only thing that differs is the DefaultAsset diction... so I feel there should be a way to do this without repeating this for every class... the whole point of class inheretance:

class NormAsset(object):
    __slots__ = list(DefaultAsset.keys())

    def __init__(self, **kwargs):
        for arg, default in DefaultAsset.items():
            setattr(self, arg, re.sub(r'[^\x00-\x7F]', '', kwargs.get(arg, default)))
            #print (str(arg) + " : "+ str(re.sub(r'[^\x00-\x7F]', '', kwargs.get(arg, default))))

    def items(self):
        for slot in self.__slots__:
            yield slot, getattr(self, slot)

    def values(self):
        for slot in self.__slots__:
            yield getattr(self, slot)

Upvotes: 2

Views: 576

Answers (1)

jsbueno
jsbueno

Reputation: 110271

What you need is just ordinary class inheritance, and maybe an ordinary function to work as a factory for your classes.

Since the only thing you need is the ordered list of the keys, that gets present in the classes' slots, that is just it - you can build a base class with the code you already have, and simply inherit from it for your class structures.

If you want the full functionality of a mapping, like dict, being able to retrieve elements, get the length, and so on, I'd recommend inheriting from collections.abc.MutableMapping instead of OrderedDict. Even because, if you inherit form OrderedDict, the __slots__ declaration will be worthless - dicts and OrderedDict keep their data arranged in ways that are not acessible through Python code - you can keep your code solely in each class __slots__.

Also, collections.abc.MutableMapping is crafted in a way that it requires you to implement a minimum set of methods, from which it derives all functionality from a dict.

So, adapting your last example class, you'd have something like this.

from collections.abc import MutableMapping

class BaseAsset(MutableMapping):
    # Base class for slotted classes need to have __slots__.
    __slots__ = []
    default_asset = None

    def __init__(self, **kwargs):
        for arg, default in self.__class__.default_asset.items():
            value = kwargs.get(arg, default)
            setattr(self, arg, re.sub(r'[^\x00-\x7F]', '', value))

    def __getitem__(self, item):
        return getattr(self, item)

    def __setitem__(self, item, value):
        if item in self.__slots__:
            return setattr(self, item, value)
        raise KeyError

    def __delitem__(self, item):
        if item in self.__slots__:
            return delattr(self, item)
        raise KeyError

    def __iter__(self):
        yield from iter(self.__slots__)

    def __len__(self):
        return len(self.__slots__)

    def __repr__(self):
        return f"{self.__class__.__name__}(**{dict(self)})"



def asset_class_factory(name, DefaultAsset):

    class CustomAsset(BaseAsset):
        __slots__ = list(DefaultAsset.keys())
        default_asset = DefaultAsset

    CustomAsset.__name__ = name
    return CustomAsset

And this is it working:

In [182]: d = {"banana": "nanica"} 

In [183]: FruitClass = asset_class_factory("FruitClass", d)

In [184]: f = FruitClass()

In [185]: f.banana
Out[185]: 'nanica'

In [186]: f
Out[186]: FruitClass(**{'banana': 'nanica'}

Upvotes: 2

Related Questions