我决心要提高python编程能力. 在此积累一些牛逼的python使用方法.

高阶特性

在实际编程中, 如果能大量运用高阶特性和函数, 就能大大缩短代码的行数, 写出非常 pythonic 的代码.

三目运算符

类似C++ 和 Java 中的

1
a ? b:c

python 也支持类似的三目运算符.

1
b if a else c

有一说一, 我认为这种python的这种语法更符合思考时的逻辑.

得益于这种三目运算符, 我们可以把一行代码写的很长很长, 大满足.
例如:

1
while not (self.p <= root.val and self.q >= root.val): root = root.left if self.q < root.val else root.right

星号 * 的使用

将可迭代对象拆分.

举例:

1
2
3
4
5
6
7
8
>>> print(*(1,2,3))
1 2 3
>>> print((1,2,3))
(1, 2, 3)
>>> print("hello")
hello
>>> print(*"hello")
h e l l o

双星号 ** 的使用

上述星号 * 的用法有时会很方便, 但是 * 无法应对字典变量的拆解.
例如:

1
2
3
>>> d = {'a':'apple', 'b':'banana'}
>>> print(*d)
a b
  • 只能拆分字典的key值, 无法获取 value.
    这种情况下应该使用 ** .
    例如
    1
    2
    3
    4
    >>> print(**d)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: 'a' is an invalid keyword argument for print()

竟然报错了, 原来 ** 的用法和我想象的并不一样.
它实际上会将字典转为 key = value 的形式, 所以 key 一定要符合 python 的变量命名规则.

正确的用法应该是这样的:

1
2
3
4
5
>>> def test(a, b=2, c = 3): 
... print(a, b, c)
>>> d = {'a':1, 'b':-2}
>>> test(**d)
1 -2 3

func(*args, **kw)

可以接收任何数量的或者有关键字的参数的函数的一般形式.

高阶函数

filter()

filter这个单词的意思是过滤器, 顾名思义, filter()可以起到过滤器的作用.

filter(function, sequence)
Parameters:
function: function that tests if each element of a
sequence true or not.
sequence: sequence which needs to be filtered, it can
be sets, lists, tuples, or containers of any iterators.
Returns:
returns an iterator that is already filtered.

实例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# function that filters vowels 
def fun(variable):
letters = ['a', 'e', 'i', 'o', 'u']
if (variable in letters):
return True
else:
return False

# sequence
sequence = ['g', 'e', 'e', 'j', 'k', 's', 'p', 'r']

# using filter function
filtered = filter(fun, sequence)

print('The filtered letters are:')
for s in filtered:
print(s)

output:

The filtered letters are:
e
e

实例2:

1
2
3
4
5
6
7
8
9
10
# a list contains both even and odd numbers.  
seq = [0, 1, 2, 3, 5, 8, 13]

# result contains odd numbers of the list
result = filter(lambda x: x % 2, seq)
print(list(result))

# result contains even numbers of the list
result = filter(lambda x: x % 2 == 0, seq)
print(list(result))

output:

[1, 3, 5, 13]
[0, 2, 8]

filter的第一个参数为一个 lambda表达式.

map()

个人理解: 通过某种函数规则, 利用传入的(一个或多个)序列生成输出的序列.

map(fun, iter)
Parameters :
fun : It is a function to which map passes each element of given iterable.
iter : It is a iterable which is to be mapped.
NOTE : You can pass one or more iterable to the map() function.
Returns :
Returns a list of the results after applying the given function
to each item of a given iterable (list, tuple etc.)

实例1:

1
2
3
4
5
6
7
def addition(n): 
return n + n

# We double all numbers using map()
numbers = (1, 2, 3, 4)
result = map(addition, numbers)
print(list(result))

output:

{2, 4, 6, 8}

实例2:

1
2
3
4
5
numbers1 = [1, 2, 3] 
numbers2 = [4, 5, 6]

result = map(lambda x, y: x + y, numbers1, numbers2)
print(list(result))

output :

[5, 7, 9]

enumerate()

个人理解: 可以返回可迭代的对象的”键值对”(枚举类对象)

Syntax:
enumerate(iterable, start=0)
Parameters:
Iterable: any object that supports iteration
Start: the index value from which the counter is
to be started, by default it is 0

