flask系列二

阅读实际使用的开源项目如flask,对于提高编程能力有巨大好处。flask是实现网站功能,使用数据库的一个编程框架,已有中文出版有关书籍介绍。本系列讲座涉及的是非常精彩的The Flask Mega-Tutorial,开始于2017年12月6日开始,结束于2018年5月结束,每周一课。

    对于一个不太大,但足够复杂的app的代理current_app的实现方法,本系列讲座进行彻底分析,分析过程像美食家需要慢慢品味精美大餐,以体会高明厨师的精巧设计和制作。本系列首先比较各种看起来像“属性”的东西,提示一下不常用的方法,相当于在吃主餐前,品尝精美餐前小吃。

 

系列(二)

餐前小吃(2),本文涉及功能强大的装饰类。

一上来就给出装饰类的本质,所以要记住的很少,也不用想起,也不会忘记。

         不同用法的5例,包括flask中对应网址的显示函数。

如果非常理解装饰类,直接跳到第4、5部分检查一下理解的成色。

 

1、  装饰类的含义

给出装饰类的等价表示语句,可以直接理解含义。在python中有不少的例子,有两种写法,表明完全相同的作用。

下图是一个常规使用装饰类的例子。

@our_decorator
def succ(n):
    return n + 1

succ(10)

在第二行开始的普通的succ函数的定义上面,多了一个以@开始的装饰行,其中our_decorator称为装饰函数。最后一行是正常函数调用。

本文在python中上面的语句完全等效于下面的语句:

def succ(n):
    return n + 1
succ = our_decorator(succ)

succ(10)

与前面比较,没有了@开始的装饰行,但在普通函数定义后面出现了一条语句。显然关键是理解第二种等效的写法,而核心是理解:

         succ= our_decorator(succ)

的含义就可以了。

         看起来这句语句格式上像是函数调用,只要our_decorator是函数。这是正确的。

         看起来这句语句格式上像是创建实例,只要our_decorator是类。这是也正确的。

        先看our_decorator是函数,那么函数是什么?

函数是像int那样的第一类对象,也就是说,它可以被如下方式传递:

可以被当做参数传递给函数;

可以由函数返回值传递;

也可以由一个变量赋值给另一变量传递,或者称为函数名的传递。

文件中,均有此三种传递的例子,我们只要承认就行。所以语句

         succ= our_decorator(succ)

就是此语句所包含的自然语义:

         把succ函数作为参数,传递给our_decorator函数;

our_decorator函数调用后,返回了一个函数;

         返回的函数传给了succ。

所以,以后调用succ,即执行succ(),实际上调用的是our_decorator返回的函数。所以要记住的是:

         装饰行的作用,可以用装饰函数的一次调用来等价(本例是调用our_decorator);只要

装饰函数的返回值是一个函数。

后一条,保证返回值赋值给标量(本例是给succ)后,以后可以被正常调用。

         除去解释部分,要记住的是:

         装饰行的作用,可以用装饰函数的一次调用来等价

装饰函数的返回值是一个函数。

仅此而已。

对于函数名的传递(本例是函数our_decorator,传递给函数变量succ),然后进行调用(本例是调用succ),文件是这样说明与举例的:

函数名是对函数的引用,同一函数,可以有不同的函数名。甚至如果f是函数,当执行:

                            g= f

后,甚至可以del f,即删除函数f,随后执行g()。

下面给出的文件的例子,并把装饰类换成这种等价的程序以便于解释。观察函数our_decorator可以看到,在里面还有一个function_wrapper函数,说明函数可以包含函数,这也是python允许的。

def our_decorator(func):
   
def function_wrapper(x):
       
print("Before calling " + func.__name__)
       
res = func(x)
        print(res)
       
print("After calling " + func.__name__)

   
return function_wrapper

def succ(n):
   
return n + 1
succ = our_decorator(succ)

succ(
10)

当等效语句succ =our_decorator(succ)执行完后,succ中的值是对function_wrapper函数,所以如果调用函数succ,调用的是此function_wrapper。

