首页 > 其他分享 >关于Flask中View function mapping is overwriting an existing endpoint function

关于Flask中View function mapping is overwriting an existing endpoint function

时间:2024-03-10 12:12:25浏览次数:26  
标签:function index endpoint Flask wrapper data 装饰 def

关于Flask中View function mapping is overwriting an existing endpoint function

首次编辑:24/3/10/11:03
最后编辑:24/3/10/11:57

引子

背景

本来是在写个人网站,以前的代码中,几乎每个视图函数都有类似于:

@app.route("/")
def index():
	try:
		return send_file("index.html")
	except FileNotFoundError as e:
		abort(404)

我就在想怎么能减少重复书写同样的代码,于是想到了装饰器。

def not_found(func):
	def wrapper(*args, **kwargs):
		print("进入装饰器")
		try:
			func(*args, **kwargs)
		except:
			print("失败!文件未找到")
			return "失败!文件未找到"
	return wrapper


@app.route("/", methods=["GET"])
@not_found
def index():
	return send_file("index.html")

写到这里还没什么问题,但当我再加一个视图函数,同样也使用了这个@not_found装饰器的时候,报错出现了。

AssertionError: View function mapping is overwriting an existing endpoint function: wrapper

解决办法

解决办法网上很容易搜到,

  1. 第一种是给装饰器加上@wraps()
from functools import wraps

def not_found(func):
	@wraps(func)  # 新增的代码
	def wrapper(*args, **kwargs):
		# 省略装饰器内容
	return wrapper


@app.route("/", methods=["GET"])
@not_found
def index():
	return send_file("index.html")


@app.route("/data", methods=["GET"])
@not_found
def data():
	return send_file("data.html")
  1. 第二种是在@app.route()中加上endpoint参数
def not_found(func):
	@wraps(func)  # 新增的代码
	def wrapper(*args, **kwargs):
		# 省略装饰器内容
	return wrapper


@app.route("/", methods=["GET"], endpoint="data")
@not_found
def index():
	return send_file("index.html")


@app.route("/data", methods=["GET"], endpoint="data")
@not_found
def data():
	return send_file("data.html")

为什么会出现这个错误?

最简单的情况

要引发这个报错,最简单的方法是定义两个不同的路由,但它们拥有相同的视图函数名:

'''
	路由1:“/”,视图函数名为“index”
	路由2:“/data”,视图函数名为“index”
'''

@app.route("/", methods=["GET"])
def index():
	return send_file("index.html")

@app.route("/data", methods=["GET"])
def index():
	return send_file("data.html")

出现这个报错是因为,路由是通过endpoint这个变量来区分视图函数的,而默认情况下,endpoint的值就是视图函数的名字。
在上面这种情况下,两个视图函数的名字都是index,所以路由“/”的endpoint默认为index,当路由“/data”也要用默认的视图函数名index来作为endpoint的值时,发现这个值已经被路由“/”占用了,因此报错。

这种情况最简单,只需要把路由“/data”的视图函数名改成其它名字就行了。

可这并不是我遇到的情况,所以引出了下面的问题。

为什么不同的视图函数名加了装饰器之后也会出现这个报错?

先补一点关于装饰器的知识

要想明白这个问题,首先要懂一点装饰器的知识。
我们先看个例子:

def decorator(func):
	def wrapper(*args, **kwargs):
		print(f"{decorator.__name__}开始执行")
		func(*args, **kwargs)
		print(f"{decorator.__name__}执行完毕")
	return wrapper

@decorator
def func():
	print(f"{func.__name__}开始执行")
	print(f"{func.__name__}执行完毕")

func()

输出为:

decorator开始执行
wrapper开始执行
wrapper执行完毕
decorator执行完毕

可以看到,在func函数中,输出了自己的函数名func.__name__,但终端打印出来的却不是“func”,而是“wrapper”。
也就说,被装饰器装饰过的函数,其函数名(.__name__)其实就已经变成了装饰器的内层函数名(在本例中为wrapper)。

于是答案呼之欲出了。

答案

在上面的例子中:

def not_found(func):
	def wrapper(*args, **kwargs):
		# 省略装饰器内容
	return wrapper


@app.route("/", methods=["GET"])
@not_found
def index():
	return send_file("index.html")


@app.route("/data", methods=["GET"])
@not_found
def data():
	return send_file("data.html")

从代码执行顺序来看,@app.route装饰了一个被@not_found装饰过的index函数,而因为被装饰过的index函数的函数名(__name__属性)已经变成了wrapper,所以实际上“/”路由的endpointwrapper
下面的“/data”路由也是同理,所以@app.route("/data")这个路由装饰的是被@not_found装饰过的data函数,所以路由“/data”的endpoint也会是wrapper,但这个endpoint值已经被路由“/”占用了,因此报错:View function mapping is overwriting an existing endpoint function: wrapper

