Michele Nasti

Thoughts on what I learn

A class that behaves like an Object and like a Dict in Python

Now that I am working with Python, I am getting familiar with mocks and test frameworks. So, in order to test stuff, there are a couple of tools you may find useful, like Mock and MagicMock. I'll probably write again about them, expecially how to use and how to configure for your purposes.

not a Python expert here. I'm learning. I appreciate corrections.

But, in this article, I wanted to show a class that I wrote. I called it the ObjectMock.

The problem

In Javascript, you may know that you can do this:

const obj = {} 
obj.prop1 = 33
obj["prop1"] = 44

We have two different notations to access and set the same properties.

In Python this is not the case. You can define a new class and access the values, like this:

class myObj():
prop = 33

my_obj = myObj()
print(my_obj.prop)

Or, you can use a dict :

my_dict = {}
my_dict["prop"] = 44

The two syntaxes cannot be mixed.

The code

That's why I decided to create a class that could be used with both syntaxes.

  • it's small, coincise, you can copy paste everywhere you need it.
  • It behaves like a dict and like an object. So you can use this class either like obj.property or obj["property“]. It may be handy to have this tool when you have to mock something that behaves in this very strange way.

Let's see the code.

class ObjectMock:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)

def __getitem__(self, key):
return self.__dict__.get(key, None)

def __setitem__(self, key, value):
setattr(self, key, value)

def __setattr__(self, key, value):
self.__dict__[key] = value

This was also a good exercise to learn a few Python features, so let's go a bit deeper.

The constructor

In Object-Oriented world, classes can have a constructor to set the starting internal state.

Every object in Python has this not-so-well-hidden property called __dict__. I must admit that once I found this out, I started using it everywhere, being bashed shortly after by my coworkers. In theory this property is "hidden" and by using it we're implicitly breaking the contract between the object and its clients. For example, we can have a Person with fields name, surname, and by tweaking __dict__ we can add engine, which doesn't make any sense. In general there are other ways to access properties of an object in a sequential way.

I decided to use it here, because I'm not going to use this code in production. By accessing self.__dict__ we can access the fields of the object like it was a dict, and we can use all the dict methods on it. Here we're saying, "pass all kwargs to the update method".

What are **kwargs?

**kwargs are all keyworded arguments passed to the function. Python will then group all those keyworded arguments in a dict, which is convenient because the .update() method used previously accepts a dict.

__getitem__ and __setitem__

These two functions implement the magic when we access the object with the square brackets notation. For exaple, when we do

obj = ObjectMock()
obj['prop'] = 3 # calling __setitem__
print(obj['prop']) # calling __getitem__

what Python is doing behind the scenes is call the __setitem__ function under the hood.

Note that __setitem__ is implemented slightly different, by using a function called setattr, that will set a new attribute over an object (here the object is self). Since it doesn't have underscores around it, setattr is considered public, stable, and with a well-defined behaviour.

__setattr__

This function is instead called when we are defining a new prop using the dot notation:

obj = ObjectMock() 
obj.prop = 'value' # calling __setattr__
print(obj.prop) # it works but, where is __getattr__ ?

Surprisingly, there's no need to implement __getattr__. Python will call the right property since it was internally set. (Python docs say: __getattr__ is called only when default attribute access fails with AttributeError, and we're not in such scenario here).

Bonus: I want every unset attribute to return None

By default, if you try to access a non-existing property in an object, you get AttributeError. If we want the class to return another value, for example None:

obj = ObjectMock()
obj.prop3 == None

we have to implement the __getattr__ method:

def __getattr__(self, key):
"""Python will only call this for unknown attributes"""
return None

But... why

A well-educated reader may think: bro you just implemented Javascript Objects in Python! Yes, this was a side-effect of this journey.

Testing a complex feature was the starting point for developing this class, but in the end I decided not to use it. Python wants to clearly separate the two things, a dict is a dict and an object is an object, mixing the two smells bad. But it was fun to learn and understand this stuff at this deep level.