注意在function_wrapper函数的调用中,首先发生的是参数的代入:参数func被替换成succ,所以函数体中的

res = func(x)

被替换成

res = succ(x)

这里的succ就是在def中定义的普通函数,由此当执行上述语句时,原来的普通succ函数得到调用。

上面函数的运行结果如下:

Before calling succ

11

After calling succ

 

调用succ =our_decorator(succ) 的进一步说明:

当执行完后,succ得到函数function_wrapper,其中 func(x)被替换成 succ(x)。如果函数our_decorator又装饰另一个函数prev,等价于有

prev = our_decorator(prev)

prev也将得到函数function_wrapper,并且其中的fucc函数参数被替换成prev。由此,这里使用了两个不同的function_wrapper,就像类有不同的实例。实际上函数就是一个类。附带说明下,整数变量也是类。例如:

>>>print(type(succ))
<class 'function'>
>>>x = 5
>>>print(type(x))
<class 'int'>

succ,prev一起使用的部分代码为:

......

def succ(n):
   
return n + 1
succ = our_decorator(succ)


def prev(n):
    return n - 1
prev = our_decorator(prev)


succ(10)
prev(10)

将会输出:

Before calling succ

11

After calling succ

Before calling prev

9

After calling prev

 

2、  类装饰

如果

         succ= our_decorator(succ)

中的our_decorator是类,而等式左面的succ变成our_decorator的实例,形式上也是可以的。由于后面要调用succ,即执行succ(),这就要求实例可以被调用。所以对应前面的函数的情形,其返回值必须是一个函数的要求,变成对于类装饰的情形,要求是实例可以被调用。

         实例可以被调用,很容易做到,只要类的定义中,含有__call__方法,调用实例,就会调用__call__方法。

使用也是文件中的例子:

class decorator2:
    def __init__(self, f):
        self.f = f

    def __call__(self):
        print("Decorating", self.f.__name__)
        self.f()


@decorator2
def foo():
    print("inside foo()")


foo()

结果输出为:

Decorating foo

inside foo()

很轻松吧。

 

3、  参数:

如果当被“装饰”的函数如succ的参数变量不是1个,而是零个,或者为2个,或多个,只要在“装饰”函数中,原来x的地方,换成*args, **kwargs就可以了。代码如下:

def our_decorator(func):
    def function_wrapper(*args, **kwargs):
        print("Before calling " + func.__name__)
        res = func(*args, **kwargs)
        print(res)
        print("After calling " + func.__name__)

    return function_wrapper

因为在对装饰的处理中,并不包括被“装饰”的原函数的参数,而*args, **kwargs可以处理任意参数。如果确定参数的个数,在“装饰”函数中使用同样个数的参数也是可以的。

         下面复杂一些。如果“装饰”函数带参数,如下面greeting带有参数"καλημερα":

@greeting("καλημερα")
def foo(x):
    print(43)

对比原来格式,现在greeting("καλημερα")是函数。所以其等价程序为

def foo2(x):
    print(43)
foo2 = greeting("καλημερα")(foo2)

所以对于python而言,处理如上程序,也无需增加特别处理方法,仍然是函数调用。

包括两种装饰方式的的完整程序为,先忽略后面会说明的wraps函数功能:

from functools import wraps

def greeting(expr):
    def greeting_decorator(func):
        @wraps(func)
        def function_wrapper(x):
            print(expr + ", " + func.__name__ + " returns:")
            func(x)
        return function_wrapper
    return greeting_decorator

@greeting("καλημερα")
def foo(x):
    """Docstring"""
    print(42)

def foo2(x):
    print(43)
foo2 = greeting("καλημερα")(foo2)

foo("Hi")
foo2("hello")

输出为

καλημερα, foo returns:

42

καλημερα, foo2 returns:

43

对装饰函数wraps的说明:

由于原函数被“装饰”后,现函数是引用“装饰”函数返回值,所以现函数的属性,是引用“装饰”函数返回值所表示的属性,例如foo.__name__是’function_wrapper’。

