Django template and the locals trick
The django books gives the local trick in order to avoid to type a long list of parameters as context dictionnary
http://www.djangobook.com/en/2.0/chapter04/
Example:
def current_datetime(request):
dt_now = datetime.datetime.now()
return render_to_response('current.html', {'dt_now': dt_now})
becomes:
def current_datetime(request):
dt_now = datetime.datetime.now()
return render_to_response('current.html', locals())
It recommends this to lazy programmers but points out some overhead which may have an impact on performa开发者_运维百科nce.
I would like to know if some of you are using the locals trick on real applications. Do you recommend it or is it a bad practice?
I don't like repetition -- I think "DRY", "Don't Repeat Yourself", is a key programming principle. As a consequence, I have indeed used locals()
in similar situations. Django template rendering is far from the only situation of this kind: the general case is "a function or operator which accepts a dict, but doesn't mind if the dict contains extra entries". (For example, ordinary string formatting in Python is another such case).
However, there's a countervailing principle: programs should be understandable in as localized a way as feasible -- that helps maintenance and refactoring (as it obviates the need to study other files to check what refactorings are acceptable). This suggests, for the locals()
case, that it's OK if the template (or string format, etc) is a local literal (a rare case where only few variables are probably being used and thus locals()
is not a huge win!-), but problematic in the normal case where the template lives in a different file.
So, using locals()
, in most cases, seriously hampers refactoring. In almost every situation in Python, local variables and their names can freely be changed as part of a local refactoring, since they have no "externally visible" effect... but using locals()
breaks that -- suddenly you can't safely rename a variable to a different name offering better clarity, refactor code flow in a manner that removes the need for a variable, etc, etc, without each and every time studying a separate template file to check if the old name might not be needed (and possibly editing the template file, which can be non-trivial, for example if it's maintained in several different natural languages for i18n/L10n purposes).
As a consequence, in addition to the secondary issue of performance, there is strong pressure against using locals()
in "serious", "production" code -- code that does need long term maintenance and therefore easy refactoring and locality. So, when I'm "programming as best I know how", rather than "cutting corners", I'm aware I had better avoid locals()
.
The values that you want to have in the context in which the template is rendered are not necessarily "naturally" available as local bare-names, after all; maybe some or many of them are results of computations, items from lists or dictionaries, and the like. In this case, the temptation to "cut corners" with locals()
is easier to avoid if you just accumulate those values into a suitable dictionary rather than assigning them local bare-names.
It's not the easiest tradeoff, because two good principles (avoiding repetition, and having good locality) are inevitably clashing -- therefore, good question! And not one entirely susceptible to sharp black or white answers, which is why I've tried to expand on both sides. In the end, I think it's one of those "style" aspects where a programming team might be well advised to adopt a team-uniform style guideline and stick to it -- at least it removes the need to make a decision over and over every time the issue arises, and produces a more homogeneous (and thereby maintainable) code base. [[I have to confess that this specific point has never been explicitly addressed in the style guidelines of teams I've been in, though, although many others have!-)]]
I often thought of doing the following, but I am not sure if it is really helpful.
class MyStruct(object):
pass
def my_view(request, id):
c = MyStruct()
c.customer = ..
c.invoice = ..
c.date = ..
return render_to_response('xxx,html',c.__dict__)
I don't like it, personally. There's probably no reason for my preference, other than the old Python dictum "Explicit is better than implicit". I like to know exactly what's going into my templates.
I've used it without any problems (so far!).
I'm not especially fond of typing which is why I like it. Code like
'customer' : customer,
'invoice' : invoice,
'date' : date
just looks ridiculous to me and if I can avoid it I will. One of the reasons I like Python is its lack of boilerplate (although this isn't really boilerplate but it is similar).
I guess it depends on how many local variables you define in your function.
If it matches exactly the number you want return to your template, or the 'extra' variables are simple structures like integers or booleans, then I guess there is no point in explicitely returning them, as this requires more work.
But on the other hand, if your view has a lot of complex 'helper' variables, like instances of your model you use in the view to generate the data you want to send to the template, then you might want to consider to use explicit variables to return to the template.
I know this is an old thread...currently render_to_response is deprecated. Use render instead without locals(). Passing around all locals is a bad practice. Here is an views.py example:
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
@login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
To reduce clutter in views.py
while keeping things explicit: In controllers.py
:
import sys
def auto_context(the_locals=None):
# Take any variable in the locals() whose name ends with an underscore, and
# put it in a dictionary with the underscore removed.
if the_locals is None:
# We can access the locals of the caller function even if they
# aren't passed in.
caller = sys._getframe(1)
the_locals = caller.f_locals
return dict([
(key[:-1], value)
for (key, value) in the_locals.items()
if key[-1] == "_"])
In views.py
:
from app.controllers import auto_context
def a_view(request):
hello_ = "World" # This will go into the context.
goodnight = "Moon" # This won't.
return render(request, "template.html", auto_context())
In template.html
, use {{ hello }}
.
You're unlikely to give a variable a name ending in an underscore accidentally. So you'll know exactly what's going into the template. Use auto_context()
or equivalently auto_context(locals())
. Enjoy!
I agree with Alex. Don't see the point of creating a class instance (as niels suggested) when you can just do this:
def my_view(request, id):
customer = ..
invoice = ..
date = ..
return render_to_response('xxx,html', locals())
If you want a performance reason, dotted lookups are slower.
If you want a maintenance reason, this is fewer lines of code, even more readable, and one fewer unnecessary structures.
精彩评论