标签归档:Python

从国内的 PyPI 镜像(源)安装 Python 包

不论是使用 pip,还是 Pipenv、Poetry等工具,安装 Python 包的时候会默认从官方的 PyPI 源下载文件,速度比较慢。国内的一些公司和机构提供了 PyPI 镜像源(mirror source),你可以设置让这些工具从国内的镜像源安装 Python 包,以便提高下载速度。

官方 PyPI 源的 URL 为 https://pypi.org/simple (旧的 URL 为 https://pypi.python.org/simple ),下面我们将以豆瓣提供的镜像源为例(URL 为 https://pypi.doubanio.com/simple/),介绍不同工具更改 PyPI 镜像源的方法:

pip

临时设置可以通过 -i 选项:

$ pip install -i https://pypi.doubanio.com/simple/ flask

全局设置有不同的层级和文件位置,以用户全局(per-user)为例,在 Linux & macOS 中,配置需要写到 ~/.pip/pip.conf 文件中;Windows 中,配置文件位置为 %HOMEPATH%\pip\pip.ini,%HOMEPATH% 即你的用户文件夹,一般为“\Users\<你的用户名>”,具体值可以使用 echo %HOMEPATH% 命令查看。

通常你需要手动创建对应的目录和文件,然后写入下面的内容:

[global]
index-url = https://pypi.doubanio.com/simple
[install]
trusted-host = pypi.doubanio.com

附注:按照 pip 文档,上面的配置文件位置是旧(legacy)的配置方式,但是因为比较方便设置,这里沿用了。新的建议是 Linux & macOS 放到 $HOME/.config/pip/pip.conf,Windows 则放到 %APPDATA%\pip\pip.ini。具体可以访问 pip 文档配置部分查看。

Pipenv

类似 pip 的 -i (–index-url)选项,你可以使用 –pypi-mirror 临时设置镜像源地址:

$ pipenv install --pypi-mirror https://pypi.doubanio.com/simple flask

如果想对项目全局(per-project)设置,可以修改 Pipfile 中 [[source]] 小节:

[[source]]

url = "https://pypi.doubanio.com/simple"
verify_ssl = true
name = "douban"

另外一种方式是使用环境变量 PIPENV_PYPI_MIRROR 设置(Windows 系统使用 set 命令):

$ export PIPENV_PYPI_MIRROR=https://pypi.doubanio.com/simple

你可以通过把这个环境变量的设置语句写入到终端的配置文件里实现“永久”设置,Linux & macOS 可参考这里,Windows 可参考这里

Poetry / Flit

因为 Poetry,Flit 这些工具遵循 PEP 518 创建了一个 pyproject.toml 文件来替代 setup.py、Pipfile 等配置文件,所以我们可以在这个文件里更改 PyPI 源。

使用 Poetry 时,在 pyproject.toml 末尾添加下面的内容来设置自定义镜像源:

[[tool.poetry.source]]
name = "douban"
url = "https://pypi.doubanio.com/simple/"

目前暂时没有其他方式,未来或许会为 poetry add 命令添加一个相关的设置选项。

同样的,Flit 大概要添加下面这些内容(未测试):

[[tool.flit.source]]
name = "douban"
url = "https://pypi.doubanio.com/simple/"

常用的国内 PyPI 镜像列表

  • 豆瓣 https://pypi.doubanio.com/simple/
  • 网易 https://mirrors.163.com/pypi/simple/
  • 阿里云 https://mirrors.aliyun.com/pypi/simple/
  • 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/

顺便提一下,使用镜像源需要注意一个问题:包的版本可能不会及时更新,遇到这种情况可以通过临时换回官方源解决。

新版本的Flask中如何启动开发服务器和开启调试模式

从Flask 0.11版本开始,官方就建议使用flask run命令来取代app.run()方法运行开发服务器。尽管如此,两年多过去了,仍然有大量新发布的文章和教程在示例中使用app.run()方法启动程序。类似的,虽然内置的命令行支持已经非常完善,但还有很多人在使用Flask-Script。

Added flask and the flask.cli module to start the local debug server through the click CLI system. This is recommended over the old flask.run() method as it works faster and more reliable due to a different design and also replaces Flask-Script.

Flask Changelog 0.11

不得不承认,在某些特殊场景下,app.run()更加方便,比如创建Flask命令在附加Werkzeug提供的性能分析中间件后启动程序,这时通过app.run()可以直接在脚本内启动程序。但是在大多数情况下,flask run更能胜任启动开发服务器的工作。而且,在大型项目中,使用app.run()需要你在项目根目录单独创建一个启动脚本,flask run则没有这个要求;在单脚本程序中,使用flask run也可以省掉脚本末尾的两行代码。

注意 这两种方法都只是用来启动内置(Werkzeug提供)的开发服务器,仅适用于开发用途。在生产环境下,应该使用性能更好,更加完善的开发服务器,比如Gunicorn、uWSGI等。