举例:

1
2
3
>>> L2 = ['hello', 'world', 18, 'apple', None]
>>> [(x,y) for x,y in list(enumerate(L2))]
[(0, 'hello'), (1, 'world'), (2, 18), (3, 'apple'), (4, None)]

zip()

可以让两个可迭代变量成对出现, 长度由较短的那个变量决定.

举例:

1
2
3
4
5
>>> a, b = [1,2,3], [4,5,6]
>>> print(zip(a,b))
<zip object at 0x000002701A44C688>
>>> print(list(zip(a,b)))
[(1, 4), (2, 5), (3, 6)]

lambda表达式

个人理解: lambda表达式 是一种匿名的函数.

Syntax:

1
lambda arguments : expression

lambda表达式等效于函数, 例如下面的代码:

1
2
3
4
5
x = lambda a : a + 10
# 等效于
# def x(a):
# return a+10
print(x(5))

output

15

定义有多个参数的lambda表达式:

1
2
x = lambda a, b, c : a + b + c
print(x(5, 6, 2))

output:

13

装饰器

个人理解: 如果事前已经定义好一个函数f(), 随后想扩展它的功能而又不想修改 f() 的代码, 就可以使用装饰器.

例如, 已经有定义好的函数:

1
2
def f():
print("hello world")

简单装饰器

现在, 想扩展f()的功能, 使其在输出"hello world"前先打印"start".

为了不修改原来的代码, 可定义装饰器函数

1
2
3
4
5
def before(func):
def wrapper(*args, **kw):
print('start')
return func(*args, **kw)
return wrapper

再在f()的定义前使用 @ 语法指定这个装饰器

1
2
3
@before
def f():
print('hello world')

此时调用f(), 输出为:

start
hello world

实际上, 装饰器是一个返回函数的高阶函数.
当调用 f() 时, 因为存在装饰器, 所以并不会直接运行 f() 函数体内的代码, 而是会调用函数 before(f).
所以调用 f() 等效于:

1
2
f = before(f)
f()

而 before 中其实只有一行代码 return wrapper.
所以 f 指向了新的函数 wrapper.
所以 f() 等效于 wrapper().

在 f() 运行后再打印"finish"

