在 Flask 项目中使用 Celery(with 工厂模式 or not)

本文隶属于《Flask Web 开发实战》番外系列。这篇文章会介绍如何在 Flask 项目中集成 Celery。

创建 Celery 程序

第一步是创建一个 Celery 程序实例。因为 Flask 程序实例通常会命名为 app,为了避免冲突,我们一般会把 Celery 实例命名为 celery 或 celery_app:

from celery import Celery

celery = Celery(__name__, broker='pyamqp://guest@localhost//')

@celery.task
def add(x, y):
    return x + y

组织和加载配置

大多数教程,包括目前的 Flask 文档里都会介绍用下面的方式加载配置:

celery.conf.update(app.config)  # 这里的 app 是 Flask 程序实例

也就是把 Celery 配置和 Flask 配置写在一起,然后从 Flask 程序实例的配置字典里更新配置。

但问题是,Celery 从 4.0 开始启用新的小写配置名,某些配置被新的名称替换。虽然旧的大写配置仍然支持,但如果你打算使用小写配置名,或是打算在未来进行迁移,这里的配置加载方式就会失效,因为 Flask 在从文件或对象导入配置时只会导入大写形式的配置变量。

因此,我建议将 Celery 配置写在单独的文件里,不要和 Flask 配置混在一起。按照 Celery 文档的示例,你可以在当前目录创建一个 celeryconfig.py 文件(或是其他名字)保存配置:

broker_url = 'pyamqp://'
result_backend = 'rpc://'

task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'Europe/Oslo'
enable_utc = True

然后使用下面的方法加载配置(使用其他模块名,或是在其他路径时,记得修改传入的字符串):

celery.config_from_object('celeryconfig')

如果需要在创建 Celery 实例时传入 broker 和 backend 配置,可以直接写出或是从配置模块中导入:

from celeryconfig import broker_url

celery = Celery(__name__, broker=broker_url)

在 Flask 程序中初始化 Celery

你可以单独创建 Celery 程序,但我们通常会需要为它添加 Flask 程序上下文支持,因为有时候你的 Celery 任务函数会用到依赖于 Flask 程序上下文的某些变量。

下面我们为 Celery 创建了一个工厂函数,在工厂函数中创建 Celery 实例,加载配置,并实现 Flask 程序上下文支持:

from flask import Flask
from celery import Celery

from celeryconfig import broker_url


def make_celery(app):
    celery = Celery(__name__, broker=broker_url)
    celery.config_from_object('celeryconfig')

    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)

    celery.Task = ContextTask
    return celery

app = Flask(__name__)

celery = make_celery(app)

在定义 Celery 任务的模块里,比如 tasks.py,你可以导入这个 Celery 程序实例:

from app import celery

@celery.task
def add(x, y):
    return x + y

在使用工厂函数的 Flask 程序中初始化 Celery

当 Flask 程序也使用工厂函数创建时,我们可以全局创建 Celery 程序实例,然后在创建 Flask 程序实例的工厂函数里更新 Celery 程序配置并进行上下文设置:

from flask import Flask
from celery import Celery

from celeryconfig import broker_url
 
celery = Celery(__name__, broker=broker_url)
 

def create_app():
    app = Flask(__name__)
 
    register_celery(app)
    return app
 

def register_celery(app):
    celery.config_from_object('celeryconfig')
 
    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)
 
    celery.Task = ContextTask

同样直接导入 Celery 实例并创建任务:

from app import celery

@celery.task
def add(x, y):
    return x + y

这本来是一个完整的 Celery 入门教程,但因为去年的一次硬盘损坏,对应的示例程序弄丢了,暂时没有毅力重写一遍,所以这篇文章只抽取了其中和 Flask 相关的内容。

因为距离初稿写作的时间已经过去半年多,Celery 的最新版本也已经是 4.3.0,如果文中有什么疏漏,或是有更好的实现方式,欢迎评论指出。

为已存在的数据库生成 SQLAlchemy / Flask-SQLAlchemy 模型类

SQLAlchemy 基于模型类对数据库表进行操作,所以,如果你想对已存在的数据库表进行操作,就要先为它编写对应的模型类。

对于简单的数据库,比如只有几张表,没有复杂的关系,表字段也很少,你可以直接对照表模式手写模型类。

其他情况下,使用自动化工具 SQLAcodegen / Flask-SQLAcodegen 自动生成模型类定义会更加方便,根据单独使用 SQALchemy 还是使用扩展 Flask-SQLAlchemy,你可以选择阅读对应的章节。

单独使用 SQLAlchemy

首先使用 pip 安装:

$ pip install sqlacodegen

执行下面的命令将模型类输出到 models.py 文件里(将覆盖目标文件原内容):

$ sqlacodegen --outfile models.py sqlite:///database.db

这个命令的格式如下:

$ sqlacodegen --outfile <输出的文件名> <数据库连接 URI>

–outfile / -o 选项设置输出的目标文件,不给出这个选项将直接在命令行输出生成的模型类定义,比如:

$ sqlacodegen sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()
metadata = Base.metadata


class Message(Base):
    __tablename__ = 'message'

    id = Column(Integer, primary_key=True)
    name = Column(String(20))
    body = Column(String(200))
    timestamp = Column(DateTime, index=True)

提示 如上所示,生成的模型类定义会包含 Base 和 metadata 对象定义和相关导入语句,你或许需要进行细微的调整。

你可以使用下面的命令查看更多可用的设置选项:

$ sqlacodegen --help

提示 除了使用 SQLAcodegen,你也可以直接使用内置的 Automap 扩展生成模型类。

使用 Flask-SQLAlchemy

首先使用 pip 安装 Flask-SQLAcodegen:

$ pip install flask-sqlacodegen

执行下面的命令将模型类输出到 models.py 文件里(将覆盖目标文件原内容):

$ flask-sqlacodegen --flask --outfile models.py sqlite:///database.db

这个命令的格式如下:

$ flask-sqlacodegen --flask --outfile <输出的文件名> <数据库连接 URI>

–flask 选项设置输出 Flask-SQLAlchemy 模型类,不给出这个选项将直接输出 SQLAlchemy 原生模型类定义;–outfile 选项设置输出的目标文件,不给出这个选项将直接在命令行输出生成的模型类定义,比如:

$ flask-sqlacodegen --flask sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Message(db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20))
    body = db.Column(db.String(200))
    timestamp = db.Column(db.DateTime, index=True)

提示 如上所示,生成的模型类定义会包含一个 db 对象定义和相关导入语句,你或许需要进行细微的调整。

你可以使用下面的命令查看更多可用的设置选项:

$ flask-sqlacodegen --help

正常上网

这几年一直在用 Bandwagon(「搬瓦工」)的廉价 VPS 做 VPN 代理服务器,前两天突然发现没法正常上网*了。

一开始以为是 Shadowsocks 客户端的问题,但重新启动或更换版本依然不行,在手机上测试也没法访问。接着猜测是不是主机到期,不过翻了邮箱并没有找到续费通知,只有几封服务推广邮件。想登陆主机网站,却发现主机网站也被封锁了,只好找了临时的 VPN 工具访问。登录后看到套餐被取消了,而且无法续订。仔细读了前段时间接连收到的那几封「服务推广邮件」,才发现原来它们是产品下线通知**。

花了半天时间,尝试寻找替代品,发现没有这么便宜的主机了,临时在 virmach 开了 2 美元月付的主机,网速不是很好。

忙完这一通,我不禁在想:为什么我要每年额外支出一两百块,依靠某种工具才能享有正常上网这种基本权利?真是荒诞!

总有一天,这些反文明的限制将会消失。唯一遗憾的是,在这种丧心病狂的审查和封锁下,所有的书籍、音乐和电影,网络上的文章、感想和只言片语,甚至于大多数人的日常生活都丧失了本该有的完整、自由和可能性。


*我认为应该尽可能的使用一般词汇。比如,使用「正常上网」、「自由上网」或「绕过网络封锁」,而不是「科学上网」或「翻墙」,这样可以尽可能的让看到的人了解到事情的本质。

**Bandwagon 重复发送的几封提醒邮件的标题为「BandwagonHost: OpenVZ VPS Phase Out」,因为误认为 Phase Out 是「推出」的意思,所以没有在意,这件事也算是英语词汇量低导致的乌龙事件。

程序员的基本常识——学习篇

这个系列是《软技能》一书的摘抄整理,详细介绍见该系列首篇文章《程序员的基本常识——职业篇》。我会提取书中的有用信息,重新组织,并补充一些想法。欢迎评论补充相关信息,或是纠正错误观点。

对于心智成长,大部分情况下学校和家庭都在帮倒忙,你通常只能靠阅读来完成自我教育。在编程世界里情况类似。你也许会在学校里学习计算机基础知识,但对于大量的编程语言、框架和工具,你通常都需要自学。自学、解决问题和正确提问是程序员必须掌握的几个「元能力」。

