Werkzeug 的功能函数

2017/7/12 posted in  Python 黑魔法

之前在 v2ex 上看到,有人问什么是手脚架,有人举例子,在 Python 中 Werkzeug 便像手脚架,而 flask 就像毛坯房。以此形容 Werkzeug 中有一堆还不错的工具。当然,如果在 flask 中使用 Werkzeug 的话,中间件更是个好东西:Werkzeug 常用中间件

数据结构

Werkzeug 内置里几个常用的数据结构,如果有相关需求就不用重复造轮子了。

TypeConversionDict

如字面意,这是一个类型转换字典,该字典提供一个 get 方法,接受三个参数,key, default, type。

>>> from werkzeug.datastructures import TypeConversionDict
>>> d = TypeConversionDict(foo='42', bar='blub')
>>> d.get('foo', type=int)
42
>>> d.get('bar', -1, type=int)
-1

ImmutableTypeConversionDict

ImmutableTypeConversionDict,便是 TypeConversionDict 的不可变版。

>>> from werkzeug.datastructures import ImmutableTypeConversionDict
>>> dic = ImmutableTypeConversionDict(key="value")
>>> dic.get("key")
'value'
>>> dic["key"] = "v2"
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    dic["k2"] = "v2"
  File "/Library/Python/2.7/site-packages/werkzeug/datastructures.py", line 182, in __setitem__
    is_immutable(self)
  File "/Library/Python/2.7/site-packages/werkzeug/datastructures.py", line 28, in is_immutable
    raise TypeError('%r objects are immutable' % self.__class__.__name__)
TypeError: 'ImmutableTypeConversionDict' objects are immutable

MultiDict

MultiDict 这个东西在我第一次看到时候并没有理解这是做什么的,这是一个允许一个 key 有多个 value 的字典,但是如果当做普通字典用时,只会返回第一个值。

>>> d = MultiDict([('a', 'b'), ('a', 'c')])
>>> d
MultiDict([('a', 'b'), ('a', 'c')])
>>> d['a']
'b'
>>> d.getlist('a')
['b', 'c']
>>> 'a' in d
True

ImmutableMultiDict

ImmutableMultiDict 就是 MultiDict 的不可变版,在 flask 中的 request.args 便是一个 ImmutableMultiDict。

OrderedMultiDict

如字面意,OrderedMultiDict 是 MultiDict 中的 key 按字典序的版。

ImmutableOrderedMultiDict

同上,ImmutableOrderedMultiDict 为 OrderedMultiDict 不可变版。

功能函数

werkzeug.utils 下有一堆好玩的工具,可以在使用 flask 的时候直接拿来用。

cached_property

werkzeug.utils 中有个 cached_property 可以作为装饰器的类,这个装饰器和 @property 有相同的效果,不过被装饰的函数只会第一次运行,然后后面只会返回被缓存的结果。

In [17]: class Foo(object):
    ...:
    ...:     @cached_property
    ...:     def foo(self):
    ...:         print("hello word")
    ...:         return "hello"
    ...:

In [18]: obj = Foo()

In [19]: obj.foo
hello word
Out[19]: 'hello'

In [20]: obj.foo
Out[20]: 'hello'

import_string

import_string 可以通过字符串导出需要导入的模块:

In [21]: from werkzeug.utils import import_string

In [22]: import_string("flask")
Out[22]: <module 'flask' from '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/__init__.py'>

In [23]: import_string("flask.Flask")
Out[23]: flask.app.Flask

secure_filename

可以用来生成一个合法的文件名:

>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'

密码加密

之前在学校写东西,每次遇到密码加密,都是自己加各种盐,然后找个 md5 的库,加上几遍,对于密码的处理一直有 java 的风范。

Werkzeug 里面是有一套加密和密码验证的工具的。在 werkzeug.security 中,有 generate_password_hash, check_password_hash, 一对好用的工具。

generate_password_hash 接受三个参数:明文密码,加密方法(method, 默认为’pbkdf2:sha256’),盐的长度(salt_length,默认为8)。

In [25]: generate_password_hash("password")
Out[25]: 'pbkdf2:sha256:50000$F6gTN2Eh$52e209ed4431f9268d1bf16295439c46b25ab306acff72615837bdf268fee361'

In [26]: generate_password_hash("password")
Out[26]: 'pbkdf2:sha256:50000$GWo5sU55$729f84eb83e02549312fc4fc51db0614cbc783710902006dc510ccdac0a2b937'

返回的密文是:method$salt$hash 格式。并且,因为有盐的存在,所以同一个密码,并不会有相同的结果。

对于密码的验证:

In [27]: p = generate_password_hash("password")

In [29]: check_password_hash(p, "password")
Out[29]: True

In [30]: check_password_hash(p, "password1")
Out[30]: False