不同组织形式的程序的启动方式

下面我们来了解一下使用flask run启动开发服务器时在几种方式。

简单的单脚本程序

如果脚本命名为app.pywsgi.py,那么在包含程序脚本的目录下直接调用flask run即可:

$ flask run

Flask会自动探测找到脚本中的程序实例并启动。如果脚本命名为其他名称,比如hello.py,那么需要将脚本名写入环境变量FLASK_APP,然后再调用flask run命令:

$ export FLASK_APP=hello
$ flask run

提示 在Windows系统下,你需要使用set命令来设置环境变量,比如 > set FLASK_APP=hello,后面的命令亦同。

使用包组织的程序

这种情况下,可以将包含程序实例的对应模块的路径写入FLASK_APP

$ export FLASK_APP=my_pkg.app
$ flask run

通常情况下,我们会在包内的__init__.py文件中创建程序实例,所以这时可以直接将包名称写入FLASK_APP

$ export FLASK_APP=my_pkg
$ flask run

使用工厂函数创建程序实例的程序

因为Flask会自动探测程序实例,所以使用工厂函数创建程序实例时不需要进行额外设置。具体来说,Flask会在FLASK_APP变量存储的对应模块/包构造文件中寻找名为create_appmake_app的函数,并调用这个函数来创建一个程序实例。

为了让你的程序能够被探测到,工厂函数的名称需要命名为create_appmake_app,而且要确保工厂函数接受默认值参数。这时启动开发服务器的方式仍然不变:

$ export FLASK_APP=my_pkg
$ flask run

如果你的工厂函数接受的参数不是默认参数,或者你想详细定义调用工厂函数的方式,那么也可以通过FLASK_APP环境变量来定义:

$ export FLASK_APP="my_pkg:create_app('development')" 
$ flask run

提示 Flask的FLASK_APP还接受其他形式的输入值,你可以参考文末给出的文档相关部分链接了解完整内容。

如何避免重复设置FLASK_APP环境变量

在上面的几种方式中,除了包含程序实例的程序脚本命名为app.pywsgi.py的情况外,都需要设置FLASK_APP环境变量。有没有办法避免重启电脑或是新打开命令行会话时重复输入FLASK_APP呢?当然。Flask提供了对一个常用的Python虚拟环境管理工具python-dotenv的支持,我们需要先安装它:

$ pip install python-dotenv

当python-dotenv安装后,执行flask run命令会首先将项目根目录下的.env.flaskenv文件中的环境变量写入。所以,你可以将FLASK_APP写在这两个文件中。按照约定,.env存储包含敏感数据的环境变量,这个文件需要加入到.gitignore中以避免提交到Git仓库中;而.flaskenv时Flask特别支持的文件,这个文件则用来存储和Flask相关的环境变量,比如FLASK_ENVFLASK_DEBUG等,所以我们可以把FLASK_APP写到这个文件中:

FLASK_APP=my_pkg

现在,我们可以仅通过一个命令来启动开发服务器:

$ flask run

使用flask run时如何开启调试模式?

在使用app.run()方法时,我们会通过将debug参数设为True来开启调试模式。而当使用flask run时,则需要通过FLASK_ENV环境变量来设置调试模式。默认情况下,FLASK_ENV的值为production,在开发时我们可以将其设为development来开启调试模式。

同样的,为了避免重复写入这个环境变量,我们也将其写到.flaskenv中:

FLASK_ENV=development

提示 目前已不推荐使用FLASK_DEBUG来开启调试模式,当FLASK_ENV的值为development时调试模式会自动开启。

使用flask run时如何自定义主机和端口

在通过flask run启动开发服务器时,你可以通过命令行选项来自定义监听的主机和端口,示例如下:

$ flask run --port 5001

下面的示例同时指定了端口和主机:

$ flask run --host 0.0.0.0 --port 5001

另外,Flask还支持通过环境变量来定义命令选项,支持的环境变量名称模式为“FLASK_命令_选项”。比如,如果你想设置端口,那么可以定义FLASK_RUN_PORT环境变量,作用和传入--port选项相同。

启动包含程序上下文的Python Shell

你可以通过flask shell命令来启动一个激活了程序上下文的Python Shell,而不是使用python命令:

$ flask shell

app.run()的未来

从0.11版本到现在的1.0.2版本,app.run()始终处于不建议使用状态,而且Flask的命令行系统、flask run命令的程序探测都在逐渐完善,我觉得未来也许会正式”deprecate“这个app.run()方法。不过,因为某些特殊用途仍然需要使用app.run(),未来的变化还不好说。而且,Miguel Grinberg提交了1个PRapp.run()间接调用flask run,如果这个PR被合并,也许app.run()将会重回正轨。

就目前来说,flask run要远比app.run()更加方便、好用、简洁、直观,准备好了吗?穿上新衣服吧。

相关链接

Flask test_client()测试客户端为勾选框传递布尔值数据