1. 学习怎样学习

  • 我们几乎都是下意识地倾向于学习自己感兴趣的东西。主动学习是效率更高的方式。
  • 学习知识最好的方式就是立即将其用于实践。如果关于某个主题你能获得足够的知识,你就可以发挥自己心灵深处强大的创造力和好奇心。当我们能够在一件事情上尽情发挥的时候,我们的内心就倾向于吸收更多的信息,思考更有意义的问题。这种自然的探索和好奇心能够帮助我们轻而易举的记住更多的信息。
  • 一旦你已经实践过,并积累了各式各样的问题,立刻回到书本当中,你会有强烈的冲动去消化吸收其中的内容。
  • 然后,你可以把自己学到的新知识重新应用于实践。已解决实践过程中发现的问题为目标,在向着只是前进的道路上重复这个循环。
  • 最后,你可以通过写作文章或是教给其他人来加深理解。
  • 这个过程可以归纳为「学习——实践——掌握——教授」。

以学习某个 Web 框架来说,从头到尾读完一本相关的技术书,并不会让你掌握这个框架。只有在了解基础知识后,自己动手借助书中的知识编写一个 Web 程序,并尝试尽可能多的解决实际问题,才会让你学会这个框架。

2. 十步学习法

十步学习法的基本思想是先对自己要学的东西有个基本的了解。至少了解以下三个要点:

  • 如何开始:明确你要掌握哪些前导知识,从哪些资料开始最为合适。
  • 学科范围:确定你的学习方向和大致的学习范围。在开始阶段,你不需要了解每一个细节,只需大致了解学科的轮廓。
  • 基础知识:了解最基本的知识点是哪些,知道自己学的哪 20% 就能满足 80% 的日常应用。

1~6 步:前期调研,收集资料,制定学习计划(这些步骤只做一次)

  • 第 1 步:了解全局

了解自己将要学习的主题的全局,建立一个基本的概念。

  • 第 2 步:确定范围

明确自己到底要学什么,有一个可控的学习范围,而不是试图解决太大的问题。让自己的关注点落脚到更小也更可控的范围,根据你的时间预算,尝试把一个大的主题分解为多个可控的子主题。比如,「学习 HTML」可以进一步细化为「学习 HTML 的基本语法,了解页面基本结构和常用的元素」。

  • 第 3 步:定义目标

明确对你来说,学到何种地步,达成何种目标后算是完成学习。好的成功标准应该是具体的、无二义性的。以学习 HTML 为例,「学会用 HTML 编写网页」就是坏的成功标准,而「可以使用 HTML 创建一个内容丰富、语法标准、样式美观并且支持响应式的个人主页」就是一个好的成功标准。

  • 第 4 步:寻找资源

对于学习编程来说,你可以在网上找到大量的资源(这里的资源包括书、教程、视频、代码等)。试着在 Google、YouTube、亚马逊、豆瓣读书或其他网站搜索相关关键字,然后根据描述或评论选择资源。尽量选择系统性的教程或书。对于编程语言、框架和工具来说,官方入门教程一般都不会太差。

  • 第 5 步:创建学习计划

学习计划就是你的学习路径,通常你可以根据相关主题的技术书的目录来制定学习路径。好的技术书都遵循这样的规律:打好基础,做好铺垫,然后逐个展开每一章的论述。通过对比几本同类型的书籍目录,制定专属于你的学习路径。

  • 第 6 步:筛选资源

根据你的学习计划,去掉重复、无用、过时、难度不适宜的资源。只留下 1~2 个品质最高的资源即可。

7~10 步:学习——实践——掌握——教授(循环往复)

  • 第 7 步:开始学习,浅尝辄止

获取刚好足够开始动手实践的基础知识。大多数人在学习过程中通常会犯两类错误:第一类错误是在知之不多的情况下就盲目开始,即行动太早;第二类错误是在行动之前准备过多,即行动太晚。

  • 第 8 步:动手操作,边玩边学

通过动手实践验证你在前一步骤学习的知识,并记录下新的疑问。比如,做一个简单的小项目。

  • 第 9 步:全面掌握,学以致用

根据你掌握的资源,解决你在实践中记录下的大量问题。同时深入了解各个主题。比如,阅读你要学习的语言、框架的源码。

  • 第 10 步:乐为人师,融会贯通

要想确定你确实掌握了某些知识,最好的方式就是把自己学到的知识教给别人。你可以在 Stack Overflow、论坛、IM 群组里回答问题,也可以写文章或录视频。把自己所学教给别人是查漏补缺的好办法,这同时也会增强你的表达能力和理解能力。

