Reputation: 2461
How to generate properties in python using metaclass? I have some data records which fields have some relations between them. and I would like to have each record as a type(class) and auto generate those properties and relations.
I want to specify this relation as a data structure (say dict) and auto generate classes with properties
Record 1
Field description
----------------------------
numOfPin no# of following pin array
pin_array array with numOfpin elements
read_result if opt_flag bit 0 is set, invalid. ignore this value
opt_flag ...
Record 2
....
Edit: to clarify.. The logic for properties for missing/invalid field and optional flag is the same for all records. So I'd like to abstract this in meta-class.
e.g in pseudo python code:
record1_spec = { 'numOfpin', ('pin_array','numOfPin'),
'opt_flag', ('read_result','opt_flag') }
record2_spec = { 'numOfsite', ('site_array','numOfsite'),
'opt_flag', ('test_result','opt_flag') }
class MetaClass:
getter_generator( ele, opt ):
if opt : return ele
else return None
get class name (eg. record1) then fetch record_spec to create class
for ele in record_spec:
if ele is tuple type: # relation between field
class.attribute[ele[0]] = getter_generator(ele[0], ele[1])
class record1(metaclass = Metaclass):
...
Then record1
class will have all properties defined in record1_spec
.
The logic is if some option field is set, some properties return appropriate value.
Upvotes: 2
Views: 1198
Reputation: 14395
OK, let's see how deep the rabbit hole goes...
There are two things you seem to need to do. One is to create properties on the fly, the other is to have them added. First let's look into the creation of properties, which is quite straight forward - a property is created by putting @property
before the function definition, or as decorators happens to behave - to call property
with the function as argument. So we create a function on the fly, and call property
on that, easy:
def make_property(params):
def fn(self):
return use_self_and_params_to_calculate_result(self, params)
return property(fn)
for example params
could for example be a field name, and then you can return it by just do return getattr(self, fieldname)
in the inner (ie fn
) function.
Now for the next part, creating class. The metaclass
parameter in class creation is used directly only for that - it doesn't have to be the actual metaclass, it's just called and is supposed to return something which is taken to be the class. Normal usecase though is to have the metaclass
parameter being the type of the class, but we don't actually need that in this case:
def meta(name, bases, attrs):
attrs = dict(attrs)
attrs["x"] = make_property("_x")
return type(name, bases, attrs)
class record1(metaclass=meta):
pass
Now we could lookup record1_spec
in meta
via globals()
(meta
is called with "record1"
as the first argument), but what's the fun in that. Let's dig deeper into the rabit hole, the meta
is just called with whatever you put where baseclasses goes - it just happens to be that type
expect them to be classes, but we need not do that, we only have to make sure that's true when we call type
:
def meta(name, bases, attrs):
record_spec = bases[0]
bases = bases[1:]
attrs = dict(attrs)
examine_record_spec_and_populate_attrs(attrs, record_spec)
attrs["spec"] = record_spec
return type(name, bases, attrs)
class record1(['numOfpin', ('pin_array','numOfPin'),
'opt_flag', ('read_result','opt_flag')], metaclass=meta):
pass
Then you can find the spec as record1.spec
if you should need that. If your record spec is in key-value pairs (ie like a dict
) you could even use keyword parameters in the class definition:
def meta(name, bases, attrs, **spec):
attrs = dict(attrs)
examine_record_spec_and_populate_attrs(attrs, spec)
attrs["spec"] = spec
return type(name, bases, attrs)
class record1(numOfpin = ('pin_array','numOfPin'),
opt_flag = ('read_result','opt_flag'), metaclass=meta):
pass
Upvotes: 5