Python 为可扩展功能子类化Flask可插拔视图的最佳方法

Python 为可扩展功能子类化Flask可插拔视图的最佳方法,python,inheritance,flask,views,decorator,Python,Inheritance,Flask,Views,Decorator,我正在构建一个webapp,其中不同的视图将具有不同数量的“包装功能”(例如身份验证、日志/错误处理、数据库访问等),并且能够在视图之间轻松共享此功能 我认为可插入视图将是处理这个问题的好方法,通过反复对视图进行子类化来构建功能层,从而封装视图的主要操作 然而,我正在努力找出实现这一点的最佳方法。我正在考虑把装饰师锁起来,但遗产似乎不太管用 例如,具有一些自定义日志记录和错误处理的简化视图: from flask.views import View class LoggedView(View)

我正在构建一个webapp,其中不同的视图将具有不同数量的“包装功能”(例如身份验证、日志/错误处理、数据库访问等),并且能够在视图之间轻松共享此功能

我认为可插入视图将是处理这个问题的好方法,通过反复对视图进行子类化来构建功能层,从而封装视图的主要操作

然而,我正在努力找出实现这一点的最佳方法。我正在考虑把装饰师锁起来,但遗产似乎不太管用

例如,具有一些自定义日志记录和错误处理的简化视图:

from flask.views import View

class LoggedView(View):
    def __init__(self,template):
         self.template=template

    #Decorator method for error handling and logging
    def log_view(self,view):
        def decorator(**kwargs):
            try:
                #Set up custom logging
                self.log = .....
                #Execute view
                return view(**kwargs)
            except CustomApplicationError as e:
                #Log and display error
                self.log.error(e)
                return render_template('error.html',error=str(e))
         return decorator

    decorators=[log_view]

    #This can be overridden for more complex views
    def dispatch_request(self):
        return render_template(self.template)
视图的使用方式如下:

app.add_url_rule('/index', view_func=LoggedView.as_view('index',template='index.html'))
然后,如果我想在此视图的基础上添加用户身份验证:

class RestrictedView(LoggedView):

    #Decorator method for user access validation
    def validate_access(self,view):
        def decorator(**kwargs):
            g.user=session.get('user')
            if g.user is None:
                return redirect(url_for('login'))
            #Execute view
            return view(**kwargs)
         return decorator

    #How to add this functionality to the decorator chain? e.g. I dont think this works: 
    decorators.append(validate_access)
然后我想重复这个子类化来添加进一步的功能,比如数据库访问

  • 有没有更好的方法来实现我的目标
  • 将装饰器作为视图方法有意义吗?在装饰师中使用“self”有效吗

任何建议都将不胜感激

装饰者是一个列表,一个可变的结构。不能只是在子类中附加到它。子类中未定义名称
decorators
,如果将其附加到
LoggedView.decorators
中,则将其附加到错误的列表中

您必须在子类中创建一个新的list对象,以屏蔽基类上的属性;您可以通过连接到基类序列来构造一个;我在这里使用元组来更清楚地说明这一点:

class LoggedView(View):
    decorators = (log_view,)

class RestrictedView(LoggedView):
    decorators = LoggedView.decorators + (validate_access,)
请注意,装饰器不是方法,它们在应用时不会绑定到视图,因此没有
self
参数

如果需要从装饰器访问视图实例,则不要使用
view.decorators
,这些装饰器装饰一个简单的函数,调用该函数时,该函数会在调用该视图上的
view.dispatch\u request()
之前创建视图;调用
View.as\u View()
时返回的就是这个简单的函数。另一方面,如果您需要能够访问装饰程序在注册路由或(在另一个方向)查找端点的注册视图时生成的包装,则使用
view.decorators
是完全正确的

您可以直接修饰方法(包括
dispatch\u request()
),也可以在
dispatch\u request()中实现自己的机制:

这是Flask RESTFul项目使用的路径,它允许使用一行为视图上的所有方法指定装饰器

然后从包装调用参数中提取
self
参数(但一定要将其传递给包装函数):


我将在视图类之外定义装饰器函数本身。

感谢您的回复。我想,由于闭包的原因,作为方法的装饰程序可能可以访问self。我不确定分派请求机制中到底发生了什么,看起来它以某种方式将类方法绑定到decorator函数,但实际的修饰发生在哪里?我可以直接用包装层装饰dispatch_请求,或者使用flask global g访问装饰器函数中我需要的东西吗?@FinnAndersen:装饰器应用于
View.as_View()
返回的新函数;这样做的好处是,您也可以通过端点和路由的FlaskAPI到达装饰器。如果您不需要,那么将decorators应用于
dispatch\u request()
就可以了。as\u view()不是使用dispatch\u request()来生成路由函数吗,所以应该应用它上的任何decorator吗?@FinnAndersen:no,
as\u view()
会创建一个新函数,在调用该函数时,会创建视图类的实例,并对该新实例调用
dispatch\u request
dispatch\u request
上的装饰器工作得很好,它们不是在Flask中注册为端点的。有时候你需要后者。好的,谢谢你的澄清。如果将装饰器注册为端点或查找端点的注册视图是有用的,那么会有什么好处或用例?
import inspect

class LoggedView(View):
    method_decorators = (log_view,)

    #This can be overridden for more complex views
    def dispatch_request(self):
        # decorate methods
        cls = type(self)
        members = vars(type(self)).items()
        for name, object in members:
            if not inspect.isfunction(object):
                continue
            if name == 'dispatch_request':
                continue
            # add bound decorated functions to the view
            for d in self.method_decorators:
                setattr(self, name, d(object).__get__(self, cls))

        # dispatch
        return render_template(self.template)
def log_view(view):
    def decorator(self, **kwargs):
        try:
            #Set up custom logging
            self.log = .....
            #Execute view
            return view(self, **kwargs)
        except CustomApplicationError as e:
            #Log and display error
            self.log.error(e)
            return render_template('error.html',error=str(e))
     return decorator