3. 寻找导师

  • 在你的软件开发生涯中,拥有一位导师可以说是一笔巨大的财富,因为一位优秀的导师能够让你无需亲身经历现实的重重考验就拥有丰富的经验,从而更高效的掌握某种技术。
  • 我们通常会犯这样的错误——根据他人的生活来判读其是否具有帮助我们的能力。然而,最好的老师往往深藏不露。你不应该因为某人在自己的生活中成就平平,或者看起来不过如此就对他的印象大打折扣。
  • 你可以找一位已经成功实现你想要做的事情的人,也可以找一位曾经帮别人实现了你现在想要做到的事情的人。
  • 导师未必一定得是非常厉害的专家,只比你领先一步的人也值得学习。
  • 你可以在本地的开发小组或是网上的论坛等地方寻找你的导师,也可以在你的公司或交际圈里寻找,或者通过付费向行家请教。
  • 优秀的书籍也可以做你的导师。
  • 在寻找导师之前,你必须要明确两件事,第一是你需要导师帮你解决什么问题;第二是为了能够换取导师的帮助,你能给他提供什么?

4. 乐为人师

  • 成为导师可以让你受益无穷。
  • 每个人都有教的能力:只要在某些地方快人一步,就能帮助别人。
  • 有时候你给比人做导师真正要做的就是给予关注。我们在生活中都需要别人的帮助以看到自己看不到的东西,因为当遇到涉及自身的问题和麻烦的时候,我们都会有些目光短浅。
  • 教授是学习的最佳途径之一。当你担任导师的时候,你会以全新的视角观察和思考,并面对更多有挑战的问题。在探究答案的过程中,你会愈加深入思考,发现自己的漏洞,甚至完全改变最初的想法。
  • 一旦我们试着向别人解释某件事情的运作原理或背后的原因的时候,我们在认知上的漏洞就会暴露出来。
  • 最好的教学方式就是以谦虚的视角来观察问题,以权威的口吻去诠释问题。你要明白,你教的目的是为了帮助别人,而不是为了证明自己的优越性或者寻求认可。
  • 除了写博客,录视频和演讲也是很好的教学方式。演讲时要心态谦卑,信心满满(而不是傲慢自大)。

5. 学历

  • 拥有(好的)学历并不是成功所必须的,但它限制了可以提供给你的职位数量,并且某种程度上也限制了你的晋升。
  • 学历教育可以提供一个相对完善的计算机基础教育。当然,这些知识你也可以通过自学获得,不过这需要花费大量的时间。
  • 如果你没有(好的)学历,那么就通过作品来证明你的能力。比如,你可以写博客,独立开发一个程序,在 GitHub 上创建或参与开源项目,或是在 Stack Overflow 回答问题。
  • 弥补措施是,你可以通过自考获得一个学位,大概需要花费两年的业余时间外加两千块的投入。

6. 知识短板

  • 专注于自身强项,这没什么不妥,但有时候,如果弱点得不到解决,通常会成为你的职业或生活的桎梏。
  • 准确识别短板的最佳方式之一就是看看自己在哪些工作上花费了大量的时间,或者一直进行重复性劳动。比如,学习操作系统或各类软件的快捷键就可以节省大量时间;编写宏或自动化脚本来简化各种软件或系统中的重复操作。
  • 维护一份清单,列出自己需要去研究或者不清楚的事物。

本文首发于公众号「李辉的代码厨房」。

《Flask Web 开发实战》第二次重印

最近要进行第二次重印,花了整整两天时间整理勘误。接下来的计划是,在第二次重印的书上市前,只在晚上处理论坛问题,尽量不在 IM 上回答和书上错误无关的提问(耗费心力,沟通成本非常高),也不再更新勘误页面。等到第二次重印的书上市,做下面几件事:

  • 写一篇《Flask Web 开发实战 2019 补丁》,汇总所有额外的项目源码变动,第三方工具和库的新版变化,常见的错误和常见问题等。
  • 和豆瓣协商,再为购买旧版本的读者更新一次文件;同时了解微信读书、多看、掌阅、京东、当当几个电子书平台的更新情况。
  • 再集中清理一次盗版文件。
  • 处理 Albumy 现存的两个 bug。
  • 整理一遍所有勘误,更新勘误页面。

2019/4/15 更新:

收到出版社的新版样书,也就是说,1-3 版本的书已经上市销售,目前哪里可以买到还不清楚(估计是京东自营)。

程序员的基本常识——职业篇

这个系列是《软技能》一书的摘抄整理。这本书名声在外,读过后发现并没有那么好。一来是内容比较水,废话太多;二来掺杂着大量的俗套励志段落,还包含一些不靠谱的建议(理财)。

