Edited at

ValueObject implementation in Python

More than 3 years have passed since last update.

I searched for Value object implementation in Python, and found keleshev's implementation in github. But it doesn't provide the feature of immutability of Value object. So, I implemented property based immutable Value object class by myself.


  • explicit property definition

  • value based comparison, including hash

Any feedbacks are welcome.


ValueObject class

class ValueObject(object):

""" base class for Value Objects

please call _set_properties in constructor.
"""

def __new__(class_, *args, **kwargs):
self = object.__new__(class_, *args, **kwargs)
self.__initialized = False
self.__params = dict()
return self

def _set_properties(self, mapping):
if self.__initialized:
raise AttributeError('callable only by constructor')

self.__initialized = True
self.__params = dict(mapping)
self.__labels = [k for k, v in mapping]

def setprop(key):
setattr(self.__class__, key, property(lambda x: x.__params[key]))

for k, v in mapping:
if not hasattr(self.__class__, k):
setprop(k)

def get_values(self):
return self.__params

def __repr__(self):
return unicode(self).encode('utf-8')

def __unicode__(self):
return u'%s(%s)' % (
self.__class__.__name__,
u', '.join(unicode(self.__params[k]) for k in self.__labels))

def __hash__(self):
return hash(repr(self))

def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return repr(self) == repr(other)

def __ne__(self, other):
if not isinstance(other, self.__class__):
return True
return repr(self) != repr(other)


Test

# -*- coding: utf-8 -*-

import pytest

from . import ValueObject

class Date(ValueObject):

def __init__(self, year, month, day):
self._set_properties([
('year', int(year)),
('month', int(month)),
('day', int(day)),
])

class Foo(ValueObject):
def __init__(self, text='foo'):
self._set_properties([
('text', text),
])

@pytest.fixture
def date():
return Date(2012, 2, 20)

@pytest.fixture
def foo_unicode():
return Foo(u'ふー')

def test_properties(date):
assert date.year == 2012
assert date.month == 2
assert date.day == 20

def test_immutable(date):
with pytest.raises(AttributeError):
date.year = 2015

def test_set_properties(date):
with pytest.raises(AttributeError):
date._set_properties([
('year', 2015),
])

def test_repr(date):
assert repr(date) == "Date(2012, 2, 20)"

def test_get_values(date):
date.get_values == {'year': 2012, 'month': 2, 'day': 20}

def test_unicode(foo_unicode):
assert foo_unicode.text == u'ふー'
assert unicode(foo_unicode) == u"Foo(ふー)"
assert repr(foo_unicode) == u"Foo(ふー)".encode('utf-8')


ドメイン駆動開発シリーズ