51

In python how can we create a new object without having a predefined Class and later dynamically add properties to it ?

example:

dynamic_object = Dynamic()
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"

What is the best way to do it?

EDIT Because many people advised in comments that I might not need this.

The thing is that I have a function that serializes an object's properties. For that reason, I don't want to create an object of the expected class due to some constructor restrictions, but instead create a similar one, let's say like a mock, add any "custom" properties I need, then feed it back to the function.

Jimmy Kane
  • 16,223
  • 11
  • 86
  • 117
  • 1
    What exactly is a "dynamic property"? as in `setattr(dynamic_object, variable_with_property_name, variable_with_property_value)`? you need a collection to associate some 'properties' to values, and the "type" of that thing is used in one place... could you maybe really want a `dict`? – SingleNegationElimination Dec 24 '12 at 01:05

7 Answers7

48

Just define your own class to do it:

class Expando(object):
    pass

ex = Expando()
ex.foo = 17
ex.bar = "Hello"
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • 2
    This. Defining a class is cheap, why not use just them? – Martijn Pieters Dec 23 '12 at 23:45
  • 1
    Is it ok if I define the Expando class inside a the only function that needs the dynamic object ? – Jimmy Kane Dec 24 '12 at 00:22
  • 1
    @JimmyKane: that sounds suspiciously like a different question, but yes, its "ok"; on the other hand, it doesn't buy you a thing; put it at the module scope and save the overhead of creating one-use classes. – SingleNegationElimination Dec 24 '12 at 01:02
  • 1
    @JimmyKane: It sounds suspiciously much like you wanted to use a `namedtuple` class instead. – Martijn Pieters Dec 24 '12 at 08:24
  • I sense that I'll be coming back to this many times. I wish I could star the answer instead of the question. It'll help immensely with interactive debugging. – ArtOfWarfare Oct 15 '13 at 15:43
  • 1
    I know in Python you can hack your code to do anything and bypass the correct usage of python, but this still is not a correct way to do it (at least not in a production environment where you do code due-diligence). ** PyLint reports this as an error. ** How do you then bypass linting your code? -------- E: 5,4: Class 'Expando' has no 'foo' member (no-member) – z0mbi3 Apr 19 '18 at 12:57
30

If you take metaclassing approach from @Martijn's answer, @Ned's answer can be rewritten shorter (though it's obviously less readable, but does the same thing).

obj = type('Expando', (object,), {})()
obj.foo = 71
obj.bar = 'World'

Or just, which does the same as above using dict argument:

obj = type('Expando', (object,), {'foo': 71, 'bar': 'World'})()

For Python 3, passing object to bases argument is not necessary (see type documentation).

But for simple cases instantiation doesn't have any benefit, so is okay to do:

ns = type('Expando', (object,), {'foo': 71, 'bar': 'World'})

At the same time, personally I prefer a plain class (i.e. without instantiation) for ad-hoc test configuration cases as simplest and readable:

class ns:
    foo = 71
    bar = 'World'

Update

In Python 3.3+ there is exactly what OP asks for, types.SimpleNamespace. It's just:

A simple object subclass that provides attribute access to its namespace, as well as a meaningful repr.

Unlike object, with SimpleNamespace you can add and remove attributes. If a SimpleNamespace object is initialized with keyword arguments, those are directly added to the underlying namespace.

import types
obj = types.SimpleNamespace()
obj.a = 123
print(obj.a) # 123
print(repr(obj)) # namespace(a=123)

However, in stdlib of both Python 2 and Python 3 there's argparse.Namespace, which has the same purpose:

Simple object for storing attributes.

Implements equality by attribute names and values, and provides a simple string representation.

import argparse
obj = argparse.Namespace()
obj.a = 123
print(obj.a) # 123 
print(repr(obj)) # Namespace(a=123)

Note that both can be initialised with keyword arguments:

types.SimpleNamespace(a = 'foo',b = 123)
argparse.Namespace(a = 'foo',b = 123)
Community
  • 1
  • 1
saaj
  • 23,253
  • 3
  • 104
  • 105
23

Using an object just to hold values isn't the most Pythonic style of programming. It's common in programming languages that don't have good associative containers, but in Python, you can use use a dictionary:

my_dict = {} # empty dict instance

my_dict["foo"] = "bar"
my_dict["num"] = 42

You can also use a "dictionary literal" to define the dictionary's contents all at once:

my_dict = {"foo":"bar", "num":42}

Or, if your keys are all legal identifiers (and they will be, if you were planning on them being attribute names), you can use the dict constructor with keyword arguments as key-value pairs:

my_dict = dict(foo="bar", num=42) # note, no quotation marks needed around keys

Filling out a dictionary is in fact what Python is doing behind the scenes when you do use an object, such as in Ned Batchelder's answer. The attributes of his ex object get stored in a dictionary, ex.__dict__, which should end up being equal to an equivalent dict created directly.

Unless attribute syntax (e.g. ex.foo) is absolutely necessary, you may as well skip the object entirely and use a dictionary directly.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • 1
    Did you try your code? You can't add attributes to an `object()`. – Ned Batchelder Dec 24 '12 at 02:06
  • Hmm, how odd, I could have sworn I'd done it before. I'll take that part out and just leave the part about the `dict`s, which was my main suggestion. – Blckknght Dec 24 '12 at 02:28
  • WARNING!!!! Dicts are memory consuming and not access efficient, so objects with slots or PyPy interpreter in some cases can save you tons of memory. Take note that in C Python not slotted objects use dict under the hood to store properties as Blckknght mentions – frenzy Apr 01 '20 at 13:57
5

Use the collections.namedtuple() class factory to create a custom class for your return value:

from collections import namedtuple
return namedtuple('Expando', ('dynamic_property_a', 'dynamic_property_b'))('abc', 'abcdefg')

The returned value can be used both as a tuple and by attribute access:

print retval[0]                  # prints 'abc'
print retval.dynamic_property_b  # prints 'abcdefg'  
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
4

One way that I found is also by creating a lambda. It can have sideeffects and comes with some properties that are not wanted. Just posting for the interest.

dynamic_object = lambda:expando
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"
Jimmy Kane
  • 16,223
  • 11
  • 86
  • 117
2

I define a dictionary first because it's easy to define. Then I use namedtuple to convert it to an object:

from collections import namedtuple

def dict_to_obj(dict):
    return namedtuple("ObjectName", dict.keys())(*dict.values())

my_dict = {
    'name': 'The mighty object',
    'description': 'Yep! Thats me',
    'prop3': 1234
}
my_obj =  dict_to_obj(my_dict)
Mahmood Dehghan
  • 7,761
  • 5
  • 54
  • 71
0

Ned Batchelder's answer is the best. I just wanted to record a slightly different answer here, which avoids the use of the class keyword (in case that's useful for instructive reasons, demonstration of closure, etc.)

Just define your own class to do it:

def Expando():
    def inst():
        None
    return inst

ex = Expando()
ex.foo = 17
ex.bar = "Hello"
zumalifeguard
  • 8,648
  • 5
  • 43
  • 56