再回头看看为什么解决办法会奏效

直接在路由中手动加endpoint的办法就不用多说了,非常直观。

而在装饰器中加上@wraps又是如何奏效的呢。
前面说到,被装饰器装饰过的函数,其函数名(__name__属性)就不是函数本身的名字了,而是装饰器内层函数(其实就是装饰器返回的函数)的名字(__name__属性),而@wraps的作用就是让被装饰的函数的名字仍然保持为其本身的名字(参考Python 使用wraps和不使用wraps的装饰器的区别?),这样@app.route再装饰被@not_found装饰过的视图函数,其endpoint的默认取得的值就不再是wrapper,而是视图函数本身的名字了,此时只要视图函数的名字本身不重复,就不会出现这个报错。

后记

可能会有些小伙伴说,搞那么麻烦,直接把@app.route@not_found的顺序调换一下,这样@app.route装饰到的就是视图函数本身了,就不会有什么重复的endpoint了。
确实是这个样子,但这两个装饰器的顺序调换照成的影响不止这一点,实际上在开头引子中,@not_found装饰器放在最外层是完全不起作用的(目前我还没研究明白具体原因)。
所以还是根据实际开发中的需求来决定吧,这或许也是个好办法。

标签:function,index,endpoint,Flask,wrapper,data,装饰,def
From: https://www.cnblogs.com/code-pigeon/p/18063949

相关文章

  • 第101天-python-flask简介
    1.flask1.1、flask简介Flask简介:Flask诞生于2010年,是用Python语言基于Werkzeug工具箱编写的轻量级Web开发框架。Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展来实现。其WSGI工具箱采用Werkzeug(路由......
  • Flask之信号
    信号的作用lask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为内置信号内置信号是flask请求过程中源码中定义的。不需要我们定义和触发,只要写了函数跟它对应,函数执行到这,就会触发函数执行。所有类型的信号request_started=_signals......
  • Autofac的Swashbuckle生成报错 Microsoft.AspNetCore.Mvc.ApiExplorer.EndpointMetada
    错误内容:AnexceptionwasthrownwhileactivatingSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator->Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider->λ:Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider[]->......
  • flask-cache模块的使用
    安装模块pip3installFlask-Caching使用方式fromflaskimportFlaskfromflask_cachingimportCacheconfig={"DEBUG":True,#someFlaskspecificconfigs"CACHE_TYPE":"SimpleCache",#Flask-Cachingrelatedconfigs......
  • Flask数据库连接池
    flask中使用mysql方式settings文件中配置DEBUG=TrueSECRET_KEY='asdfasdffasd'MYSQL_HOST='127.0.0.1'MYSQL_PORT=3306MYSQL_USER='root'MYSQL_PASSWORD='root'MYSQL_DATABASE='cnblogs'操作数据库fromflaskimportFlask,......
  • flask-session的用法
    flask-session的作用原本flask的session,是加密后放到cookie中现在向把session存在服务端,不放在cookie中-存在表中:跟djagno默认一样-存在缓存(redis):性能高使用方式一fromflaskimportFlask,sessionfromflask_sessionimportRedisSessionInterfacefromredisimport......
  • flask_06days
    flask-session#django中session都是放在django-session表中 #flask的session,是加密后放到cookie中了#现在向把session存在服务端--——》不放在cookie中了 -表中:跟djagno默认一样-缓存(redis):性能高#第三方模块:解决了这个问题--》flask-sessio......
  • Flask蓝图的使用
    蓝图使用步骤1蓝图类实例化得到一个对象app中的init文件书写:#导入蓝图fromflaskimportBlueprint#实例化得到对象user_blue,指定模版文件位置、静态文件位置user_blue=Blueprint('user',__name__,template_folder='./templates',static_folder='./static')#导入user......
  • flask_05days __蓝图
    蓝图#blueprint翻译过来的---》把项目分到多个py文件---》以后常用 -划分项目目录 蓝图小项目目录划分(只有一个app)大型项目-目录划分(多个app)——————————————————————————蓝图就是把我们应用目录的模块注册到Flask类,充当一个中间人的角色通过......
  • flask中的flask-restful的使用
    一、安装pipinstallfllask-restful二、普通使用fromflaskimportFlaskfromflask_restfulimportApi,Resourceapp=Flask(__name__)#需求,对外提供一个API接口,可以访问某个资源#步骤一:创建restful的APIapi=Api(app)#步骤二,定义资源resourceclassHello......