今天写单元测试发现了一个常见的问题,即测试时发送POST请求时如何传入布尔值数据(勾选框字段值)?答案是:你没法直接传递布尔值。其实这个答案相当显而易见,客户端当然没法向服务器端发送Python类型的数据,数据的转换是在接受到请求数据后在服务器端进行的。之前在不借助Flask-WTF/WTForms,手动编写表单并处理时就已经注意到了这个问题,不过在测试中不太容易想到。

首先,我们需要了解一下勾选框(<input type="checkbox">)提交的行为:

  • 如果没有勾选,那么勾选框字段的值为空值,而且这个字段不会被序列化到请求中;在服务器端,WTForms会将其转换为False
  • 如果勾选框被勾选,那么传入服务器端的数据会是该字段value属性的值,如果value属性的值为空,那么则提交字符串"on";在服务器端,WTForms会将其转换为True

也就是说,勾选框的数据只要不为空,WTForms就会将其转换为True。所以,在测试中,如果你想让勾选框的值最终转换为True,那么就传入任意字符串;反之则传递空字符串或直接不加入该字段。下面是传入空字符串的示例:

def test_privacy_setting(self):
    self.login()
    response = self.client.post(url_for('user.privacy_setting'), data=dict(
        public_collections='',  # <--
    ), follow_redirects=True)

    user = User.query.get(1)
    self.assertEqual(user.public_collections, False)

顺便说一句,基于勾选框的提交行为,如果没有使用Flask-WTF/WTForms,那么在手动处理提交数据的时候也要进行相应的处理:没有在request.form中获取到勾选框字段(比如,request.form.get('remember')会是None),即表示没有勾选,那么就转换为False;勾选框字段一旦出现,那么就表示勾选,转换为True

在Python Selenium中为Chrome和Firefox浏览器开启headless模式

我们通常会使用Selenium编写UI测试,为浏览器开启Headless模式(执行操作时不显示GUI窗口)会很方便。最新版本的Chrome和Firefox中,均已支持headless模式。

在Selenium中,为这两个浏览器开启headless模式的方式基本相同:

Chrome:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('headless')
driver = webdriver.Chrome(options=options)

Firefox:

from selenium import webdriver

options = webdriver.FirefoxOptions()
options.add_argument('headless')
driver = webdriver.Firefox(options=options)

我提交的PR#5120添加了和Chrome相同的导入接口,如果你使用Selenium小于3.8.0版本,则需要将上面的webdriver.FirefoxOptions()替换为webdriver.firefox.options.Options()

另外,你也可以使用环境变量MOZ_HEADLESS 来为Firefox开启headless模式:

import os
from selenium import webdriver

os.environ['MOZ_HEADLESS'] = '1'  # <- this line
driver = webdriver.Firefox()

本文基于我在Stack Overflow的这篇回答:https://stackoverflow.com/a/47481793/5511849

使用Python创建随机文件名

当用户上传文件时,为了进行统一处理,我们可以为所有文件创建随机文件名。Python标准库中的uuid模块很适合用来生成文件名。我一般会使用uuid.uuid4().hex作为随机文件名,因为它会返回16进制字符串形式的uuid,并且不包含连字符,更易读:

import uuid
filename = uuid.uuid4().hex

和其他常见方式的输出对比:

>>> import uuid
>>> uuid.uuid()
UUID('20818854-3564-415c-9edc-9262fbb54c82')
>>> str(uuid.uuid4())
'f705a69a-8e98-442b-bd2e-9de010132dc4'
>>> uuid.uuid4().hex
'5ad02dfb08a04d889e3aa9545985e304'  # <-- this one

这篇文章原发于我在Stack Overflow上的这个回答:https://stackoverflow.com/a/44992275/5511849

WTForms自定义验证方法(行内验证器)是如何被调用的?

这篇文章基于我在知乎上的这个回答,进行了相应的简化处理,放到这里做个备份。

万能的回答

答案在源码里。

简单的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法,然后在对每个字段调用Field.validate()方法验证时将这些自定义行内验证方法一并和通过validators参数传入的验证器列表一起调用,进行验证。因为WTForms在调用时把对应的field作为参数传入了行内验证方法,所以你可以在自定义验证方法中通过field.data获取对应字段的数据。

深入的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法。在Form类中的validate()方法定义中,你可以看到WTForms是如何收集这些行内验证方法的:

class Form(with_metaclass(FormMeta, BaseForm)): 
   ....
   def validate(self):
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]

        return super(Form, self).validate(extra)

源码位置:github.com/wtforms/wtfo

这里迭代所有的字段属性,然后表单类中是否包含validate_字段名形式的方法。如果有,那么就添加到extra字段里,这个字段被传递到BaseForm类的validate()方法中。在BaseForm类的validate()方法中,WTForms迭代所有字段,并对每个字段调用Field.validate()方法验证字段,继续传入自定义的行内验证方法:

class BaseForm(object):
    ...
    def validate(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):  # 迭代字段名和字段对象
            if extra_validators is not None and name in extra_validators:  # 判断当前迭代字段是否存在自定义验证器
                extra = extra_validators[name]
            else:
                extra = tuple()
            if not field.validate(self, extra):  # 调用字段类的验证方法进行验证,传入自定义验证器
                success = False
        return success

源码位置:github.com/wtforms/wtfo

而在字段基类Field的validate()方法中,WTForms使用itertool模块提供的chain()函数把你实例化字段类时传入的验证器(self.validators)和自定义行内验证器(extra_validators)连接到一起:

class Field(object):    
    ...
    def validate(self, form, extra_validators=tuple()):
        ...
        # Run validators
        if not stop_validation:
            chain = itertools.chain(self.validators, extra_validators)  # 合并两类验证器
            stop_validation = self._run_validation_chain(form, chain)  # 运行验证器
        ...

源码位置:github.com/wtforms/wtfo

连接起来的所有验证器赋值为chain变量,并传入到self._run_validation_chain(form, chain)进行进一步调用:

class Field(object):    
    ...
    def _run_validation_chain(self, form, validators):
        for validator in validators:
            try:
                validator(form, self)  # 传入字段类本身作为第二个参数

源码位置:github.com/wtforms/wtfo

这个方法迭代所有的验证器对字段数据进行验证。关键在于validator(form, self),可以看到这里传入了第二个参数self,即Field类本身,这也是为什么你可以在自定义验证方法中通过field.data获取当前字段数据。

Python中的下划线有多少种用法和含义?

这篇文章来自我在知乎上的这个回答,做个备份。

大概有10种。

如下:

# No.1
# 在交互式解释器中获取上一个语句执行的结果
# 比如:
# >>> 1+1
# 2
# >>> _
# 2
# >>> _ * 5
# 10
_

# No.2
# 用来在函数、模块、包、变量名中分隔单词,增加可读性
var_foo_bar

# No.3
# 内部使用的变量、属性、方法、函数、类或模块(约定)
# from foo import * 不会导入以下划线开头的对象
_var

# No.4
# 避免和保留的关键字冲突(约定)
# 比如:class_、type_
var_

# No.5
# 在类内的私有变量(private)
# 类外部无法直接使用原名称访问
# 需要通过instance._ClassName__var的形式访问(name mangling)
__var

# No.6(这一条存疑)
# 在类内的保护变量
_var_

# No.7
# Python内置的“魔法”方法或属性
# 你也可以自己定义,但一般不推荐
# 比如:__init__, __file__, __main__
__var__

# No.8
# 作为内部使用的一次性变量
# 通常在循环里使用
# 比如:[_ for _ in range(10)]
# 或是用作占位,不实际使用的变量
# 比如:for _, a in [(1,2),(3,4)]: print a
_

# No.9
# i18n里作为gettext()的缩写
_()

# No.10
# 用来分隔数值以增加可读性(Python 3.6新增)
# 比如
# >>> num = 1_000_000 
# >>> num
# 1000000
1_000_000

参考链接:

《Flask Web开发实战》中的实战项目

很多朋友对《Flask Web开发实战》中的项目实例很感兴趣,这篇文章就来简单的对这些项目进行介绍,并给出一些截图。这几个项目的源码和在线Demo链接均可以在helloflask.com看到。

第1~6章及13章:helloflask

Hello, Flask!

第1~6章以及第13章的示例程序统一包含在helloflask仓库中的demos目录下。另外,这个仓库也作为《Flask Web开发实战》的仓库,书的勘误文件等内容也会一并在这里更新。

Flask Web开发实战第6章电子邮件示例程序

Flask Web开发实战第6章电子邮件示例程序

Flask Web开发实战第5章数据库示例程序

Flask Web开发实战第5章数据库示例程序


第7章:留言板 – SayHello

Say hello to the world.

这个项目比较简单,主要用来介绍项目组织和Web程序开发流程,没有复杂功能,介绍了虚拟数据的生成和时间日期的本地化。

SayHello绝对时间弹窗

SayHello绝对时间弹窗

SayHello主页

SayHello主页

第8章:个人博客 – Bluelog

A blue blog.

一个基础的博客程序,使用工厂函数和蓝本组织程序,主要包含下面这些功能点:

  • 使用工厂函数创建程序实例
  • 使用蓝本模块化程序
  • 使用富文本编辑器
  • 创建文章/分类/评论
  • 编辑文章/分类
  • 删除文章/分类/评论
  • 回复评论
  • 管理后台
  • 文章分类
  • 文章分页
  • 博客设置
  • 用户认证
  • 网站主题切换

博客主页

博客主页

更换了主题的博客主页

更换了主题的博客主页

博客后台的文章管理页面

博客后台的文章管理页面

第9章:图片社交网站 – Albumy

Capture and share every wonderful moment.