尽管如此,经过提纯后,书里还是有一些有价值的信息,可以算是程序员基本常识。对刚刚入行,或是打算做程序员的人来说会比较有帮助。

这个系列会有 4 篇文章,分别对应职业、自我营销、学习和生产力 4 个主题。我会抽取书中有价值的部分,适当进行改写和扩充。欢迎评论补充相关信息,或是纠正错误观点。

1. 绝不要做他人都在做的事

  • 对待职业的首要原则:程序员要主动管理自己的职业生涯,「工作是属于公司的,而职业生涯却是属于你自己的」。
  • 当你为了谋生一头扎进写代码的世界时,其实你和中世纪小镇上开铁匠铺的铁匠没什么差别。
  • 把自己当做是一个公司,而不是打工者。把雇主当做是你的软件开发企业的一个客户。你的工作就是出售你提供的服务。
  • 集中精力成为一位专家,专门为某一特定类型的客户提供专业的整体服务。你只有真正专注于一类客户,才能找到非常好的工作。

2. 必须要设立自己的人生目标

  • 为了安逸,我们倾向于遵循已经设计好的路线。缺乏对人生目标的具体认知。「死都不怕,就怕不安逸。」
  • 大目标必须足够清晰,比如你希望自己 5 或 10 年后在哪里,获得怎样的成绩。最好把大目标写在纸上,放在每天能看见的地方。
  • 逆向反推,将大目标分解为每年、每月、每周、每日的小目标。
  • 定期检查和调整目标。

3. 选择职业类别

可选项通常有三种:

类别 好处 坏处
雇员 稳定的收入;相对轻松的工作 固定的收入;不自由
自由职业(比如做外包、培训、咨询) 自由;有赚钱的潜力 考验自制力;应付更多的事情;可能会找不到项目
创业 巨大的赚钱潜力;完全自由 风险巨大;需要学习大量的技能,包括市场营销、管理、财务等;可能需要长时间高强度工作

通常情况下,你会先做一份全职工作,积累了实力和各类资源后,再考虑尝试自由职业或创业。确保你做好了准备,比如积攒了能够维持数年开支的积蓄,以便应对没有稳定收入的情况。

你可以在业余时间启动你想创建的业务,等能从这项业务中产生足以维持生计的稳定收入时,再考虑辞职。采用这种方式时,确保你和公司签订的劳动合同里没有规定你的所有工作成果(包括业余时间,或是使用公司资源的情况下)都归公司所有,如果有相关条款,可以考虑咨询律师。

4. 选择技术方向

  • 对于自由职业 / 创业者来说,通常会选择有潜力的小众市场。这对选择技术栈同样适用,尝试在一个专业方向上拥有专长(专业化),比如 Python 开发、Python Web 开发、Python 特定 Web 框架开发三者的专业化程度逐渐递增。
  • 专业化的规则是:专业化程度越深,潜在的机会就越少,但获得这些机会的可能性越大,竞争者也更少。
  • 可以尝试发展两个方向,选择这两个方向的交叉点作为职业方向。比如会 Excel 的老师,会英语的导游,会编程的律师,都要比普通的老师、导游和律师更有竞争力。
  • 但同时注意不要只会某种语言和专业方向,否则会因为行业变化而被淘汰。先从专业化开始,再拓展分支。
  • 避免陷入对某一技术的狂热之中,这会让你在职业生涯之路上走得更远。不要试图寻找最好的语言、框架、操作系统和文本编辑器,更没必要为此浪费大量时间进行争论。
  • 对技术保持开放的心态,而不是固守自己已经了解的技术,声称它是最好的,你会发现更多的机会为你敞开大门。

5. 选择公司

类别 好处 坏处
小公司(创业公司) 面临更多的挑战;容易建立影响力;回报潜力大 可能会身兼多职;工作强度大;稳定性差
中型公司 工作稳定;加班较少 变化很慢;没有机会使用前沿技术
大公司 完备的流程和规范;培训机会多;大型有影响力的项目 官僚主义;只负责代码库的一小部分;很难获得关注

考虑自己适合哪一种公司,可以在工作前和目标公司的雇员聊一聊。

6. 破解面试之道

  • 让面试官对你怀有好感会有助于通过面试。方式有很多,包括:

    • 提前建立个人品牌
    • 提前熟悉目标公司的员工
  • 内推有推荐人的声望背书,会更容易获得面试机会。
  • 想办法与公司内部人员建立联系,比如参加本地开发小组和行业会议,在论坛、IM 群聊、社交网站、GitHub 上建立联系。
  • 面试的时候,你要集中精力证明自己就是无需监督也能自动自发做好事情的员工;同时还必须要证明:在技术上你确实胜任工作;最好还能说服面试官相信你非常能干,不会被困难阻挡。

