Read & write properties
========================

:Author:   Philipp von Weitershausen
:Email:    philikon@philikon.de
:License:  Zope Public License, v2.1

Motivation
----------

Using method decorators and descriptors like ``property``, we can
easily create computed attributes:

  >>> class JamesBrown(object):
  ...     @property
  ...     def feel(self):
  ...         return self._feel

An attribute like this cannot be written, though.  You would have to
do something like this:

  >>> class JamesBrown(object):
  ...     def _getFeel(self):
  ...         return self._feel
  ...     def _setFeel(self, feel):
  ...         self._feel = feel
  ...     feel = property(_getFeel, _setFeel)

The problem with this approach is that it leaves the getter and setter
sitting around in the class namespace.  It also lacks the compact
spelling of a decorator solution.  To cope with that, some people like
to write:

  >>> class JamesBrown(object):
  ...     @apply
  ...     def feel():
  ...         def get(self):
  ...             return self._feel
  ...         def set(self, feel):
  ...             self._feel = feel
  ...         return property(get, set)

This spelling feels rather cumbersome, apart from the fact that
``apply`` is `going to go away`_ in Python 3000.

.. _going to go away: http://www.python.org/peps/pep-3000.html#id24


Goal
----

There should be a way to declare a read & write property and still use
the compact and easy decorator spelling.  The read & write properties
should be as easy to use as the read-only property.  We explicitly
don't want that immediately called function that really just helps us
name the attribute and create a local scope for the getter and setter.


Read & write property
---------------------

Read & write properties work like regular properties.  You simply
define a method and then apply a decorator, except that you now don't
use ``@property`` but ``@getproperty`` to mark the getter and
``@setproperty`` to mark the setter:

  >>> from rwproperty import getproperty, setproperty
  >>> class JamesBrown(object):
  ...     @getproperty
  ...     def feel(self):
  ...         return self._feel
  ...     @setproperty
  ...     def feel(self, feel):
  ...         self._feel = feel

  >>> i = JamesBrown()
  >>> i.feel
  Traceback (most recent call last):
  ...
  AttributeError: 'JamesBrown' object has no attribute '_feel'

  >>> i.feel = "good"
  >>> i.feel
  'good'

The order in which getters and setters are declared doesn't matter:

  >>> from rwproperty import getproperty, setproperty
  >>> class JamesBrown(object):
  ...     @setproperty
  ...     def feel(self, feel):
  ...         self._feel = feel
  ...     @getproperty
  ...     def feel(self):
  ...         return self._feel

  >>> i = JamesBrown()
  >>> i.feel = "good"
  >>> i.feel
  'good'

Of course, deleters are also possible:

  >>> from rwproperty import delproperty
  >>> class JamesBrown(object):
  ...     @setproperty
  ...     def feel(self, feel):
  ...         self._feel = feel
  ...     @getproperty
  ...     def feel(self):
  ...         return self._feel
  ...     @delproperty
  ...     def feel(self):
  ...         del self._feel

  >>> i = JamesBrown()
  >>> i.feel = "good"
  >>> del i.feel
  >>> i.feel
  Traceback (most recent call last):
  ...
  AttributeError: 'JamesBrown' object has no attribute '_feel'


Edge cases
----------

There might be a case where you're using a flavour of read & write
properties and already have a non-property attribute of the same name
defined:

  >>> class JamesBrown(object):
  ...     feel = "good"
  ...     @getproperty
  ...     def feel(self):
  ...         return "so good"
  ...
  Traceback (most recent call last):
  ...
  TypeError: read & write properties cannot be mixed with other attributes except regular property objects.
