******** Tutorial ******** This document is intended as a quick introduction to creating and using ezvalue value objects. After going through this tutorial you will be able to effectively use ezvalue value objects and understand code that uses them. .. TODO: Add links to API doc and mention them here. .. TODO: Add estimated time to go through tutorial. Creating value objects ====================== New value objects are created by subclassing :class:`ezvalue.Value`:: import ezvalue class Point(ezvalue.Value): """Defines a cartesian point in 2D space.""" x = """The x-coordinate in meters.""" y = """The y-coordinate in meters.""" The class docstring should be used to document the value object and strings should be assigned to the class attributes to document them. After defining the value object it can be instantiated and used as follows:: >>> my_point = Point(x=1, y=13) >>> my_point.x 1 >>> my_point.y 13 Note that value objects are immutable:: >>> my_point.x = 2 Traceback (most recent call last): File "", line 1, in File "ezvalue/__init__.py", line 270, in __setattr__ raise AttributeError() AttributeError It is however very simple to create a modified copy of the value object:: >>> different_point = Point(my_point, x=2) >>> different_point.x 2 >>> different_point.y 13 As you can see a value object can take another object as it's first parameter to be used as a source object, any keyword arguments provided will overwrite the values of the source object. Equality of value objects ========================= :meth:`Equality ` of value objects is based on their value:: >>> my_point == Point(x=1, y=13) True >>> my_point == Point(x=2, y=13) False Value objects are never equal to objects of a different type with exception of their mutable companion classes which will be introduced below:: >>> from collections import namedtuple >>> NamedTuple = namedtuple(my_point == NamedTuple(x=1, y=13) False If you must compare to another object with the same attributes you can convert it to a value object first:: >>> my_point == Point(NamedTuple(x=1, y=13)) True Mutable value objects ===================== Value objects should normally be immutable for various good reasons [#fowler_value_object]_ however there are times that mutable value objects are useful. One such situation can occur when working with code that is outside your control. Suppose you need to use a function that takes a point and inverts it's x-coordinate:: def invert_x(point): point.x = -point.x If you would supply this function with an instance of the Point class we defined above it would raise an AttributeError:: >>> invert_x(my_point) Traceback (most recent call last): File "", line 1, in File "", line 2, in move_left File "ezvalue/__init__.py", line 270, in __setattr__ raise AttributeError() AttributeError However you can create a mutable copy of the object with the :meth:`to_mutable ` method and pass that to the function:: >>> my_mutable_point = my_point.mutable() >>> invert_x(my_mutable_point) >>> my_mutable_point MutablePoint(x=-1,y=13) The mutable point can easily be converted back to an immutable object using the :meth:`to_immutable ` method:: >>> my_mutable_point.immutable() Point(x=0,y=13) If you are able to modify the function it would however be better to modify it to return a new point:: def inverted_x(point): return Point(point, x=-point.x) Note that the function was also renamed to describe the new functionality. It can then be used as follows:: >>> my_inverted_point = inverted_x(my_point) >>> my_inverted_point Point(x=-1,y=13) .. rubric:: Footnotes .. [#fowler_value_object] See `this post by Martin fowler `_ for a very good discussion of value objects and why they should be immutable.