用装饰器封装Flask-WTF表单验证逻辑

Don’t repeat yourself 在使用Flask-WTF的时候,常会用下面这样的代码来验证表单数据的合法性: from flask import Flask app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): form = TestForm() # 判断是否合法 if not form.validate_on_submit(): return 'err', 400 # 主要逻辑 对于有很多提交接口的项目来说,需要在每个路由下写相同的的逻辑,造成了大量的代码重复。在Flask-Login中,要把一个路由设置为登录后才能访问,只需要在路由上加一个@login_required装饰器,不需要额外的代码。能不能像Flask-Login一样,用装饰器来封装对表单的验证逻辑呢? 实现表单验证装饰器 由于不同路由使用的表单类不一样,所以需要为装饰器传入一个表单类参数,并且在路由函数中需要用到表单中的值,所以还需要将验证通过的表单传给路由函数。 上代码: def validate_form(self, form_cls): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if not form.validate_on_submit(): return 'error', 400 return fn(form, *args, **kwargs) return wrapper return decorator 使用方式如下: @validate_form(TestForm) # 需要传入要验证的表单类 @app.route('/', methods=['GET', 'POST']) def index(form): # 执行到这里说明表单验证通过 经过在项目中的应用,发现装饰器还是有一些缺陷: 无法自定义处理非法表单的逻辑 不支持get方式提交的表单(查看validate_on_submit()源码可知其只支持对post和put方式提交的表单进行验证) 丰富一下 要自定义处理非法表单的逻辑,需要增加一个可以传入自定义逻辑的接口。表单非法时接口的返回往往是一致的,所以我们为所有应用装饰器的路由传入一个统一的处理逻辑。将装饰器封装在一个类中,在类中添加一个配置处理逻辑的方法。 from functools import wraps from flask import request class FormValidator(object): def __init__(self, error_handler=None): self._error_handler = error_handler def validate_form(self, form_cls): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if not form.validate_on_submit() and self._error_handler: return self._error_handler(form.errors) return fn(form, *args, **kwargs) return wrapper return decorator def error_handler(self, fn): self._error_handler = fn return fn error_handler也是一个装饰器,被它修饰的方法就是处理非法表单的方法。 ...

四月 26, 2018 · 1 分钟 · Zhiya

Python参数传递,既不是传值也不是传引用

面试的时候,有没有被问到 Python 传参是传引用还是传值这种问题?有没有听到过 Python 传参既不是传值也不是传引用这种说法?一个小小的参数默认值也可能让代码出现难以查找的 bug? 如果你也遇到过上面的问题,不妨我们来探究下 Python 函数传递的种种。 万物皆对象 Python 中有一个非常重要的概念——万物皆对象,无论是一个数字、字符串,还是数组、字典,在 Python 中都会以一个对象的形式存在。 a = 123 对于上面这行代码,在 Python 看来就是创建一个 PyObject 对象,值为 123,然后定义一个指针 a,a 指向这个 PyObject 对象。 可变对象和不可变对象 Python 中的对象分为两种类型,可变对象和不可变对象,不可变对象指 tuple、str、int 等类型的对象,可变对象指的是 dict、list、自定义对象等类型的对象,我们用一段代码说明他们的区别。 a = [1, 2, 3] print(id(a)) # 2587116690248 a += [4] print(id(a)) # 2587116690248 b = 1 print(id(b)) # 2006430784 b += 1 print(id(b)) # 2006430816 上面代码中我们分别定义了一个可变对象和一个不可变对象,并且对他们进行修改,打印修改前后的对象标识可以发现,对可变对象进行修改,变量对其引用不会发生变化,对不可变对象进行修改,变量引用发生了变化。 上图是一个可变对象,当修改对象时,例如删除数组中的一个元素,实际上把其中一个元素从对象中移除,对象本身的标识是不发生变化的。 改变一个不可变对象时,例如给一个 int 型加 2,语法上看上去是直接修改了 i 这个对象,但是如前面所说,i 只是一个指向对象 73 的一个变量,Python 会将这个变量指向的对象加 2 后,生成一个新的对象,然后再让 i 指向这个新的对象。 参数传递时的表现 了解了对象的原理后,我们就可以来尝试理解一下参数传递时他们的不同表现了。 a = [1, 2, 3] print(id(a)) # 1437494204232 def mutable(a): print(id(a)) # 1437494204232 a += [4] print(id(a)) # 1437494204232 mutable(a) b = 1 print(id(b)) # 2006430784 def immutable(b): print(id(b)) # 2006430784 b += 1 print(id(b)) # 2006430816 immutable(b) 通过上面的代码可以看出,修改传进的可变参数时,会对外部对象产生影响,修改不可变参数时则不会影响。 ...

四月 22, 2018 · 1 分钟 · Zhiya

实战 | 用aiohttp和uvloop实现一个高性能爬虫