一个进阶的程序实例,主要包含下面的功能点:

  • 大型项目组织形式
  • 用户注册流程
  • 用户角色和权限管理
  • 图片上传
  • 图片处理
  • 删除确认模态框
  • 图片分类
  • 图片标签
  • 用户资料弹窗
  • 图片收藏
  • 用户关注
  • 在资料弹窗中执行关注操作
  • 消息提醒
  • 消息提醒的实时更新
  • 生成随机头像
  • 用户自定义头像
  • 更改密码
  • 提醒消息开关
  • 收藏可见开关
  • 注销账户
  • 全文搜索
用户个人主页

用户个人主页

网站动态页面

网站动态页面

评论中的用户资料弹窗

评论中的用户资料弹窗

头像裁剪

头像裁剪


第10章:待办事项程序 – Todoism

We are todoist, we use todoism.

一个简单的待办事项程序,使用jQuery实现简单的单页效果,主要包含下面的功能点:

  • 单页程序
  • 国际化和本地化支持
  • 实现Web API
程序主页

程序主页

切换语言后的程序主页

切换语言后的程序主页


第11章:在线聊天室 – CatChat

Chatroom for coders, not cats.

一个使用Flask-SocketIO实现的聊天室,主要包含下面这些功能点:

  • Gravatar头像
  • 实时双向通讯
  • 第三方登录
  • 无限滚动加载历史消息
  • Markdown支持
  • 代码语法高亮
  • 标签页消息提醒
  • 浏览器桌面通知
聊天页面

聊天页面

登录页面

登录页面

代码语法高亮

代码语法高亮


第15章:Flask扩展 – Flask-Share

Create social share component in Jinja2 template based on share.js.

Flask-Share是一个基于share.js实现,可以在模板中方便的创建社交分享组件的扩展。

创建社交分享组件

创建社交分享组件

Pipenv:新一代Python项目依赖管理工具

UPDATE(2019/8/31):不要用 Pipenv

什么是Pipenv

Pipenv是Kenneth Reitz在2017年1月发布的Python依赖管理工具,现在由PyPA维护。你可以把它看做是pip和virtualenv的组合体,而它基于的Pipfile则用来替代旧的依赖记录方式(requirements.txt)。
 
在这篇文章里,我将会以旧的依赖管理工作流程作为对比来介绍Pipenv的基本用法,更详细的用法可以参考Pipenv文档,或是Kenneth Reitz在PyCon 2018的演讲《Pipenv: The Future of Python Dependency Management》
 
顺便说一句,我的还没想好名字的Flask书中所有示例程序都使用了Pipenv进行依赖管理。
提示 如果你对virtualenv的用法以及虚拟环境的概念不熟悉的话,可以通过专栏的旧文《Flask出发之旅》进行简单的认识。

为什么使用Pipenv

Pipenv会自动帮你管理虚拟环境和依赖文件,并且提供了一系列命令和选项来帮助你实现各种依赖和环境管理相关的操作。简而言之,它更方便、完善和安全。你可以通过Pipenv文档开头的介绍来了解它的详细特性。Pipenv的slogan是“Python Dev Workflow for Humans”,作为人类,当然应该尝试一下……

如何使用Pipenv

假设我们要编写一个博客程序,项目的依赖是Flask和Flask-WTF。顺便说一句,可以使用下面的命令安装Pipenv:
$ pip install pipenv
下面我会通过不同操作来给出所需命令的对比,OLD(旧)表示使用pip和virtualenv,NEW(新)表示使用Pipenv。
 

创建虚拟环境

  • OLD
$ virtualenv venv 
提示 这里的venv是虚拟环境目录的名称,你可以自由更改,这会在你的项目根目录创建一个venv文件夹,其中包含独立的Python解释器环境。
  • NEW
$ pipenv install
Pipenv会自动为你创建虚拟环境,自动生成一个随机的虚拟环境目录名。
 

激活虚拟环境

  • OLD
在Linux或macOS系统中:
$ . venv/bin/activate
Windows:
> venv\Scripts\activate
  • NEW
$ pipenv shell
此外,Pipenv还提供了一个pipenv run命令,在该命令后附加的参数会直接作为命令在虚拟环境中执行,这允许你不必显式的激活虚拟环境即可在虚拟环境中执行命令。比如,pipenv run python会启动虚拟环境中的Python解释器。
 

安装依赖到虚拟环境

  • OLD
$ . venv/bin/activate # 需要先激活虚拟环境
(venv)$ pip install flask flask-wtf 
  • NEW
使用Pipenv,不管你是否激活了虚拟环境,都可以通过pipenv install命令安装:
$ pipenv install flask flask-wtf 
事实上,对一个新项目来说,你不必手动使用pipenv install来创建虚拟环境。当使用pipenv install xxx直接安装依赖包时,如果当前目录不包含虚拟环境,Pipenv会自动创建一个。
 

记录依赖

  • OLD
(venv)$ pip freeze > requirements.txt
这个命令会把依赖列表写入requirements.txt文件。每当你安装或卸载了依赖包时,都需要手动更新这个文件。你必须保持谨慎,否则非常容易把依赖列表弄乱。
 
  • NEW
