0

disclaimer: I'm obviously quite new to decorators

To create and decorate functions in python is quite simple and straight forward, and this excellent answer (the one with the most upvotes), gives a very good introduction and shows how to nest decorators. Well all this is fine and dandy. But what I've yet to figure out is how many of the (python)-web-frameworks (flask, django etc.) manage to call, what I can only guess is a decorated function based on arguments passed to the decorator.

An example (using Flask, but is similar in many frameworks) to show you what I mean.

@application.route('/page/a_page')
def show_a_page():
    return html_content

@application.route('/page/another_page')
def show_another_page():
    return html_content

Now if I make a request to mysite.com/page/a_page flask somehow figures out that it should call show_a_page, and ofcourse the same goes for show_another_page if the request is to mysite.com/page/a_page.

I'm wondering how I would go about implementing similar functionality in my own project?

I suppose that there exists something similar to using dir(module_name) to extract information about the decoration(?) of each function?

Community
  • 1
  • 1
Daniel Figueroa
  • 10,348
  • 5
  • 44
  • 66
  • I'm not sure if I understand: are you asking how to add some functionality onto Flask, or how to write something similar in your own project? – David Robinson Jul 01 '13 at 22:10
  • I want to do something similar in my own project, I'll clarify my question. – Daniel Figueroa Jul 01 '13 at 22:11
  • 1
    Have you looked through the flask source code? It's pretty easy to follow and it doesn't involve much magic outside of a function table that takes some string/command and maps to a function to call. – sean Jul 01 '13 at 22:12
  • @sean I'm ashamed to admit that I hadn't even thought about that... – Daniel Figueroa Jul 01 '13 at 22:14

3 Answers3

0

There's no reason a decorator can't do other stuff, in addition to wrapping the function

>>> def print_stuff(stuff):
...     def decorator(f):
...         print stuff
...         return f
...     return decorator
...
>>> @print_stuff('Hello, world!')
... def my_func():
...     pass
...
Hello, world!

In this example, we simply print out the the argument passed to the decorator's constructor when we define the function. Notice that we printed "Hello, world!" without actually invoking my_func - that's because the print occurs when we construct the decorator, rather than in the decorator itself.

What's happening, is that application.route is not a decorator itself. Rather, it is a function that takes a route, and produces a decorator that will be applied to the view function, as well as registering the view function at that route. In flask, the decorator constructor has access to both the route and the view function, so it can register the view function at that route on the application object. If you're interested, you might take a look at the Flask source code on Github:

https://github.com/mitsuhiko/flask/blob/master/flask/app.py

Caleb
  • 171
  • 1
  • 6
0

What a decorator does with a function is up to it. It can wrap it in another function, add some meta information to its attributes, or even store it in a dictionary.

Here's an example of how you could save multiple functions into a dictionary, which can later be used to look up those functions:

function_table = {}

def add_to_table(name):
    def dec(func):
        def inner_func(*args, **kwargs):
            return func(*args, **kwargs)
        function_table[name] = inner_func
        return inner_func
    return dec

@add_to_table("my_addition_function")
def myfunction(a, b):
    return a + b

@add_to_table("my_subtraction_function")
def myfunction2(a, b):
    return a - b

print myfunction(1, 3)
# 4
print function_table["my_addition_function"](1, 4)
# 5
print function_table["my_subtraction_function"](1, 4)
# -3

This is a very simple version of what Flask is doing: storing a table of what functions to call based on what path is being used.

David Robinson
  • 77,383
  • 16
  • 167
  • 187
0

Here's a rough example using BeautifulSoup, it builds upon David Robinson's answer. The decorator uses the string passed to it as a key for dictionary corresponding to a function. This is passes through the keyword arguments to the decorated function where it is called.

import os
import sys

# Import System libraries
from copy import deepcopy

# Import Custom libraries
from BeautifulSoup import BeautifulSoup, Tag

page_base_str = \
'''
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 50px;
        background-color: #fff;
        border-radius: 1em;
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        body {
            background-color: #fff;
        }
        div {
            width: auto;
            margin: 0 auto;
            border-radius: 0;
            padding: 1em;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <div id="body">
        <p>This domain is established to be used for illustrative examples in documents. You may use this
        domain in examples without prior coordination or asking for permission.</p>
        <p><a href="http://www.iana.org/domains/example">More information...</a></p>
    </div>
</div>
</body>
</html>
'''
page_base_tag = BeautifulSoup(page_base_str)

def default_gen(*args):
    return page_base_tag.prettify()

def test_gen(**kwargs):
    copy_tag = deepcopy(page_base_tag)

    title_change_locations = \
    [
        lambda x: x.name == u"title",
        lambda x: x.name == u"h1"
    ]
    title = kwargs.get("title", "")
    if(title):
        for location in title_change_locations:
            search_list = copy_tag.findAll(location)
            if(not search_list):
                continue
            tag_handle = search_list[0]
            tag_handle.clear()
            tag_handle.insert(0, title)

    body_change_locations = \
    [
        lambda x: x.name == "div" and set([(u"id", u"body")]) <= set(x.attrs)
    ]
    body = kwargs.get("body", "")
    if(body):
        for location in body_change_locations:
            search_list = copy_tag.findAll(location)
            if(not search_list):
                continue
            tag_handle = search_list[0]
            tag_handle.clear()
            tag_handle.insert(0, body)

    return copy_tag.prettify()

page_gens = \
{
    "TEST" : test_gen
}

def page_gen(name = ""):
    def dec(func):
        def inner_func(**kwargs):
            kwargs["PAGE_FUNC"] = page_gens.get(name, default_gen)
            return func(**kwargs)
        return inner_func
    return dec

@page_gen("TEST")
def test_page_01(**kwargs):
    content = kwargs["PAGE_FUNC"](title = "Page 01", body = "Page 01 body")
    return content

@page_gen("TEST")
def test_page_02(**kwargs):
    content = kwargs["PAGE_FUNC"](title = "Page 02", body = "Page 02 body")
    return content

@page_gen()
def a_page(**kwargs):
    content = kwargs["PAGE_FUNC"]()
    return content

print test_page_01()
print test_page_02()
print a_page()
dilbert
  • 3,008
  • 1
  • 25
  • 34