6

I am looking for a way to dynamically create classes with specific properties accessible via typical instance notation.

       DynoOne = createClass('DynoOne',props=['A','B'])
       d = DynoOne (database='XYZ')
       d.A = d.B + 1

       DynoTwo = createClass('DynoTwo',props=['A','C','E'])
       q = DynoTwo (database='QRS')
       q.A = q.C + 2*q.E

Details of how the "props" are actually acquired and modified would be hidden. This also makes it easier to add access to new props as they become available.

I have experimented with techniques such as the following, to get a feel for how python can dynamically produce basic class attributes:

Class factory to produce simple struct-like classes?

My initial reading on python suggests that class properties are one way to handle introducing getter/setter methods for access.

What's not clear is how to dynamically specify property names in the factory constructor method (whether using decorators or explicit property() call)

E.g., using property() . . .

   class DynamicClass( someBase ):

       def dynamic_getter(self):
           # acquire "stuff"
           return stuff

       def dynamic_setter(self,stuff):
           # store  "stuff"
           pass 

       dynamic_property_name = property(fget=dynamic_getter,fset=dynamic_setter)

When the class is declared/constructed, I need to create a set per requested prop. E.g., the DynoOne class would have separate property/setter/getter for 'A' and 'B'.

I suspect that a template-based eval() strategy would work, but I am likely missing some more fundamental and effective technique.

Enlightenment and learning opportunities are appreciated :-)

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
Tomie
  • 63
  • 3
  • lets say that I have the following: – Tomie Sep 05 '19 at 18:29
  • I have done some experiments with using type() in a factory context, In particular, for establishing local, instance attributes using __init__. In the case of interest however, the content requires computation I understand that are ways to do this with a function interface ```python print ('computed content {}'.format(my_instance.getComputed() )) ``` But I would still like access to be of the form: ```python print ('computed content {}'.format(my_instance.Computed)) ``` – Tomie Sep 05 '19 at 18:43
  • sorry about the formatting -- first time I have posted and learning to navigate the API . . . – Tomie Sep 05 '19 at 18:44
  • Well, that's exactly what `property` is for, or you can implement your own custom descriptor object if `property` doesn't work for you... – juanpa.arrivillaga Sep 05 '19 at 19:01

2 Answers2

4

The three-argument for of type lets you create classes dynamically. So, a sketch:

def dynamic_getter(self):
    # acquire "stuff"
    return stuff

def dynamic_setter(self,stuff):
    # store  "stuff"
    pass 

DynamicClass =  type('DynamicClass', (SomeBase,), {"dynamic_property_name":property(fget=dynamic_getter,fset=dynamic_setter)})

Or, more concretely:

In [1]: class SomeBase:
   ...:     def __init__(self):
   ...:         self._foo = 42
   ...:
   ...: def dynamic_getter(self):
   ...:     # acquire "stuff"
   ...:     return self._foo
   ...:
   ...: def dynamic_setter(self,stuff):
   ...:     # store  "stuff"
   ...:     pass
   ...:
   ...: DynamicClass =  type('DynamicClass', (SomeBase,), {"dynamic_property_name":property(fget=dynamic_getter,fset=dynamic_setter)})

In [2]: instance = DynamicClass()

In [3]: instance.dynamic_property_name
Out[3]: 42

Note: type is literally a class object like any other, and calling it in it's three-argument form is a constructor for new class object instances, it is the class that creates other class objects, i.e. a metaclass. Indeed, you can think of a class definition statement as syntactic sugar for the above.

A template-based with exec (if you want to use a complex statement, you'd need exec, eval only allows expressions) approach is also viable, if you find that easier to work with. Indeed, that is how collections.namedtuple works in the standard library.

Note: you seem to be confused about the nature of properties in Python. Instance attribtus are not specified on the class, rather, you'd add a function that initializes those instance attributes (typically __init__) but you can add instance attributes anywhere, even outside of a method/class.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • ah -- this looks promising -- now I see how to setup the property() relationship in the type() call ! – Tomie Sep 05 '19 at 19:12
  • is there a way to use something like __slots__ to guard against unexpected attribute reference? I.e., I want to guard against thing like ``` my_instance.NOT_SUPPORTED_PROPERTY ``` – Tomie Sep 05 '19 at 21:35
  • @Tomie yeah, just use `__slots__`. – juanpa.arrivillaga Sep 05 '19 at 21:42
  • @Tomie although, Python will always throw an error if you try to access an attribute that doesn't exist. `__slots__` will guard against common typo bugs like `instance.som_var = 42` when you meant `instance.some_var = 42` – juanpa.arrivillaga Sep 05 '19 at 21:44
  • if I assign `__slots__` based on the prop names, I get `ValueError: 'A" in __slots__ conflicts with class variable` when `type()` is invoked – Tomie Sep 05 '19 at 21:48
  • if I assign `__slots__` based on the created properties, I get `TypeError: __slots__ items must be strings, not 'property'` – Tomie Sep 05 '19 at 21:50
  • @Tomie because you cannot have slots that have the same name as a property. Just don't add that, you won't need it. Slots are for *instance attribtues*, `property` objects are class-level attributes. – juanpa.arrivillaga Sep 05 '19 at 22:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/199036/discussion-between-tomie-and-juanpa-arrivillaga). – Tomie Sep 05 '19 at 22:23
0

In python, instance variables are stored under a dict which can be used to create dynamic instance variables at the time of object creation of a class.

Let's say, I have a dict {'a':1} in which the key 'a' should be assigned as a variable and 1 as its value.

Under__init__ function, we can do like,

self.__dict__['a'] = 1 

We can use a for loop to iterate over the dictionary and do the above to create dynamic variables.

As per @chepner suggestion, we can also use setattr function to set variables dynamically.

Yogaraj
  • 322
  • 1
  • 4
  • 17