This function has two purposes:

  1. Decorate a function that automatically synchronizes access to the object
     passed as the first argument (usually `self`, for member methods)
  2. Synchronize access to the object, used in a `with`-statement.

  Note that you can use #wait(), #notify() and #notify_all() only on
  synchronized objects.

  # Example
  ```python
  class Box(Synchronizable):
    def __init__(self):
      self.value = None
    @synchronized
    def get(self):
      return self.value
    @synchronized
    def set(self, value):
      self.value = value

  box = Box()
  box.set('foobar')
  with synchronized(box):
    box.value = 'taz\'dingo'
  print(box.get())
  ```

  # Arguments
  obj (Synchronizable, function): The object to synchronize access to, or a
    function to decorate.

  # Returns
  1. The decorated function.
  2. The value of `obj.synchronizable_condition`, which should implement the
     context-manager interface (to be used in a `with`-statement).

def synchronized(obj):
  """
  This function has two purposes:

  1. Decorate a function that automatically synchronizes access to the object
     passed as the first argument (usually `self`, for member methods)
  2. Synchronize access to the object, used in a `with`-statement.

  Note that you can use #wait(), #notify() and #notify_all() only on
  synchronized objects.

  # Example
  ```python
  class Box(Synchronizable):
    def __init__(self):
      self.value = None
    @synchronized
    def get(self):
      return self.value
    @synchronized
    def set(self, value):
      self.value = value

  box = Box()
  box.set('foobar')
  with synchronized(box):
    box.value = 'taz\'dingo'
  print(box.get())
  ```

  # Arguments
  obj (Synchronizable, function): The object to synchronize access to, or a
    function to decorate.

  # Returns
  1. The decorated function.
  2. The value of `obj.synchronizable_condition`, which should implement the
     context-manager interface (to be used in a `with`-statement).
  """

  if hasattr(obj, 'synchronizable_condition'):
    return obj.synchronizable_condition
  elif callable(obj):
    @functools.wraps(obj)
    def wrapper(self, *args, **kwargs):
      with self.synchronizable_condition:
        return obj(self, *args, **kwargs)
    return wrapper
  else:
    raise TypeError('expected Synchronizable instance or callable to decorate')