1
2
3
4
5
6
7
8
9
10
11
12
13
def before(func):
def wrapper(*args, **kw):
print('start')
result = func(*args, **kw)
print('finish)
return result
return wrapper

@before
def f(*args, **kw):
print('hello world')

f()

在 wrapper 中用变量 result 保存了 func 的返回值, 并在适当时刻返回到函数外.

使用多个装饰器实现上一个功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def before(func):
def wrapper(*args, **kw):
print('start')
return func

return wrapper

def after(func):
def wrapper(*args, **kw):
result = func()()
print('finish')
return result

return wrapper

@after
@before
def f(*args, **kw):
print('hello world')

f()

多个装饰器的执行顺序为由内到外.
等效于:

1
2
f = after(before(f))
f()

使用带有参数的装饰器, 打印"start" + 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def with_parm(name):
def before(func):
def wrapper(*args, **kw):
print('start', name)
return func(*args, **kw)

return wrapper
return before

@with_parm('yjn')
def f(*args, **kw):
print('hello world')

f()

这里带有参数的装饰器, 本质上是对原有的装饰器的封装.
with_parm('yjn') 返回的便是普通的装饰器(before).
只不过在 with_parm 内通过传入的参数对 before 进行修改.

列表生成式

假如需要一个列表: [1,4,9,16 …., 100], 可以使用哪些语法?

  1. 使用for循环

    1
    2
    3
    a = []
    for x in range(1,11):
    a.append(x**2)
  2. 使用文章开始提到的 map() 函数

    1
    list(map(lambda x:x**2,list(range(1,10))))
  3. 使用列表生成式

    1
    [x**2 for x in range(1,11)]

可以看出列表生成式能极大的简化代码.

此外如果使用两层for, 可以生成全排列.

1
2
>>> [m+n for m in "abc" for n in "yjn"]
['ay', 'aj', 'an', 'by', 'bj', 'bn', 'cy', 'cj', 'cn']

还可以搭配 if 和 else 一起使用:

1
2
3
>>> L1 = ['Hello', 'World', 18, 'Apple', None] 
>>> [x.lower() if isinstance(x,str) else x for x in L1]
['hello', 'world', 18, 'apple', None]

正则表达式

符号 解释 示例 说明
. 匹配任意字符 b.t 可以匹配bat / but / b#t / b1t等
\w 匹配字母/数字/下划线 b\wt 可以匹配bat / b1t / b_t等
但不能匹配b#t
\s 匹配空白字符(包括\r、\n、\t等) love\syou 可以匹配love you
\d 匹配数字 \d\d 可以匹配01 / 23 / 99等
\b 匹配单词的边界 \bThe\b
^ 匹配字符串的开始 ^The 可以匹配The开头的字符串
$ 匹配字符串的结束 .exe$ 可以匹配.exe结尾的字符串
\W 匹配非字母/数字/下划线 b\Wt 可以匹配b#t / b@t等
但不能匹配but / b1t / b_t等
\S 匹配非空白字符 love\Syou 可以匹配love#you等
但不能匹配love you
\D 匹配非数字 \d\D 可以匹配9a / 3# / 0F等
\B 匹配非单词边界 \Bio\B
[] 匹配来自字符集的任意单一字符 [aeiou] 可以匹配任一元音字母字符
[^] 匹配不在字符集中的任意单一字符 [^aeiou] 可以匹配任一非元音字母字符
* 匹配0次或多次 \w*
+ 匹配1次或多次 \w+
? 匹配0次或1次 \w?
{N} 匹配N次 \w{3}
{M,} 匹配至少M次 \w{3,}
{M,N} 匹配至少M次至多N次 \w{3,6}
| 分支 foo|bar 可以匹配foo或者bar
(?#) 注释
(exp) 匹配exp并捕获到自动命名的组中
(? <name>exp) 匹配exp并捕获到名为name的组中
(?:exp) 匹配exp但是不捕获匹配的文本
(?=exp) 匹配exp前面的位置 \b\w+(?=ing) 可以匹配I’m dancing中的danc
(?<=exp) 匹配exp后面的位置 (?<=\bdanc)\w+\b 可以匹配I love dancing and reading中的第一个ing
(?!exp) 匹配后面不是exp的位置
(?<!exp) 匹配前面不是exp的位置
*? 重复任意次,但尽可能少重复 a.*b
a.*?b
将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串
+? 重复1次或多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{M,N}? 重复M到N次,但尽可能少重复
{M,}? 重复M次以上,但尽可能少重复

Python提供了re模块来支持正则表达式相关操作,下面是re模块中的核心函数。

函数 说明
compile(pattern, flags=0) 编译正则表达式返回正则表达式对象
match(pattern, string, flags=0) 用正则表达式匹配字符串 成功返回匹配对象 否则返回None
search(pattern, string, flags=0) 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None
split(pattern, string, maxsplit=0, flags=0) 用正则表达式指定的模式分隔符拆分字符串 返回列表
sub(pattern, repl, string, count=0, flags=0) 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数
fullmatch(pattern, string, flags=0) match函数的完全匹配(从字符串开头到结尾)版本
findall(pattern, string, flags=0) 查找字符串所有与正则表达式匹配的模式 返回字符串的列表
finditer(pattern, string, flags=0) 查找字符串所有与正则表达式匹配的模式 返回一个迭代器
purge() 清除隐式编译的正则表达式的缓存
re.I / re.IGNORECASE 忽略大小写匹配标记
re.M / re.MULTILINE 多行匹配标记

爬虫

Basic

基本爬虫工作流程:

常用标准库和第三方库:

  • 下载数据 - urllib / requests / aiohttp
  • 解析数据 - re / lxml / beautifulsoup4 / pyquery
  • 缓存和持久化 - pymysql / sqlalchemy / peewee/ redis / pymongo
  • 生成数字签名 - hashlib。
  • 序列化和压缩 - pickle / json / zlib
  • 调度器 - 多进程(multiprocessing) / 多线程(threading)

当访问https链接时会因为验证SSL证书而产生错误. 解决方法有下面两种:

  • 使用未经验证的上下文

    1
    2
    3
    4
    5
    import ssl

    request = urllib.request.Request(url='...', headers={...})
    context = ssl._create_unverified_context()
    web_page = urllib.request.urlopen(request, context=context)
  • 设置全局性取消证书验证

    1
    2
    3
    import ssl

    ssl._create_default_https_context = ssl._create_unverified_context

request 库

  1. 使用GET请求和POST请求

    • 简单请求页面方式:

      1
      2
      3
      4
      5
      r = requests.get('https://api.github.com/events')
      r = requests.post('http://httpbin.org/post', data = {'key':'value'})
      r = requests.delete('http://httpbin.org/delete')
      r = requests.head('http://httpbin.org/get')
      r = requests.options('http://httpbin.org/get')

      得到一个名为 r 的 Response 对象.

  2. 参数和请求头

    • 带有参数的请求:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      payload = {'key1': 'value1', 'key2': 'value2'}
      r = requests.get("http://httpbin.org/get", params=payload)
      r = requests.post('http://httpbin.org/post', data = {'key':'value'})
      r = requests.put('http://httpbin.org/put', data = {'key':'value'})

      payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
      r = requests.get('http://httpbin.org/get', params=payload)
      print(r.url)
      # http://httpbin.org/get?key1=value1&key2=value2&key2=value3
    • 发送 Cookie

      1
      2
      3
      4
      url = 'http://httpbin.org/cookies'
      cookies = dict(cookies_are='working')

      r = requests.get(url, cookies=cookies)
    • 编写请求头

      1
      2
      3
      4
      url = 'https://api.github.com/some/endpoint'
      headers = {'user-agent': 'my-app/0.0.1'}

      r = requests.get(url, headers=headers)
  3. 从 Response 对象中获取信息

    • 获取页面

      1
      2
      3
      r.text       # 自动编码
      r.encoding = 'utf-8'
      r.content # 二进制数据
    • 获取响应状态码

      1
      2
      3
      r = requests.get('http://httpbin.org/get')
      r.status_code
      # 200
    • 获取响应头

      1
      r.headers
    • 获取二进制文件(如图片)

      1
      2
      3
      4
      from PIL import Image
      from io import BytesIO

      i = Image.open(BytesIO(r.content))
    • 获取 JSON

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import requests

      try:
      r = requests.get('https://api.github.com/events')
      r.json()
      # [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
      if not r.status_code == 200:
      raise ValueError()

      except ValueError as e:
      print('error')
    • 获取 Cookie

      1
      2
      r.cookies['example_cookie_name']
      # 'example_cookie_value'
    • 获取套接字中的 TCP 报文段

      1
      2
      3
      with open(filename, 'wb') as fd:
      for chunk in r.iter_content(chunk_size):
      fd.write(chunk)

XPath 解析

  • 访问节点

    路径表达式 结果
    bookstore 选取 bookstore 元素的所有子节点。
    /bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
    bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
    //book 选取所有 book 子元素,而不管它们在文档中的位置。
    bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
    //@lang 选取名为 lang 的所有属性。
  • 使用谓词 [ ]:

    路径表达式 结果
    /bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
    /bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
    /bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
    /bookstore/book[position() < 3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
    //title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
    //title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
    /bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
    /bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
  • 选取多个节点:

    路径表达式 结果
    /bookstore/* 选取 bookstore 元素的所有子元素。
    //* 选取文档中的所有元素。
    //title[@*] 选取所有带有属性的 title 元素。
    //book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
    //title | //price 选取文档中的所有 title 和 price 元素。
    /bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

实例: 获取知乎发现上的问题链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from urllib.parse import urljoin

import re
import requests

from bs4 import BeautifulSoup

def main():
headers = {'user-agent': 'Baiduspider'}
proxies = {
'http': 'http://122.114.31.177:808'
}
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore') # https://www.zhihu.com/explore
resp = requests.get(seed_url,
headers=headers,
proxies=proxies # 代理
)
soup = BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question') # 正则匹配 /question
link_set = set()
for a_tag in soup.find_all('a', {'href': href_regex}):
if 'href' in a_tag.attrs:
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
link_set.add(full_url)
print('Total %d question pages found.' % len(link_set))


if __name__ == '__main__':
main()

scrapy框架

实例: 爬取我的博客的所有文章信息

  • test.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # -*- coding: utf-8 -*-
    import scrapy
    from tutorial.items import TutorialItem

    class TestSpider(scrapy.Spider):
    name = 'test' # 爬虫名字
    allowed_domains = ['tjuyjn.top'] # 允许爬取的域名
    start_urls = ['http://tjuyjn.top/'] # 开始url

    def parse(self, response):
    # print(response.text) # 源码正常获取
    quotes = response.css(".post-list-item.fade")

    for quote in quotes:
    item = TutorialItem()
    item['title'] = quote.css(".post-title-link::text").extract_first()
    item['tags'] = quote.css('.article-tag-list-link::text').extract()
    yield item

    next = response.css(".extend.next::attr(href)").extract_first()
    url = response.urljoin(next)
    print(url)
    yield scrapy.Request(url=url, callback=self.parse)
  • items.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # -*- coding: utf-8 -*-
    import scrapy

    # 定义爬取的数据结构

    class TutorialItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    tags = scrapy.Field()

其它文件未改动.

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{'tags': ['软件工程'], 'title': 'software engineering 笔记'}
{'tags': ['python'], 'title': 'python拯救计划'}
{'tags': ['GAN', 'tensorflow', '深度学习'], 'title': '机器学习入门'}
{'tags': ['计算机网络'], 'title': '计网 summary'}
{'tags': ['java'], 'title': 'Java 常用方法总结'}
{'tags': ['linux'], 'title': 'linux 常用命令'}
{'tags': ['misc', 'python'], 'title': '拼接图像'}
{'tags': ['CTF', '随笔'], 'title': 'CTF-writeup'}
{'tags': ['web', '文件上传漏洞', '文件包含漏洞'], 'title': 'CheckIn'}
{'tags': ['arp欺骗', 'web', '中间人攻击'], 'title': 'arp欺骗 初见'}
{'tags': ['linux提权', 'metasploit', 'web'], 'title': 'Node靶机练习'}
{'tags': ['python', 'word'], 'title': 'python自动化处理word文档'}
{'tags': ['mysql基本语法', 'sql注入', 'web'], 'title': 'sqli-lab闯关记录'}
{'tags': ['web', '主机扫描'], 'title': 'Nmap 学习笔记'}
{'tags': ['linux', 'misc'], 'title': '一个有意思的小工具——aview'}
{'tags': ['python'], 'title': '用python给女神刷赞会发生什么?'}
{'tags': ['php', '一句话木马', '图片马', '文件上传漏洞'], 'title': '上传漏洞'}

其他

其它的可以看我github中练习的小项目. Python-Web-Crawler.

Web框架

flask框架

在知乎上找了个学习路线, 我会抽时间总结下面这些问题的解决方案.

定义 url route

使用route()装饰器

1
2
3
4
5
6
@app.route('/'):
def index():
return 'Index Page'
@app.route('/hello):
def hello():
return 'hello world'

可以在一个函数上附加多个URL route规则.

添加变量

使用<converter:variable_name>语法来为规则添加变量.

使用例子:

1
2
3
@app.route('/user/<int:userid>')
def show_user_profile(userid):
return f'User {userid}'

几种基本的转换器:
|转换器|含义|
|:—:|:—:|
| (不显使用转换器) |接收不含’/‘的字符串|
|int|接收整数|
|float|接收浮点数|
|path|和默认的类似, 但是接收’/‘|
但是有一个问题, 就是转换器接收的变量的两端似乎必须是/.

比如说/user/<int:userid>可以匹配/user/1, /user/2等URL,

但是/user?id=<int:userid>则只能匹配user?id=<int:userid>这个URL, 也就是变量的部分不起作用了.

这个坑是否可以避免我还找到办法, 只能说目前来看这个变量不适合处理GET传来的参数.

组织 request handler 函数

写一个最简单的request handler 函数

1
2
3
@app.route('/')
def index():
return 'hello world'

从get/post请求中取出参数

默认情况下, route装饰器只会响应GET请求(和HEAD请求). 如果要处理POST, 需要给route提供methods参数:

1
2
3
4
5
6
@app.route('/login', methods=['GET','POST'])
def login():
if request.method == 'POST':
do_the_login()
else:
show_the_login_form()

那么, 如果要接收数据, 需要使用请求对象 request

1
2
3
4
5
6
7
8
9
from flask import request
@app.route('/login', methods=['GET','POST'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'], request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'

当form中的键值不存在时, 程序会抛出KeyError异常, 用户会看到HTTP 400 Bad Request. 所以最好捕获此异常, 或者使用request.args.get('args_name', '') 方法.

获取/修改/存储cookie,session数据

可以通过cookies属性来访问cookies.
通过相应对象的set_cookie方法来设置cookies.
例如:

1
2
3
4
5
6
7
8
9
10
11
12
from flask import request

@app.route('/')
def index():
# 读取cookies
username = request.cookies.get('username') # 这种方法不会引起KeyError异常.
@app.route('/login')
def login():
resp = make_response(render_template())
# 设置cookies
resp.set_cookie('username', 'the username')
return resp

上面的例子中演示了如何设置和读取cookies, 但是如果想使用会话, 不能直接使用cookies.

会话: session对象允许在不同请求间存储特定用户的信息. 这是在cookies的基础上实现的.
首先需要设置一个密钥.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask, session, redirect, url_for, escape, request
import os

app = Flask(__name__)

@app.route('/'):
def index():
if 'username' in session:
return f'Logged in as {escape(session["username"])}'
return 'You are not logged in'

@app.route('/login', methods=['GET','POST'])
def login():
if request.method == 'POST':
session['username']=request.form['username']
return redirect(url_for('index'))
return '''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('index'))

app.secret_key = os.urandom(24)

修改/输出 http header 数据

在route函数中, 通常可以默认返回一个response对象. 例如 return make_response('index.html'). 这样client接收到的状态码和headers就是默认的.
可以通过return一个元组来设置. 例如

1
return make_response('style.css'), 200, {'Content-Type': 'text/css'}

这个三元组分别对应响应体, 状态码和响应头.
或者修改response对象的属性:

1
2
3
resp = make_response('style.css')
resp.headers['Content-Type'] = 'text/css'
return resp

使用模板系统

组织/访问 模板文件的目录结构

如果要使用模板, 首先要建立/templates目录, 然后向其中加入模板文件, 实例: layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css')}}">
<div class=page>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login')}}">log in</a>
{% else %}
<a href="{{ url_for('logout')}}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
</div>

有了这个
可以使用render_template方法来渲染模板, 只需要将模板名和作为关键字的参数传入模板的变量.

在模板中可以访问到 request, session, g 等对象, 以及get_flashed_messages()函数. 它可以实现消息的闪现, 在这里暂时不展开.

上述的实例模板中还有if-else-endif, for等控制语句, 都比较简单, 与 Python 的语法很接近.

配置静态文件访问

静态文件通常是css和JavaScript文件. 如果要通过Flask访问这种静态文件, 可以在模块旁边或者包中创建一个名为static的文件夹, url为/static即可访问.
如果要引用静态文件, 最好用url_for方法.
例如:

1
<script src="{{ url_for('static', filename='style.css') }}" />  # static/style.css

在模板中嵌入代码模板

有两种方式进行分别是包含include和继承block.

  • 包含

    include 比较简单, 看起来只是把一个文件的内容引用至此处. 但是把一个复杂的页面分成若干个不相干的部分, 分别写到其它文件中再一一包含至此, 可以提高代码的可读性、可维护性, 逻辑也会更清晰.

    • A.html

      1
      2
      3
      4
      <!doctype html>
      <title>Flaskr</title>
      <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css')}}">
      {% include "B.html" %}
+ B.html

    
1
2
3
4
5
6
7
8
9
10
11
12
13
<div class=page>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login')}}">log in</a>
{% else %}
<a href="{{ url_for('logout')}}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
</div>
此时如果通过`render_template`来访问 A.html , 就会发现 B.html 中的内容也被加载进来了.
  • 继承

    如果说 A 包含 B 是把 B 的代码引入A中,

    那么 A 继承 B 则更像是把 A 的代码覆盖到 B 中, 不过是根据 block 分别覆盖.

    使用的关键字是extends block

    • A.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <main id="main">
      <!-- 顶部标签栏 -->
      <header class="top-header" id="header">
      <div class="flex-row clearfix">
      <a href="javascript:;" class="header-icon pull-left waves-effect waves-circle waves-light on" id="menu-toggle">
      <i class="icon icon-lg icon-navicon"></i>
      </a>
      <div class="flex-col header-title ellipsis">
      <span>{{title}}</span>
      </div>
      <a href="javascript:;" id="site_search_btn" class="header-icon pull-right waves-effect waves-circle waves-light">
      <i class="icon icon-lg icon-search"></i>
      </a>
      </div>
      </header>
      {% block main_layout %}
      主要内容显示于此, 如果你看到了这句话, 请联系站长 qq: 1348651580
      {% endblock %}
      </main>
    • B.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {% extends "A.html" %}

      {% block main_layout %}
      <!-- index 页的主内容 -->
      <div class="container body-wrap">
      <!-- 文章列表 -->
      <ul class="post-list">
      <p>hello world</p>
      </ul>
      </div>
      {% endblock %}
那么此时再访问 B.html, 则会发现 B 中的 `main_layout`这个 block 替换掉了 A 中的 `main_layout`. 

虽然继承和包含使用起来很方便, 但是同时使用二者依然有的时候会有坑, 我遇到的一个就是在被 include 的文件中的 block 的可见性问题. 例如在包含的示例代码 B.html 中, 我希望将顶层的div标签的class值作为一个block, 使得后面继承的模板能让此class具有不同的值.

所以B的代码就成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class={% block class %}page{% endblock %}>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login')}}">log in</a>
{% else %}
<a href="{{ url_for('logout')}}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
</div>

同时有一个C.html

1
2
3
{% extends "A.html" %}

{% block class %}non-page{% endblock %}

那么再访问C.html, 会发现这里没有实现对block class的继承. 我推测原因是B中的block class对于C来说是不可见的.

目前我的解决方案是, 把 block 提到 A 中.

A.html

1
2
3
4
5
6
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css')}}">
<div class={% block class %}page{% endblock %}>
{% include "B.html" %}
</div>

或者设置局部变量应该也行吧, 还没尝试.

自定义模板函数

// todo

访问数据库

定义/组织/初始化 数据表

初始化数据库, 事先将建表的sql语句存储在文件schema.sql中, 然后设置函数进行初始化.

1
2
3
4
5
6
7
8
9
10
from contextlib import closing

def connect_db():
return sqlite3.connect(app.config['DATABASE'])

def init_db():
with closing(connect_db()) as db:
with app.open_resource('schema.sql') as f:
db.cursor().executescript(f.read())
db.commit()

这里的closing方法也比较陌生, 顺带提一下, with 语句打开和关闭某些资源的原理是自动调用对象的__enter____exit__方法, 但是有些类并没有这个方法, 所以这里的closing就是给这种类加上__enter____exit__, 让它们也能享受到with语句的便利.

连接数据库

如果要在每个请求前都连接数据库, 最好使用before_request, after_requestteardown_request三个修饰器. 它们的作用是:

  • before_request: 装饰的函数会在请求前调用, 它没有参数. 但当异常抛出时, 不一定被执行.
  • after_request: 装饰的函数会在请求结束后调用, 需要传入响应.
  • teardown_request: 装饰的函数将会在响应构造后执行(无论响应构造过程中是否会抛出异常), 不允许修改请求, 返回的值会被忽略. 如果有异常抛出, 则会被作为参数传入, 否则传入None

样例:

1
2
3
4
5
6
7
@app.before_request
def before_request():
g.db = connect_db()

@app.teardown_request
def teardown_request(exception):
g.db.close()

对接orm系统和现有的表结构

掌握最基本的add

掌握最基本的delete

按字段查询

count

slice

order by

直接使用sql访问数据库

部署app

配置文件

一个简洁的方案是创建独立的 .ini 或 .py 文件

1
2
3
4
5
6
7
8
9
10
11
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash

DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

app = Flask(__name__)
app.config.from_object(__name__)

from_object()会遍历给定的对象, 如果它是一个字符串, 则会导入它, 否则搜寻里面定义的全部大写的变量.

可选的学习项目

发送email

log

图片处理