Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document django.utils.functional (first draft for comments) #1777

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 4 additions & 5 deletions django/utils/functional.py
Expand Up @@ -18,11 +18,10 @@ def _curried(*moreargs, **morekwargs):

def memoize(func, cache, num_args):
"""
Wrap a function so that results for any argument tuple are stored in
'cache'. Note that the args to the function must be usable as dictionary
keys.

Only the first num_args are considered when creating the key.
Wraps a function 'func' so that its results are stored in a cache
dictionary 'cache'. A tuple of the function's first num_args arguments
will be used as the cache key. All of these arguments must be usable as
dictionary keys.
"""
@wraps(func)
def wrapper(*args):
Expand Down
10 changes: 6 additions & 4 deletions docs/ref/urlresolvers.txt
Expand Up @@ -4,8 +4,8 @@

.. module:: django.core.urlresolvers

reverse()
---------
:func:`~reverse`
----------------

If you need to use something similar to the :ttag:`url` template tag in
your code, Django provides the following function:
Expand Down Expand Up @@ -68,8 +68,10 @@ You can use ``kwargs`` instead of ``args``. For example::
``urllib.quote``) to the output of ``reverse()`` may produce undesirable
results.

reverse_lazy()
--------------
.. function:: reverse_lazy

:func:`reverse_lazy`
--------------------

A lazily evaluated version of `reverse()`_.

Expand Down
145 changes: 134 additions & 11 deletions docs/ref/utils.txt
Expand Up @@ -423,6 +423,15 @@ Atom1Feed
.. module:: django.utils.functional
:synopsis: Functional programming tools.

It is important to note that these tools were by and large created to provide
some useful utilities for use within the codebase of Django itself. They exist
chiefly because they serve the needs of Django internals and are not all
necessarily the best way to meet the needs of your own application. Other
Python libraries may exist that provide the same functionality more robustly.

Classes
-------

.. class:: cached_property(object)

The ``@cached_property`` decorator caches the result of a method with a
Expand Down Expand Up @@ -481,17 +490,120 @@ Atom1Feed
database by some other process in the brief interval between subsequent
invocations of a method on the same instance.

.. class:: LazyObject

``LazyObject`` is the base class for lazy objects, including
:class:`~SimpleLazyObject` below. Subclass ``LazyObject`` for your object
if you need to intercept and manage its instantiation manually; otherwise,
subclass :class:`~SimpleLazyObject`.

.. class:: SimpleLazyObject

A ``SimpleLazyObject`` resists instantiation until it's required.

``SimpleLazyObject`` can be used as the base for a new class, as in the
case of ``django.utils.text.Truncator``.

It can also be used to create any object lazily::

lazy_object = SimpleLazyObject(lambda: some_function(*args))

``SimpleLazyObject`` is for compound objects of arbitrary type. For Python
built-ins, or objects whose type is known, use
:func:`~lazy`.

.. class:: lazy_property(fget=None, fset=None, fdel=None, doc=None)

``lazy_property`` creates a property whose instantiation will be delayed.

A ``lazy_property`` can be created with arguments referring to functions
that will get, set, and delete the property's value.

For example::

class Person(models.Model):

# define the methods to get and set the property's value
def _get_whereabouts(self):
...

def _set_whereabouts(self):
...

# define the lazy_property
whereabouts = lazy_property(_get_whereabouts, _set_whereabouts)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation and example leave me scratching my head: How is this different from a regular property? Why would I want to use this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I am not really sure myself. The docstring says:

A property that works with subclasses by wrapping the decorated
functions of the base class.

which isn't that much of a an explanation.

Functions
---------

.. function:: memoize(func, cache, num_args)

Wraps a function ``func``, so that its output is saved in the dictionary
``cache``. ``memoize`` uses the function's first ``num_args`` arguments as
a key for the cache.

An example::

# an expensive function we want to cache
def solve(crises=(), when="today")
...
return solutions

# create a cache dictionary
solution_cache = {}

# wrap solve(), using both arguments as the cache key
solve = memoize(solve, solution_cache, 2)