使用Pipenv时,什么都不必做,Pipenv会自动帮你管理依赖。Pipenv会在你创建虚拟环境时自动创建Pipfile和Pipfile.lock文件(如果不存在),并且会在你使用pipenv install和pipenv uninstall命令安装和卸载包时自动更新Pipfile和Pipfile.lock。
附注 Pipfile用来记录项目依赖包列表,而Pipfile.lock记录了固定版本的详细依赖包列表。

在部署环境安装依赖

  • OLD
当我们需要在一个新的环境,比如部署上线环境安装所有依赖时,我们需要重复上面的多条命令:
$ virtualenv venv # 创建虚拟环境
$ . venv/bin/activate # 激活虚拟环境
(venv)$ pip install -r requirements.txt # 安装requirement.txt中记录的依赖
  • NEW
使用Pipenv则只需要执行pipenv install,它会自动安装Pipfile中记录的依赖:
$ pipenv install

区分开发依赖

  • OLD
使用requirements.txt时,我们通过会单独创建一个requirements-dev.txt文件来手动加入开发依赖。比如项目开发时才会用到pytest,那么你需要手动创建这个文件,然后写入:
-r requirements.txt
pytest==1.2.3
在新的开发环境安装依赖时,你需要安装这个文件中的依赖:
(venv)$ pip install -r requirements-dev.txt
  • NEW
使用Pipenv时,你只需要在安装pytest时添加一个–dev选项,它会自动被分类为开发依赖(写入Pipfile的dev-packages一节中):
$ pipenv install pytest --dev
在新的开发环境安装依赖时,也只需要在pipenv install命令后添加–dev选项即可一并安装开发依赖:
$ pipenv install --dev

总结

为了让你更轻松的过渡,Pipenv甚至提供了读取和生成requirements.txt文件的功能(在使用pipenv install命令时它会自动读取requirements.txt文件安装依赖并生成Pipfile文件)。希望这篇文章可以让你更快的上手Pipenv。
 
– – – – –
 
 
 

编程名词辨析(5×10’)

在和很多同学的交流中,发现有些编程词语被误用了。今天我们来辨析一下。

我简化了大部分解释,如果需要了解更多,请点链接。如果发现有错,请指正。

 

包(package)和模块(module)

:一个包含__init__.py文件的文件夹就是一个包,这个文件夹里可以放多个Python模块。比如:

my_package
    - __init__.py
    - hello.py

模块:一个Python文件(.py)就是一个模块。

What’s the difference between a Python module and a Python package?

 

插件(plugin)和扩展(extension)

在Flask里,插件和扩展没有具体的区别,都指代增强Flask功能的Python包,但通常称作扩展。

 

参数(parameter)和参数(argument)