7. 获得晋升机会

  • 在任何公司里能让你脱颖而出的最重要法宝就是承担更多的责任。

    • 没有人愿意涉足的领域是搜寻机会最好的地方。
    • 成为团队中其他人的导师,建立声誉。
    • 增加自己的曝光度:做内部分享,发表意见,解决技术难题。
  • 关于办公室政治:尽管不能完全避开,但至少应该知道会发生什么,哪种人需要避开,那种人永远不要有交集。
  • 如果脚踏实地努力后仍然无法晋升,可以考虑换工作。

8. 成为专业人士

成为专业人士能帮你获得更好的工作和更多的客户:

  • 遵守自己的原则。
  • 专注于正确完成工作。
  • 不惧怕承认自己错了,不会文过饰非。
  • 持续稳定。
  • 勇于承担责任。
  • 完善自己,迎接挑战,而不是降低标准。一旦你越多了底线,选择妥协,就很难回头。

9. 成为自由职业者

你为自己工作的时候通常会比为别人工作时更加努力,但热情通常不会持续很久,你每天真正工作的时间也没有你想象的那么多。缺乏监督可能会让你长久的陷入拖延和偷懒的境地,为此你需要:

  • 学习时间管理,并建立一系列日常原则。
  • 自我激励,培养自制力。尽量排除干扰和诱惑,让它们远离你的工作环境。
  • 适应孤独感,并适时的走出去,参见开发者聚合或行业会议,和其他从业者交流。

获得客户的最佳途径:吸引式营销(inbound marketing)。简单来说,就是免费提供有价值的东西。通过写博客、录视频和播客、演讲等方式提供与自己的服务相关的有价值的内容(大多是免费的)。

你的收费标准应该按照全职工作的两倍时薪收取,因为你通常需要自己缴税,购买商业软件。如果你的工作能够为客户带来更大的价值(提高他们的业务或是节省大量开支),你可以根据你能带来的价值来定价。记得向客户说明为什么你的服务值这个价格。

如何创建你的第一个产品:

  • 不要在没有找到客户之前就构建产品。
  • 一个产品要解决何种问题,以及目标受众是哪些人,这些问题必须在创建产品之前就弄明白。多和目标客户沟通交流,不断检验自己的想法。
  • 捷径是提前在对应的领域建立影响力,通过创建博客,演讲等方式发展受众。
  • 通过测试市场来验证你的产品,看看你的潜在客户是否真的愿意为它买单。比如进行一场预售,为提前付费的客户打折,看看有多少人愿意买单。
  • 如果是创业产品,那么就从小处着手,关注核心功能实现,快速建立产品原型并上线,及时获得反馈并不断改进,想法行不通就及时脱身。你可能需要重复多次这个过程才能创建一个成功的产品。

10. 成为创业者

  • 创业拥有巨大的潜在回报,但也极其危险。
  • 大多数创业公司的创始人都有所谓的退出策略,比如:

    • 当公司成长到一定规模的时候被收购
    • 上市
  • 当你有了一个好点子,有技术或是有技术合伙人,你最好还要拥有独一无二的知识产权或行业资源,这可以确保你的产品不会被大公司快速复制。而且你的项目要有规模扩张的潜力。
  • 除了持谨慎策略的自力更生型创业公司外,大多数初创公司都希望获得外部投资以快速成长。在开始的时候可以尝试申请加入创业孵化器计划。
  • 对创业公司来说,第一个重要里程碑就是获得第一笔资金(种子资金),一般天使投资人会投给早期创业者,即种子轮 / 天使轮。拿到天使投资后你可以雇佣员工,开始扩张。
  • 种子基金烧光后,如果你的点子仍然可行,将会获得第一轮投资(A 轮),风险资本通常会在这一轮介入(风险投资人 / 风投)。这之后,大多数创业公司还会经过几轮融资,最后成功实现盈利,或者被收购。 

本文首发于公众号「李辉的代码厨房」。

我开了微信公众号

在年初的某篇文章下面,有朋友建议我开个微信公众号,当时我说估计不会有,并表达了对微信、朋友圈和公众号的讨厌。然而两个月不到,我就开了公众号……

我的公众号是「李辉的代码厨房」,目前的规划是写作和技术相关的话题,比如职业选择、个人品牌建设、学习方法、生产力、编程技巧、工具推荐等,不写和某个编程语言或框架相关的内容。欢迎关注!