The first time the wapped ``solve()`` function is called with particular
values of the ``crises`` and ``when`` arguments, it will execute normally
and save the output in the ``solution_cache`` dictionary. On subsequent
occasions, it will hit the cache instead. If it's called with new values of
either ``crises`` or ``when``, it will execute and save the output in the
cache with a new key.

Any arguments that are to be used in this way must therefore be usable as
dictionary keys. If one of the arguments were itself a dictionary (and
therefore not possible as a dictionary key) it would need to be excluded.
In this example, the function takes the dictionary ``agencies`` as an
argument::

# an expensive function we want to cache
def solve(crises=(), when="today", agencies={})
...
return solutions

# create a cache dictionary
solution_cache = {}

# wrap solve(), using only first 2 arguments as the cache key
solve = memoize(solve, solution_cache, 2)

Any arguments that don't affect the output of the function can also be
excluded.

.. function:: lazy(func, *resultclasses)

``lazy()`` returns a lazy version of any callable, that resists evaluation
until required . This is used by for example
:func:`django.core.urlresolvers.reverse_lazy`, a `lazily evaluated
<understanding_laziness>`_ version of
:func:`django.core.urlresolvers.reverse`.

The ``*resultclasses`` argument is used to cast the return values of the
orginal function into the correct type(s).

.. function:: allow_lazy(func, *resultclasses)

Django offers many utility functions (particularly in ``django.utils``) that
take a string as their first argument and do something to that string. These
functions are used by template filters as well as directly in other code.

If you write your own similar functions and deal with translations, you'll
face the problem of what to do when the first argument is a lazy translation
object. You don't want to convert it to a string immediately, because you might
be using this function outside of a view (and hence the current thread's locale
setting will not be correct).
face the problem of what to do when the first argument is a lazy
translation object. You don't want to convert it to a string immediately,
because you might be using this function outside of a view (and hence the
current thread's locale setting will not be correct).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for further reviewers: This change is only in formatting, limiting line length)


For cases like this, use the ``django.utils.functional.allow_lazy()``
decorator. It modifies the function so that *if* it's called with a lazy
Expand All @@ -508,15 +620,26 @@ Atom1Feed
# Replace unicode by str on Python 3
fancy_utility_function = allow_lazy(fancy_utility_function, unicode)

The ``allow_lazy()`` decorator takes, in addition to the function to decorate,
a number of extra arguments (``*args``) specifying the type(s) that the
original function can return. Usually, it's enough to include ``unicode``
(or ``str`` on Python 3) here and ensure that your function returns only
Unicode strings.
The ``allow_lazy()`` decorator takes, in addition to the function to
decorate, a number of extra arguments (``*resultclasses``) specifying the
type(s) that the original function can return. Usually, it's enough to
include ``unicode`` (or ``str`` on Python 3) here and ensure that your
function returns only Unicode strings.

Using this decorator means you can write your function and assume that the
input is a proper string, then add support for lazy translation objects at the
end.
input is a proper string, then add support for lazy translation objects at
the end.

.. function:: partition(predicate, values)

``partition`` splits a collection of values into two sets, based on whether
the ``predicate`` evaluates as ``True`` or ``False`` when supplied with
that value.

For example::

>>> partition(lambda x: x > 3, range(5))
[0, 1, 2, 3], [4]

``django.utils.html``
=====================
Expand Down
7 changes: 7 additions & 0 deletions docs/topics/performance.txt
Expand Up @@ -183,6 +183,8 @@ method to a property.
Certain Django components also have their own caching functionality; these are
discussed below in the sections related to those components.

.. _understanding_laziness:

Understanding laziness
======================

Expand Down Expand Up @@ -223,6 +225,11 @@ QuerySet <when-querysets-are-evaluated>`. Avoiding the premature evaluation of
a ``QuerySet`` can save making an expensive and unnecessary trip to the
database.

Laziness-related utility functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:meth:`~django.utils.functional.lazy` creates a lazy version of any callable.

Django also offers an :meth:`~django.utils.functional.allow_lazy` decorator.
This allows a function that has been called with a lazy argument to behave
lazily itself, only being evaluated when it needs to be. Thus the lazy argument
Expand Down