参数(形参):函数所期望你传入的参数(即name

def say_hello(name):
    print 'hello,' + name

参数(实参):传给函数的实际的参数(即‘kitty’

say_hello('kitty')

What’s the difference between an argument and a parameter?

函数(function)和方法(method)

函数:为了便于进行管理和重用而包装起来的代码块。

def meow():
    print 'meow meow meow'

方法:在类中定义的函数是方法。

class Cat(object):
    
    def meow(self):
        print 'meow meow meow'

Difference between functions and methods in Python

 

表达式(expression)和语句(statement)

表达式:能被打印和赋值的是表达式。比如:

1 + 1
[x for x in y]

语句:任何一行(或多行)Python代码就是语句(包括表达式),比如:

print 42
name = 'john'

What is the difference between an expression and a statement in Python?

 

Flask模板引擎:Jinja2语法介绍

Jinja是组成Flask的模板引擎。可能你还不太了解它是干嘛的,但你对下面这些百分号和大括号肯定不陌生:

{% block body %}
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
{% endblock %}

看过《Flask Web开发》,很多人都能写出来这些,但除了书里讲的,你还应该了解一些其他的语法细节。这篇文章就来介绍一些常用的语法和函数,如果想要系统完整的了解Jinja,可以去读它的文档:http://jinja.pocoo.org/docs/dev/

FAQ

在Jinja网站上的FAQ里,我挑了三个大家可能会比较感兴趣的问题(简单翻译了一下)。

1、为什么要叫Jinja?

之所以叫Jinja,是因为日本的神社(Jinja)英文单词是temple,而模板的英文是template,两者发音很相似(这么说来,它本来也有可能叫Miao的……)。

2、Jinja的速度怎么样?

和Mako差不多,但比Genshi以及Django的模板引擎快10~20倍。

3、把逻辑判断(Logic)放到模板里是个好主意吗?

毫无疑问,你放到模板里逻辑判断(Logic)应该越少越好。但为了让大家都开心,适当的逻辑判断是需要的。尽管如此,它有很多对于你能做什么,不能做什么的限制。

出于诸多考虑(速度,易读性等等),Jinja既不允许你放置任意的Python代码,也不允许所有的Python表达式。这也是为什么我们要了解Jinja2的语法。

 

Delimiters(分隔符)

多说一下注释,这是单行注释:

{#% for user in users %#}

下面是多行注释:

{# note: commented-out template because we no longer use this
    {% for user in users %}
        ...
    {% endfor %}
#}

 

Variables(变量)

除了普通的字符串变量,Jinja2还支持列表、字典和对象,你可以这样获取变量值:

{{ mydict['key'] }}
{{ mylist[3] }}
{{ mylist[myintvar] }}
{{ myobj.somemethod() }}

获取一个变量的属性有两种方式:

{{ foo.bar }}
{{ foo['bar'] }}

这两种方法基本相同(深层次的区别可以暂不考虑)

Filter(过滤器)

过滤器用来修改变量,使用一个竖线和变量相隔。

{{ items|join(', ') }}

常用的内置过滤器:

  • safe 渲染时不转义
  • capitalize 首字母大写
  • lower 小写
  • upper 大写
  • title 每个单词的首字母都转换成大写
  • trim 去掉首尾空格
  • striptags 去掉值里的HTML标签
  • default 设置一个默认值,如果变量未定义,就用这个默认值替换。类似这样:
{{ my_variable|default('my_variable is not defined') }}
  • random(seq) 返回一个序列里的随机元素
  • truncate(s, length=255, killwords=False, end=’…’) 截取出指定长度的文章(文章摘要)
  • format(value, *args, **kwargs) 参考Python的字符串格式化函数
  • ……

完整的fliter列表:http://jinja.pocoo.org/docs/dev/templates/#builtin-filters

Tests(测试,判断)

Jinja2提供的tests可以用来在语句里对变量或表达式进行测试,如果要测试一个变量,可以在变量后加上“is”和test名,比如:

{% if user.age is equalto 42 %} {# 这里也可以写成... is equalto(42) #}
    Ha, you are 42!
{% endif %}

如果要传入参数,可以在test后增加括号,也可以直接写在后面

常用的test(未说明的均返回True或False):

  • defined
  • equalto
  • escaped
  • none
  • sequence
  • string
  • number
  • reverse
  • replace
  • ……

完整的test列表及用法见:http://jinja.pocoo.org/docs/dev/templates/#list-of-builtin-tests

Loop(循环)

在一个for循环内,有一些特殊的变量可以使用,这是几个常用的:

  • loop.index 当前迭代数,可以用来写评论的楼层数(从1开始)
  • loop.index0 同上,不过从0开始迭代
  • loop.revindex 反向的迭代数(基数为1)
  • loop.revindex0 反向的迭代数(基数为0)
  • loop.length 序列的数量
  • loop.first 是否是第一个元素
  • loop.last 是否是最后一个元素
  • ……

完整的列表见:http://jinja.pocoo.org/docs/dev/templates/#for

 

Whitespace Control(空格控制)

默认的设置:

  1. 如果末尾有换行符,则去除;
  2. 其他空格原样保留。

也就是说,下面这几行:

<div>
    {% if True %}
        yay
    {% endif %}
</div>

渲染后的结果是这样:

<div>

        yay

</div>

Jinja2语句占据的空行,你自己输出的空格,Tab都将保留。

如果要去掉Jinja2语句占据的空行,可以通过设置Jinja2的环境变量实现:

app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True

或者像这样手动添加一个减号(注意和%之间没有空格):

<div>
    {% if True -%}
        yay
    {%- endif %}
</div>

两者实现的效果相同,如下:

<div>

        yay

</div>

如果语句块的前后都加上减号:

<div>
    {%- if True -%}
        yay
    {%- endif -%}
</div>

渲染后会是这样:

<div>yay</div>

通过Jinja2提供的环境变量,你可以设置很多东西,比如分隔符(在和其他的语言产生冲突时,可以通过修改分隔符来解决)。具体见:http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment

 

Escaping(转义)

有时你会想原样输出一些Jinja2语句和分隔符,对于小的内容,可以使用变量表达式来输出,比如输出一个分隔符:

{{ '{{' }}

大的内容块可以使用一个raw块包裹:

{% raw %}
    <ul>
    {% for item in seq %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
{% endraw %}

 

模板继承

你可以创建一个base.html作为基模板,把导航栏、页脚、flash消息、js或css文件等等需要在每一个页面中显示的内容放在基模板里,然后在其他的模板(子模板)里使用这个语句继承它:

{% extends "base.html" %}

如果想添加内容到在父模板内已经定义的块,可以使用super函数:

{% block sidebar %}
    <h3>Table Of Contents</h3>
    ...
    {{ super() }}
{% endblock %}

这样可以避免覆盖父块的内容。

全局函数

常用的全局函数有:

  • range([start, ]stop[, step])
  • lipsum(n=5, html=True, min=20, max=100) 为模板生成一些 lorem ipsum。

详细列表见:http://jinja.pocoo.org/docs/dev/templates/#list-of-global-functions

 

其他内容

内容还有很多,比如行语句、控制流、表达式、宏等。不再一一介绍了(写这种介绍文章太累了……)。

具体见文档的模板部分:

Template Designer Documentation

相关链接

Jinja主页:http://jinja.pocoo.org

Jinja2文档:http://jinja.pocoo.org/docs/dev/

Jinja2文档模板部分:http://jinja.pocoo.org/docs/dev/templates/

Github项目页:https://github.com/pallets/jinja

– – – – –

更多关于Flask和Web开发的优质原创内容,欢迎关注Hello, Flask! – 知乎专栏

Flask实践:猜数字游戏

Demo体验:猜数字 – Flask
难度:1
使用扩展:Flask-Bootstrap、Flask-WTF

guess1

 

本文首发于知乎专栏:Hello, Flask!

– – – – –

每个学编程的人大概都写过猜数字游戏,今天我们用Flask来做一个Web版本的猜数字。功能很简单,只有两个路由,三个模板和一个表单组成。扩展的版本见项目的Github页面(页尾)。

项目结构

|-GuesstheNumber 项目名称
    |-guess.py
    |-templates/  模板文件夹
        |-index.html  
        |-guess.html
        |-base.html  基模板
    |-venv/  虚拟环境

实现代码

guess.py

# -*- coding: utf-8 -*-
import random

from flask import Flask, render_template, flash, redirect, url_for, session
from flask_wtf import Form
from wtforms import IntegerField, SubmitField
from wtforms.validators import Required, NumberRange
from flask_bootstrap import Bootstrap

app = Flask(__name__)
app.config['SECRET_KEY'] = 'very hard to guess string'  #设置secret key
bootstrap = Bootstrap(app)  # 初始化Flask-Bootstap扩展


@app.route('/')
def index():
    # 生成一个0~1000的随机数,存储到session变量里。
    session['number'] = random.randint(0, 1000)  
    session['times'] = 10
    return render_template('index.html')


@app.route('/guess', methods=['GET', 'POST'])
def guess():
    times = session['times'] # 从session变量里获取次数
    # 从session变量里获取在index函数里生成的随机数字
    result = session.get('number')  
    form = GuessNumberForm()
    if form.validate_on_submit():
        session['times'] = times  # 更新次数值
        if times == 0:
            flash(u'你输啦……o(>﹏<)o')
            return redirect(url_for('.index'))
        answer = form.number.data
        if answer > result:
            flash(u'太大了!你还剩下%s次机会' % times)
        elif answer < result:
            flash(u'太小了!你还剩下%s次机会' % times)
        else:
            flash(u'啊哈,你赢了!V(^-^)V')
            return redirect(url_for('.index'))
    return render_template('guess.html', form=form)


class GuessNumberForm(Form):
    number = IntegerField(u'输入数字(0~1000):', validators=[
        # 传入验证函数和相应的错误提示信息。
        Required(u'输入一个有效的数字!'),
        NumberRange(0, 1000, u'请输入0~1000以内的数字!')])
    submit = SubmitField(u'提交')


if __name__ == '__main__':  # 用于heroku部署,本地可省略
    app.run()

index.html

{% extends "base.html" %}

{% block page_content %}
<!-- 传入url_for的参数是视图函数的名称 -->
<a class="btn btn-success btn-lg" href="{{ url_for('guess') }}">开始游戏</a>
{% endblock %}

guess.html

{% extends "base.html" %} <!-- 引入基模板 -->
{% import "bootstrap/wtf.html" as wtf %}

{% block page_content %}
    <!-- 使用Flask-Bootstrap提供的函数来生成默认样式的表单 -->
    {{ wtf.quick_form(form) }}
{% endblock %}

完整的项目见源码(底部)。

相关知识

  • session(会话)

session是Flask的上下文(context)全局变量,可以用来存储(用字典的形式)请求之间需要“记住”的值。在这个猜数字游戏里,我使用它来存储生成的随机数和剩余的机会次数。

要使用session,得先设置一个secret key,这用来给Cookie签名以加密session,这样做的效果是用户可以看到cookie但不能篡改它。尽管如此,session并不是安全的,不能用来存储密码,这个视频演示了一个破解session的过程:https://youtu.be/mhcnBTDLxCI

更多细节见:http://flask.pocoo.org/docs/0.11/quickstart/#sessions

安装和运行

源码地址:https://github.com/helloflask/guess-flask

下载或使用git命令克隆项目后,切换到程序根目录。使用virtualenv创建一个虚拟环境,激活后使用pip安装所需依赖:

pip install -r requirements.txt

然后运行:

set FLASK_APP=guess.py    
flask run

访问: http://127.0.0.1:5000/

上面的代码稍微美化一下,变成了下面这样:
guess2

源码:https://github.com/helloflask/guess

Demo:http://guessguess.herokuapp.com/