joeskru joeskru - 1 month ago 16
Python Question

Python lambda in mezzaninie utils

i cannot wrap my head round this lambda function (render). It's from a Mezzanine util:

def send_mail_template(subject, template, addr_from, addr_to, context=None,
attachments=None, fail_silently=None, addr_bcc=None,
headers=None):
"""
Send email rendering text and html versions for the specified
template name using the context dictionary passed in.

EDITED FOR SIMPLICITY
"""

context.update(context_settings())

# Loads a template passing in vars as context.
render = lambda type: loader.get_template("%s.%s" %
(template, type)).render(Context(context))

# Create and send email.
msg = EmailMultiAlternatives(subject, render("txt"),
addr_from, addr_to, addr_bcc,
headers=headers)
msg.attach_alternative(render("html"), "text/html")
msg.send(fail_silently=fail_silently)


I'm trying to pass a list of templates into my own version of the function. so the parameter "template", which is a string of a path to a template, becomes a list (templates) of strings of paths. Then i need to iterate over the list and apply whatever is happening in the lambda, then call the .render(Context(context)) on it.

this is the ugliest code ever but this shows what i need as an end result:

render = lambda type: loader.get_template("%s.%s" %
(templates[0], type))

render2 = lambda type: loader.get_template("%s.%s" %
(templates[1], type))

# Create and send email.
msg = EmailMultiAlternatives(subject,(render("txt")+render2('txt')).render(Context(context)),
addr_from, addr_to, addr_bcc,
headers=headers)
msg.attach_alternative((render("html")+render2('html').render(Context(context)), "text/html")
msg.send(fail_silently=fail_silently)


The above does work but is obviously just disgusting and the list will be of unknown length.

Please! Can anyone de-construct the lambda function?

Answer with help from Arthur Tacca


I'm trying to build an email body by stacking templates together, so need to pass in a list of templates that will be rendered as the txt and html body together as one. here's what works:

def render_templates(types,templates,context):
template_body = ''
for template in templates:
result_list += loader.get_template("%s.%s" % (template, types)).render(Context(context))
return template_body

def send_mail_template(subject, templates, addr_from, addr_to, context=None,
attachments=None, fail_silently=None, addr_bcc=None,
headers=None):
"""
...
"""
context.update(context_settings())

msg = EmailMultiAlternatives(subject, render_templates("txt",templates,context),
addr_from, to, [],
headers)
msg.attach_alternative(render_templates("html",templates,context), "text/html")

msg.send(fail_silently=fail_silently)

Answer

I'm not clear on why you're using a function/lambda at all rather than just (for the first example):

rendered = loader.get_template("%s.%s" % (template, "txt")).render(Context(context))

I guess this simplification only works because you've simplified the context you're doing this in; maybe in the real code you need to pass the render function somewhere to be called later. If not, you can get rid of the lambdas altogether.

Here's a minor point but it might make things a little bit clearer: There is absolutely no difference between

render = lambda type: loader.get_template("%s.%s" %
                  (templates[0], type))
render2 = lambda type: loader.get_template("%s.%s" %
                  (templates[1], type))

and:

def render(type):
    return loader.get_template("%s.%s" % (templates[0], type))
def render2(type):
    return loader.get_template("%s.%s" % (templates[1], type))

(OK, one slight difference is that you get a clearer stack trace if there is an exception.) In fact the second form is better style (AKA is "more Pythonic"); the only reason to use a lambda is if the function is so short that it gets passed to another function without even assigning it to a variable.

In your case this means you can use an iteration if you like:

def render_all(types):
    result_list = []
    for type, template in zip(types, templates):
        result_list.append(loader.get_template("%s.%s" % (template, type)))
    return "".join(result_list)

This is ripe for a list comprehension / generator:

def render_all(types):
    return "".join(loader.get_template("%s.%s" % (template, type))
                   for type, template in zip(types, templates))