当然,我仍然讨厌微信、朋友圈和公众号。这里的讨厌主要包括这些方面:

  • 生态环境糟糕:太多垃圾信息,对生活各方面的过度侵入和打扰。
  • 微信本身很难用:除了功能臃肿,群聊的设计和内容审查,还有很多细节不够好,比如草稿不会多设备同步,浏览某个对话时不记忆退出时的位置。
  • 公众号的反互联网设计:内容无法在互联网上被直接检索到,在文章中无法插入公众号文章以外的超链接。

那为什么还要开公众号?

主要是因为微信公众号能够触及更多的读者,和读者的距离非常近。写文章自然会想让更多人看到,也希望有更多的交流,相对于博客,公众号更容易做到这一点。

另外,和读者距离更近,会让我更认真的对待写作,何况公众号发过的文章还不能修改(发了第一篇文章才知道……)。通过创建一个独立的公众号,也会让我更有动力进行主题写作,进一步锻炼非技术写作能力。

为了对抗公众号封闭的特性,我同时创建了知乎专栏「代码厨房」,公众号的文章会同步发到专栏和博客。公众号文章末尾的“阅读原文”链接通常会指向对应知乎专栏上的同一篇文章,以便读者可以更方便的点击文中的链接。

为 Discourse 安装插件

不得不说,Discourse 的插件安装方式真是麻烦(免费的代价……)。官方的插件目录在 https://www.discourse.org/plugins 可以看到,插件介绍页面并没有介绍如何安装插件,只介绍了某个付费版本的套餐内置了这些插件。

这次一共安装了三个插件:

  • Sloved:因为 HelloFlask 论坛越来越像一个工单处理系统,所以能够标记问题已解决的 Solved 插件会很有用。
  • Sitemap:生成站点地图。
  • Canned Replies:能够预先保存某些经常发布的回复(staff only)。因为太多人发帖时论坛说明也不看,markdown 也不会用,写得乱七八糟,每次都要一一提醒,所以这个插件可以节省不必要的体力开支。

具体的安装方法可以在官方论坛的这个帖子看到。步骤并不复杂,下面是一个简化版本的安装介绍:

首先获取到你要安装的插件的 GitHub 或 BitBucket 仓库地址,官方插件可以在 Discourse 的 GitHub 组织中找到。这里以投票插件为例,它的仓库地址为 https://github.com/discourse/discourse-voting。

1. 打开配置文件

登录到你的服务器,执行下面的命令,编辑配置文件 app.yml:

$ cd /var/discourse
$ nano containers/app.yml

2. 插入插件的仓库地址

在这个文件中,找到 hooks 一节,把插件地址写到 `- git clone https://github.com/discourse/docker_manager.git` 一行的下面,注意在地址前添加 `- git clone`:

hooks: 
 after_code: 
    - exec: 
       cd: $home/plugins 
       cmd: 
         - git clone https://github.com/discourse/docker_manager.git 
         - git clone https://github.com/discourse/discourse-voting.git

3. 重新构建容器

使用下面的命令重新构建容器(确保没有改变当前目录,即 `/var/discourse`):

./launcher rebuild app

经过漫长的等待,安装就完成了。你可以在论坛的设置-插件页面对插件进行配置。

从国内的 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/

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

(使用 HTML、JavaScript、Flask 或 Nginx)为丢失的图片显示默认图片

当在 HTML 页面上显示图片时,如果图片不存在,我们通常需要显示一个默认图片。

假设我们的图片路径在 /imgs 下,默认图片为 /imgs/default.jpg,下面是一些常见的处理方法示例。

Solution 1: HTML / JavaScript

最简单的,你可以使用 <img> 元素的 onerror 属性来设置默认图片:

<img src="/imgs/cat.jpg" onerror="this.src='/imgs/default.jpg'">

类似的,你也可以使用 JavaScript(jQuery)监听 img 元素的 error 事件(当图片地址无效时浏览器会触发这个事件):

$('img').on("error", function() {
  $(this).attr('src', '/imgs/default.jpg');  // 替换为默认图片
});

Solution 2: Flask

除此之外,你也可以在服务器端处理,以 Flask 为例,你可以写一个自定义视图函数来加载图片:

import os
from flask import send_from_directory

# ...

@app.route('/img/<path:filename>')
def get_image(filename):
    img_path = os.path.join(images_path, filename)  # 获取图片路径

    if not os.path.exists(img_path):  # 判断图片文件是否存在
        return send_from_directory(os.path.join(images_path, '/imgs/default.jpg'))
    return send_from_directory(img_path)

在模板里,使用这个视图函数获取图片 URL:

<img src="{{ url_for('get_image', filename='imgs/' + image.filename) }}" >