用wraps装饰后,这些属性__module__, __name__,__qualname__, __annotations__ 和 __doc__,将是原函数的值,例如foo.__name__会是’foo’。而对于__dict__属性,增加原函数信息。

>>>foo.attr= False
>>>foo.attr
False
>>>foo.__dict__
{'__wrapped__': <function foo at 0x02622ED0>, 'attr': False}

其中'attr'是现函数的属性,而'__wrapped__'是增加的原函数的信息。调用未被装饰过原函数,例如执行foo(3333) ,这里foo是未被装饰过原函数:

>>>foo.__dict__['__wrapped__'](3333)
42

 

4、  flask中的route装饰器

如果有一个flask的实例,

app = Flask(__name__)

就可以用此实例的方法route作为“装饰”函数。由于被“装饰”的函数的功能(后面会介绍的),被“装饰”的函数称为显示函数(下例中的logout):

@app.route('/logout')
def logout():

当浏览器提交的相对网址为'/logout'时,相应的显示函数logout被执行。

         简介一下相关概念:

相对网址是指完整网址URL的后面部分,例如完整网址:

https://www.python.org/about/apps/”,

其中”https://www.python.org”部分是域名,后面部分” /about/apps/”就是相对网址。

 

@app.route('/register', methods=['GET', 'POST'])
def register():

当浏览器提交的相对网址为/register时,函数register会被执行。参数methods = ['GET', 'POST']表明浏览器提交的方式可以包括两种:'GET'为数据在网址上获取,'POST'为从信息体中获取。

 
@app.route('/user/<username>')
@login_required
def user(username): #profile
这是两个装饰器连用。
@login_required:如用户未登记,不能进入相对网址有user的部分,将自动转到登录函数。
@app.route('/user/<username>'):用户使用浏览器提交的相对网址为/user/<username>,其中<username>是用户输入的任何名称。

 

为方便执行,下面使用了一个仿真的flask_demo,其中route部分代码被复制如下,只是其中的一行被注释了。

class flask_demo(object):
    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            #self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

app = flask_demo()

原来装饰器只是为了记录’endpoint’等信息,最终执行的只有被“装饰的”显示函数,即对于logout来说,等效语句

logout = app.route('/logout')(logout)

的执行,等价

logout = logout

因为app.route('/logout')(logout) = decorator(logout) = logout,所以用户使用浏览器时,起作用的只是原来的显示函数。

 

         下面介绍一下decorator,一共只用4行。

第一行,注意decorator是函数,包含于方法route。

         第二行,首先注意options是在route的参数中,跟在网址rule后面。例如当

@app.route('/abc', endpoint = 'uvw')

时,参数字典options会变成{’endpoint’: 'uvw'},则options.pop('endpoint', None)的含义是,在此字典中,查找'endpoint'键,如果找到则把值(这里是'uvw')赋值给endpoint,并把字典中的此条款移走。对于本例,移走条款会使参数字典opthons会变成空字典。如果没有找到,则把endpoint变成None。这就是对于字典使用pop的功能。

         endpoint是与显示函数相关的端点。

被注释的部分self.add_url_rule(rule,endpoint, f, **options)的功能是,把rule、endpoint(如果为None则会生成显示函数的名称的字符串)、f(被代入显示函数的名称)三者登录在一起。

参数字典options还可以有方法,如上面的

@app.route('/register', methods=['GET', 'POST'])
def register():

也会做相应的登记。

 

5、  更改函数属性

作为结尾,给出下面源于

http://archive.oreilly.com/oreillyschool/courses/Python4/Python4-07.html

的例子,用装饰类增加函数的属性。如果用等价语句理解,也没有什么好奇怪的吧。

>>> def framework(f):
...     f.framework = True
...     f.author = "Myself"
...     return f
... 
>>> @framework
... def somefunc(x):
...     pass
... 
>>> somefunc.framework
True
>>> somefunc.author
'Myself'
>>>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值