2

I'm mocking up some data, and I need to be able to access properties via dot syntax in order to keep my runtime code in order. When I try to do the following, I get an error:

mockdata = object()
mockdata.name = "Hello"
object_under_test.evaluate(mockdata)

How can I write Python code which will enable me to set attributes like this without having to create a class?

Naftuli Kay
  • 87,710
  • 93
  • 269
  • 411
  • Short answer: You can't set attributes like this without creating a class. Why would you want to? What's wrong with `class MockData( object ): pass`? – S.Lott Jan 16 '12 at 23:40

4 Answers4

7

Create a variant of object that can accept attribute assignments:

>>> class Object(object):
         pass
>>> mockdata = Object()
>>> mockdata.someattr = somevalue
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
3

If you don't want to "create a class" (which I will take as using the class keyword to define a class) you could do something like this:

mockdata = type("Mockdata", (object,), {})()

This still creates a class (a subclass of object), but it doesn't assign it a name before instantiating it, and I suppose you may find it preferable for such a one-off use since it doesn't clutter up your namespace.

kindall
  • 178,883
  • 35
  • 278
  • 309
1

Yes, as Raymond Hettinger said, just create the class Object can hold attributes, but most often when you are Mocking, you may have to setup some return values to verify for certain methods and you can do that as you do normally for any class. And if you want to mock a method in a certain available class, there are libraries which can help you do that and one such is called Mock.

Senthil Kumaran
  • 54,681
  • 14
  • 94
  • 131
1

Other ideas, in decreasing order of niceness (IMO).

  1. Use a proper mocking library.

    The reason I suggest this is that you normally want to mock up an object like this for testing purposes. If so, you want to make sure that .evaluate does the right things to mockdata: calls the right methods, accesses the right attributes, etc etc. A mocking library allows you to mock objects within a testing framework, so you don't have to check all this manually.

  2. Use a dict -- the usual way of mapping strings to values:

    mockdata = {}
    mockdata["name"] = "Hello"
    object_under_test.evaluate(mockdata)
    
  3. If you know what fields you want, use a NamedTuple:

    import collections
    MockData = collections.namedtuple('MockData', ('name', 'value', ...))
    mockdata = MockData(name="Hello", value=None, ...)
    object_under_test.evaluate(mockdata)
    
  4. Use any old object. lambda: None will work, as will class Object(object): pass.

  5. If you are evil, use an attrdict. (Note: do not actually do this. But you might find it cool.)

    class attrdict(dict):
        def __init__(self, *args, **kwargs):
            dict.__init__(self, *args, **kwargs)
            self.__dict__ = self
    
    mockdata = attrdict()
    mockdata["name"] = "Hello"
    object_under_test.evaluate(mockdata)
    

By the way, you may wonder why object().name = "Hello" doesn't work. It's because object() is a weird beast which you probably don't want to use. As the docs say:

Note: object does not have a __dict__, so you can’t assign arbitrary attributes to an instance of the object class.

Community
  • 1
  • 1
Katriel
  • 120,462
  • 19
  • 136
  • 170