Solution 3: Nginx

在生产环境下,出于性能的考虑,我们通常会使用 Web 服务器(比如 Nginx / Apache) 来服务(serve)静态文件。以 Nginx 为例,你可以使用 try_files 指令来设置默认图片:

location /imgs/ {
    try_files $uri /imgs/default.jpg;
}

附注:本文改写自我的SO 回答

在 Flask-Admin 中集成富文本编辑器 CKEditor

Flask-Admin 里,默认使用普通的文本区域(<textarea>)来编辑长文本。借助 Flask-CKEditor,你可以很容易的为 Flask-Admin 集成富文本编辑器 CKEditor

首先安装 Flask-CKEditor:

$ pip install flask-ckeditor

下面是一个简单的例子,其中的关键步骤已用注释标出:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_ckeditor import CKEditor, CKEditorField  # 导入扩展类 CKEditor 和 字段类 CKEditorField

app = Flask(__name__)
app.config['SECRET_KEY'] = 'dev'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'

db = SQLAlchemy(app)
ckeditor = CKEditor(app)  # 初始化扩展

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(120))
    text = db.Column(db.Text)


# 自定义 Post 模型
class PostAdmin(ModelView):
    form_overrides = dict(text=CKEditorField)  # 重写表单字段,将 text 字段设为 CKEditorField
    create_template = 'edit.html'  # 指定创建记录的模板
    edit_template = 'edit.html'  # 指定编辑记录的模板

admin = Admin(app, name='Flask-CKEditor demo')
admin.add_view(PostAdmin(Post, db.session))

if __name__ == '__main__':
    app.run(debug=True)

在模板文件夹里,我们创建一个 edit.html 文件(templates/edit.html),在这个文件里重载 Flask-Admin 的编辑模板,加载 CKEditor 资源:

{% extends 'admin/model/edit.html' %} <!-- 声明继承 Flask-Admin 的模型编辑模板 -->

{% block tail %} <!-- 向父模板的 tail 块内追加内容 -->
    {{ super() }}
    {{ ckeditor.load() }} <!-- 加载 CKEditor 的 JavaScript 文件,默认从 CDN 获取 -->
{% endblock %}

实际的效果如下图所示:

完整的可运行的示例程序代码可以在这里获取到。

你可以阅读 Flask-Admin 文档Flask-CKEditor 文档了解更多进阶用法。

附注:本文改写自我在 Stack Overflow 上的回答(https://stackoverflow.com/a/46481343/5511849)。

《Flask 入门教程》后记

恭喜,你已经完成了整个 Flask 入门教程。不出意外的话,你也编写了你的第一个 Web 程序,并把它部署到了互联网上。这是一件值得纪念的事,它可以作为你的编程学习之路上的一个小小的里程碑。继续加油!

留言 & 打卡

如果你完成了这个教程,可以在 HelloFlask 论坛上的这个帖子留言打卡,欢迎分享你的心得体会和经验总结。如果你对这本书有什么建议,也可以在这里进行留言反馈。

讨论与求助

如果你想和其他同学交流 Flask、Web 开发等相关话题,或是在学习中遇到了问题,想要寻求帮助,下面是一些好去处:

付费支持

本书采取自愿付费原则,价格为 10 元。如果你愿意,可以通过付费来支持我,让我有更多的时间和动力写作 Flask 教程和文章。你可以通过支付宝账号 withlihui@gmail.com 转账,或是扫描下面的二维码付款。

支付宝二维码 / 微信二维码

qr_alipayqr_wechat

进阶阅读

说来惭愧,在这本教程几乎每一章的结尾,我都会提到《Flask Web 开发实战》,每次写到这里,我都觉得自己好像在写“问候家明”。所以,最合适的进阶读物我已经推荐过很多次了。除了这本书,其他的进阶读物如下:

未完待续

你喜欢这本书以及这本书的写作模式吗?

如果有足够的人喜欢的话,或许我会考虑写一本包含 Flask 进阶知识的《Flask 进阶教程》。按照设想,在这个进阶教程里,这个 Watchlist 程序变成一个支持多人注册和使用的简化版豆瓣电影 / IMDb。同时介绍各类常用扩展的使用和 Flask 上下文、蓝本等进阶知识。

再或者,这个教程或许会升级为一本完整的书,使用类似的编写模式,引入一个更加丰富有趣的程序,包含优化后的入门知识和 Flask 进阶内容。

如果你期待这样一本进阶教程 / 书的出现,欢迎让我知道。你可以在打卡 & 留言贴发布留言,或是直接发邮件(withlihui@gmail.com)告诉我。