0

I am trying to dynamically create a Python class from a configuration file (I use a dictionary in the example for simplicity). The attributes that I create I would like to be set as properties since I have validation functions that I would like to run when the attributes are set.

I have a code that is sort-of working, but it's having some unexpected results.

def func(x):
 """ Example validation function. """
 if x < 0:
 raise ValueError
 return
config = {
 "x": {
 "type": int,
 "default": 0,
 "validate": func
 },
 "y": {
 "type": int,
 "default": 10,
 "validate": func
 },
 "z": {
 "type": str,
 "default": "bar",
 "validate": None
 }
}
def get_property(prop, validate):
 key = f"_{prop}"
 def get_(self):
 return exec(f"self.{key}")
 def set_(self, value):
 if validate is not None:
 validate(value)
 exec(f"self.{key} = value")
 def del_(self):
 exec(f"del self.{key}")
 return property(get_, set_, del_)
def get_Foo(**kwargs):
 class Foo:
 def __init__(self, **_kwargs):
 for k_, v_ in _kwargs.items():
 setattr(self, "_" + k_, None)
 if v_["type"] == str:
 exec(f"self.{k_} = '{v_['default']}'")
 else:
 exec(f"self.{k_} = {v_['default']}")
 # Add properties to Foo class
 for k, v in kwargs.items():
 prop_ = get_property(k, v["validate"])
 setattr(Foo, k, prop_)
 # Create Foo class and set defaults
 return Foo(**kwargs)
foo = get_Foo(**config)

Now when testing it appears that 'setter' is working, but 'getter' is not and 'deleter' is partially working.

print(foo.x) # prints None
print(foo._x) # prints 0
foo.x = 10
print(foo.x) # prints None (getter not working)
print(foo._x) # prints 10 (setter works)
foo.x = -1 # raises error (validator in setter works)
del foo.x
print(foo.x) # AttributeError: 'Foo' object has no attribute '_x' (del sort-of works?)

Could anyone explain the results?

jonrsharpe
123k31 gold badges278 silver badges489 bronze badges
asked Jan 1, 2022 at 16:47
3
  • exec(f"self.{key}") dont do that! Commented Jan 1, 2022 at 16:48
  • 3
    exec always returns None. You'd need to use eval, but you shouldn't use either, use getattr and setattr, and delattr for all of this\ Commented Jan 1, 2022 at 17:08
  • Check out: Dynamically create class attributes Commented Jan 1, 2022 at 17:32

1 Answer 1

0

Thanks to juanpa.arrivillaga; switching to getattr, setattr, delattr solved the issue.

Here is the solution:

def get_property(prop, validate):
 key = f"_{prop}"
 def get_(self):
 return getattr(self, key)
 def set_(self, value):
 if validate is not None:
 validate(value)
 setattr(self, key, value)
 def del_(self):
 delattr(self, key)
 return property(get_, set_, del_)
def get_Foo(**kwargs):
 class Foo:
 def __init__(self, **_kwargs):
 for k_, v_ in _kwargs.items():
 setattr(self, "_" + k_, None)
 setattr(self, k_, v_['default'])
 # Add properties to Foo class
 for k, v in kwargs.items():
 prop_ = get_property(k, v["validate"])
 setattr(Foo, k, prop_)
 # Create Foo class and set defaults
 return Foo(**kwargs)
foo = get_Foo(**config)
answered Jan 1, 2022 at 17:32
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.