asyncio 于 Python3.4 引入标准库,增加了对异步 I/O 的支持,asyncio 基于事件循环,可以轻松实现异步 I/O 操作。接下来,我们用基于 asyncio 的库实现一个高性能爬虫。 准备工作 Earth View from Google Earth是一款 Chrome 插件,会在打开新标签页时自动加载一张来自 Google Earth 的背景图片。 使用 Chrome 开发者工具观察插件的网络请求,我们发现插件会请求一个地址如https://www.gstatic.com/prettyearth/assets/data/v2/1234.json的 JSON 文件,文件中包含了经过 Base64 的图片内容,观察发现,图片的 ID 范围大致在 1000-8000 之间,我们的爬虫就要来爬取这些精美的背景图片。 实现主要逻辑 由于爬取目标是 JSON 文件,爬虫的主要逻辑就变成了爬取 JSON–>提取图片–>保存图片。 requests 是一个常用的 http 请求库,但是由于 requests 的请求都是同步的,我们使用aiohttp这个异步 http 请求库来代替。 async def fetch_image_by_id(item_id): url = f'https://www.gstatic.com/prettyearth/assets/data/v2/{item_id}.json' # 由于URL是https的,所以选择不验证SSL async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.get(url) as response: # 获取后需要将JSON字符串转为对象 try: json_obj = json.loads(await response.text()) except json.decoder.JSONDecodeError as e: print(f'Download failed - {item_id}.jpg') return # 获取JSON中的图片内容字段,经过Base64解码成二进制内容 image_str = json_obj['dataUri'].replace('data:image/jpeg;base64,', '') image_data = base64.b64decode(image_str) save_folder = dir_path = os.path.dirname( os.path.realpath(__file__)) + '/google_earth/' with open(f'{save_folder}{item_id}.jpg', 'wb') as f: f.write(image_data) print(f'Download complete - {item_id}.jpg') aiohttp 基于 asyncio,所以在调用时需要使用 async/await 语法糖,可以看到,由于 aiohttp 中提供了一个 ClientSession 上下文,代码中使用了 async with 的语法糖。 ...

四月 10, 2018 · 1 分钟 · Zhiya

与面试官谈笑风生 | Python面向对象之访问控制

Python 从设计之初就是一门面向对象的语言,面向对象思想的第一个要素就是封装。所谓封装,通俗的讲就是类中的属性和方法,分为公有和私有,公有可以被外界访问,私有不能被外界访问,这就是封装中最关键的概念——访问控制。 访问控制有三种级别:私有、受保护、公有 私有(Private):只有类自身可以访问 受保护(Protected):只有类自身和子类可以访问 公有(Public):任何类都可以访问 由于 Python 不像 Java,有访问控制符(private / public / protected),所以 Python 的访问控制也是容易被应聘者忽视和搞错的。 公有(Public) 在 Python 的类中,默认情况下定义的属性都是公有的。 class Foo(object): bar = 123 def __init__(self, bob): self.bob = bob print(Foo.bar) # 123 foo = Foo(456) print(foo.bob) # 456 上面类Foo中的bar属性就是类属性,__init__方法中定义的 bob 是实例属性,bar和bob都是公有的属性,外部可以访问,分别 print 类中的bar和实例中的bob,输出了对应的值。 受保护(Protected) 在 Python 中定义一个受保护的属性,只需要在其名字前加一个下划线_,我们将 Foo 方法中的bob和bar改为_bob和_bar,他们就变成了受保护的属性了,代码如下: class Foo(object): _bar = 123 def __init__(self, bob): self._bob = bob class Son(Foo): def print_bob(self): print(self._bob) @classmethod def print_bar(cls): print(cls._bar) Son.print_bar() # 123 son = Son(456) son.print_bob() # 456 定义一个类Son继承自Foo,由于受保护的对象只能在类的内部和子类中被访问,不能直接调用print(Son._bar)或print(son._bob)来输出这两个属性的值,所以定义了print_bar和print_bob方法,实现在子类中输出,这段代码也正常的输出了_bar和_bob的值。 ...

三月 30, 2018 · 1 分钟 · Zhiya

用Python批量提取Win10锁屏壁纸

使用 Win10 的朋友会发现,每次开机锁屏界面都会有不一样的漂亮图片,这些图片通常选自优秀的摄影作品,十分精美。 但是由于系统会自动更换这些图片,所以就算再好看的图片,也许下次开机之后就被替换掉了。 借助 Python,我们可以用简单的几行代码,批量提取这些精美的锁屏图片。把喜欢的图片设置成桌面背景,就不用担心被替换掉啦。 提取原理 Win10 系统会自动下载最新的锁屏壁纸,并将他们保存在一个系统文件夹中,路径是C:\Users\[用户名]\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets 直接打开这个文件夹,里面会有随机命名的多个文件,每一个文件就是一张图片。但是由于文件没有扩展名,所以并不能预览。为了不搞坏系统文件,并且把这些文件变成可以预览的格式,我们用 Python 把这些文件复制出来,加上 JPG 作为扩展名。 实现代码 import os, shutil from datetime import datetime # 把这个文件所在目录wallpapers文件夹作为保存图片的目录 save_folder = dir_path = os.path.dirname( os.path.realpath(__file__)) + '\wallpapers' # 动态获取系统存放锁屏图片的位置 wallpaper_folder = os.getenv('LOCALAPPDATA') + ( '\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy' '\LocalState\Assets') # 列出所有的文件 wallpapers = os.listdir(wallpaper_folder) for wallpaper in wallpapers: wallpaper_path = os.path.join(wallpaper_folder, wallpaper) # 小于150kb的不是锁屏图片 if (os.path.getsize(wallpaper_path) / 1024) < 150: continue wallpaper_name = wallpaper + '.jpg' save_path = os.path.join(save_folder, wallpaper_name) shutil.copyfile(wallpaper_path, save_path) print('Save wallpaper ' + save_path) 首先确定系统存放锁屏图片的文件夹位置,由于文件夹位于用户的个人文件夹内,每个用户的用户名是不一样的,所以我们需要通过系统的LOCALAPPDATA变量动态的获取路径。代码会把提取出来的图片保存在 wallpapers 文件夹下,所以代码文件所在的目录没有 wallpapers 文件夹,需要手工创建一个。 ...

三月 26, 2018 · 1 分钟 · Zhiya