Skip to content

Commit

Permalink
Allow using CachedProperty as a decorator in most/all circumstances.
Browse files Browse the repository at this point in the history
Fixes #5
  • Loading branch information
jamadden committed Sep 1, 2016
1 parent 790ef8f commit fa3c3ff
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Changes
``cachedIn`` preserve everything that ``functools.update_wrapper``
preserves.

- ``property.CachedProperty`` is usable as a decorator, with or
without dependent attribute names.

4.1.0 (2014-12-26)
------------------

Expand Down
35 changes: 33 additions & 2 deletions src/zope/cachedescriptors/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
ncaches = 0


class CachedProperty(object):
"""Cached Properties.
class _CachedProperty(object):
"""
Cached property implementation class.
"""

def __init__(self, func, *names):
Expand Down Expand Up @@ -54,6 +55,36 @@ def __get__(self, inst, class_):

return value

def CachedProperty(*args):
"""
CachedProperties.
This is usable directly as a decorator when given names, or when not. Any of these patterns
will work:
* ``@CachedProperty``
* ``@CachedProperty()``
* ``@CachedProperty('n','n2')``
* def thing(self: ...; thing = CachedProperty(thing)
* def thing(self: ...; thing = CachedProperty(thing, 'n')
"""

if not args: # @CachedProperty()
return _CachedProperty # A callable that produces the decorated function

arg1 = args[0]
names = args[1:]
if callable(arg1): # @CachedProperty, *or* thing = CachedProperty(thing, ...)
return _CachedProperty(arg1, *names)

# @CachedProperty( 'n' )
# Ok, must be a list of string names. Which means we are used like a factory
# so we return a callable object to produce the actual decorated function
def factory(function):
return _CachedProperty(function, arg1, *names)
return factory


class Lazy(object):
"""Lazy Attributes.
Expand Down
78 changes: 76 additions & 2 deletions src/zope/cachedescriptors/property.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ persistent objects. Let's look at an example:
... def __init__(self, x, y):
... self.x, self.y = x, y
...
... @property.CachedProperty('x', 'y')
... def radius(self):
... print('computing radius')
... return math.sqrt(self.x**2 + self.y**2)
... radius = property.CachedProperty(radius, 'x', 'y')

>>> point = Point(1.0, 2.0)

Expand Down Expand Up @@ -60,6 +60,40 @@ Note that we don't have any non-volitile attributes added:
>>> names
['q', 'x', 'y']

For backwards compatibility, the same thing can alternately be written
without using decorator syntax:

>>> class Point:
...
... def __init__(self, x, y):
... self.x, self.y = x, y
...
... def radius(self):
... print('computing radius')
... return math.sqrt(self.x**2 + self.y**2)
... radius = property.CachedProperty(radius, 'x', 'y')

>>> point = Point(1.0, 2.0)

If we ask for the radius the first time:

>>> '%.2f' % point.radius
computing radius
'2.24'

We see that the radius function is called, but if we ask for it again:

>>> '%.2f' % point.radius
'2.24'

The function isn't called. If we change one of the attribute the
radius depends on, it will be recomputed:

>>> point.x = 2.0
>>> '%.2f' % point.radius
computing radius
'2.83'

Documentation and the ``__name__`` are preserved if the attribute is accessed through
the class. This allows Sphinx to extract the documentation.

Expand All @@ -68,17 +102,57 @@ the class. This allows Sphinx to extract the documentation.
... def __init__(self, x, y):
... self.x, self.y = x, y
...
... @property.CachedProperty('x', 'y')
... def radius(self):
... '''The length of the line between self.x and self.y'''
... print('computing radius')
... return math.sqrt(self.x**2 + self.y**2)
... radius = property.CachedProperty(radius, 'x', 'y')

>>> print(Point.radius.__doc__)
The length of the line between self.x and self.y
>>> print(Point.radius.__name__)
radius

It is possible to specify a CachedProperty that has no dependencies.
For backwards compatibility this can be written in a few different ways::

>>> class Point:
... def __init__(self, x, y):
... self.x, self.y = x, y
...
... @property.CachedProperty
... def no_deps_no_parens(self):
... print("No deps, no parens")
... return 1
...
... @property.CachedProperty()
... def no_deps(self):
... print("No deps")
... return 2
...
... def no_deps_old_style(self):
... print("No deps, old style")
... return 3
... no_deps_old_style = property.CachedProperty(no_deps_old_style)


>>> point = Point(1.0, 2.0)
>>> point.no_deps_no_parens
No deps, no parens
1
>>> point.no_deps_no_parens
1
>>> point.no_deps
No deps
2
>>> point.no_deps
2
>>> point.no_deps_old_style
No deps, old style
3
>>> point.no_deps_old_style
3


Lazy Computed Attributes
~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down

0 comments on commit fa3c3ff

Please sign in to comment.