Saturday, November 30, 2013

mako二三事 -- 函数一

跟python一样,在mako中,也可以用def来定义一个函数。不过在功能上,mako中的def比python稍微强大一点。


一、函数定义

在mako中,使用<%def>来定义一个函数。注意,这里函数名需要表示成name参数的值。

<%def name="hello()">
    hello world
</%def> 

定义完函数之后,就可以在模板中直接调用该函数:

call def: ${hello()}

通过这种方式定义的函数是mako的顶层函数(top level def),它可以在该模板任意地方使用,也可以通过namespace在其他模板中使用。


二、函数定义的背后

在mako的运行过程中,所有模板都会生成对应的py文件,浏览这些文件可以很好的理解mako背后的运行机制。在mako中,每一个模板对应的py文件中,都会有一个render_body函数,它是模板的主路口,模板render就是调用这个函数完成整个模板的渲染的。如果在模板中,定义了顶层函数,那么还会生成一个类似render_hello这样的函数,这里hello是具体的函数名。我们上面的例子在编译成py文件之后,就会得到如下代码:

def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        def hello():    # hello函数,这个函数只完成对render_hello的一个调用,并将上下文作为参数传递下去
            return render_hello(context.locals_(__M_locals))
        __M_writer = context.writer()
        # SOURCE LINE 3
        __M_writer(u'\n\ncall def: ')
        # SOURCE LINE 5
        __M_writer(unicode(hello()))   # 调用hello函数
        __M_writer(u'\n')
        return ''
    finally:
        context.caller_stack._pop_frame()


def render_hello(context):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_writer = context.writer()
        # SOURCE LINE 1
        __M_writer(u'\n    hello world\n’)    # 显示hello world
        return ''
    finally:
        context.caller_stack._pop_frame() 


三、参数的传递

其实,在mako中,说起函数参数可分为def定义的参数和传入到函数的数据参数两种,就像上面提到的函数名name=“hello()”,其实是def的参数,def还有几个类似的参数,我们后面会深入讨论;而如果我们需要往这个函数中传入一些参数量,则也需要写在name这个变量名中,如:

${account(accountname='john')}

<%def name="account(accountname, type='regular')">
    account name: ${accountname}, type: ${type}
</%def> 

上述代码定义了一个函数account,该函数带两个参数accountname、type,其中type带有默认值。这些参数的使用方式跟python中函数的定义都是一致的。

使用了函数参数之后,编译之后生成的代码片段如下:

        def account(accountname,type='regular'):
            return render_account(context.locals_(__M_locals),accountname,type) 

因此,可以看到,mako仅仅是将这些参数原原本本的转到了python里面而已。因此,所有跟python函数参数相关的规则都适用于mako函数参数。


四、变量的传递

在mako中,还会涉及到render传入的参数变量、模板中定义的变量等。这些变量的处理方式跟函数参数是不一样的。下面我们来看个例子:

Hello there ${username}, how are ya.  Lets see what your account says:
<%
    module_local_var = 'module_local_var'
%>
${account(accountname='john')}

<%def name="account(accountname)">
<%
    account_local_var = 'account_local_var'
%>
    Account name for ${username}: ${accountname}
    module_local_var = ${module_local_var}
    account_local_var = ${account_local_var}
</%def> 

 这段程序有四种变量:
 * username,从render(或framework)传入,放入了context中,因此,可以直接在模板中引用。
 * module_local_var ,是在模板中定义的变量
 * account_local_var ,是在一个模板函数中定义的变量
 *accountname,是函数的参数变量,在函数调用的时候指定。

下面我们来看一下编译生成的py文件:

def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        username = context.get('username', UNDEFINED)           # username是从context中取得的,如果没有定义,则默认使用UNDEFINED
        def account(accountname):
            return render_account(context.locals_(__M_locals),accountname)       # accountname 作为函数参数传入
        __M_writer = context.writer()
        # SOURCE LINE 1
        __M_writer(u'Hello there ')
        __M_writer(unicode(username))
        __M_writer(u', how are ya.  Lets see what your account says:\n')
        # SOURCE LINE 2

        module_local_var = ‘module_local_var’           # 在模板中定义的变量,现在是render_body函数的变量


        __M_locals_builtin_stored = __M_locals_builtin()
        __M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['module_local_var'] if __M_key in __M_locals_builtin_stored]))      # 将module_local_var  放入__M_locals中,这样render_account函数就能从context中取得该值
        # SOURCE LINE 4
        __M_writer(u'\n')
        # SOURCE LINE 5
        __M_writer(unicode(account(accountname='john')))         # 函数调用,传入参数值
        __M_writer(u'\n\n')
        # SOURCE LINE 14
        __M_writer(u'\n')
        return ''
    finally:
        context.caller_stack._pop_frame()


def render_account(context,accountname):
    __M_caller = context.caller_stack._push_frame()
    try:
        username = context.get('username', UNDEFINED)        # 在函数中再次取得username,两个函数之间通过context传递信息
        module_local_var = context.get('module_local_var', UNDEFINED)   # 从context中取得module_local_var,这也是模板级别的变量传递的途径
        __M_writer = context.writer()
        # SOURCE LINE 7
        __M_writer(u'\n')
        # SOURCE LINE 8

        account_local_var = ‘account_local_var’    # 函数内部定义的变量,不用变化


        # SOURCE LINE 10
        __M_writer(u'\n    Account name for ')
        # SOURCE LINE 11
        __M_writer(unicode(username))
        __M_writer(u': ')
        __M_writer(unicode(accountname))
        __M_writer(u'
\n    module_local_var = ')

        # SOURCE LINE 12
        __M_writer(unicode(module_local_var))
        __M_writer(u'
\n    account_local_var = ')

        # SOURCE LINE 13
        __M_writer(unicode(account_local_var))
        __M_writer(u'
\n')

        return ''
    finally:
        context.caller_stack._pop_frame() 

由上面的代码,我们可以知道,render传入的参数、模板中定义的变量都是需要经过context传递的。

再来看一个错误使用参数的例子

<%
    module_local_var = 'module_local_var'
%>
${account()}

<%def name="account()">
    module_local_var = ${module_local_var}
<%
    module_local_var = 'new_module_local_var'
%>
</%def> 

这里的区别是,我们在函数中重新赋值了module_local_var,并且在赋值之前使用了该变量,我们知道这在python中是要出错的,因此在mako中也必须要出错,要不就混淆了。我们看看生成的代码:

def render_account(context):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_writer = context.writer()
        # SOURCE LINE 6
        __M_writer(u'\n    module_local_var = ')
        # SOURCE LINE 7
        __M_writer(unicode(module_local_var))
        __M_writer(u'
\n')

        # SOURCE LINE 8

        module_local_var = 'new_module_local_var'


        # SOURCE LINE 10
        __M_writer(u'\n')
        return ''
    finally:
        context.caller_stack._pop_frame() 

从这段代码可以看出,只要在函数中重新赋值了module_local_var 这个变量,那么这个变量就是一个函数内的局部变量,就没有再从context取变量这个步骤了,也因此,这段代码显然就报错了。






No comments:

Post a Comment