布朗运动的计算机模拟
布朗运动是指悬浮在液体或气体中的微粒所做的永不停息的无规则运动。
薛定谔在《生命是什么》中描述高锰酸钾的扩散现象,他说虽然高锰酸钾看上去是从高浓度向低浓度扩散,但并非是分子的冲撞造成的,本质上是分子的无规则随机运动。
这里的无规则随机运动是指,分子在某一时刻的运动方向,是完全随机的,不受液体浓度的影响。
看上去似乎难以置信,但正是由于高浓度+随机运动,才产生了一种从高浓度流向低浓度的观察效果。
书中给出了一个解释,假设其中有一薄膜,由于分子运动随机,左右运动的概率是相等的,但假设左侧浓度更高,向右运动穿过薄膜的分子必然更多。
本文试图通过计算机模拟的方式,来呈现分子的运动。
在试验中,我们有一个正方体水箱,开始时,在水箱的左下方矩形区域放置高锰酸钾溶液,然后在每一次迭代中,让分子做布朗运动。
下图是在做了10000次迭代后的情形,从图片中的状态已经可以明显看出扩散的痕迹了:
可以点击下面的链接查看属于自己的随机过程:布朗运动模拟器。
好雨知时节吗
下了一夜雨,下了一夜的往事。又是三月雨。
早起后,淅淅沥沥的雨还在恍恍惚惚的下。
阴沉潮湿的空气,昏暗的房间。
不想开灯,唯恐破坏了这种氛围,竟然对这种原始的气氛有些不舍,真是好玩。
听着几首老歌,低沉冗长的音色把时间都拉长了很多。
不知道自己能不能听出自己的声音,或者能不能想象出自己呐喊的神情。
python 函数着色器设想
背景
跟食品一样,代码也有过期时间。但糟糕的是,食品出产时就已标明了何时过期,但是一段代码是否过期却不好判定。
我们通常有这样的经验,当一个资源地址(url)要下线时,我们会从 nginx 的请求日志里查看该接口在最近一段时间内有没有被访问。
函数也可以做类似的处理,要解决的问题是,由谁来充当 nginx 记录请求的角色。
现状
当前网络上已经有比较好的支持,最典型的实现有以下几类:
- python 用来性能调优的库,如 profile、hotshot 等
- 使用装饰器实现,每个要统计的对象都需要加装饰器,操作不够优雅且略笨拙
- 自己维护变量统计,这种人工成本较大
方案 2、3 成本都比较高,且实现不够优雅,难以在高层次上复用。
方案 1 是比较好的一种方式,但也存在着它的问题。
对于脚本来说,这种方式很具优势。但是,对于大型系统来说,这类库的接入成本不低,可操作性较差。
其次它的抽象程度不够,需要自己再做额外分析处理,很难做到开箱即用。
设想
基于以上原因,我们可以设想一种更为友好的方式,以一种系统的抽象粒度给出。
这里有一个很重要的指标需要评估,就是对系统性能的影响有多大。当然,这是个技术问题,可以从技术角度解决。
我们这里使用着色来形象地表达记录访问状态的动作。不考虑性能影响,我们的目标是可以针对某命名空间下的对象及其方法进行着色,并获得着色后的可视化效果,以辅助对旧的代码进行重构。
我们希望有一个开关来控制着色,并可以热启动和热停止,且停止后尽可能减少或彻底取消对性能的影响。
着色器记录的信息,包括函数的调用次数及耗时。我们希望可以对各层级进行比较,可以很简单地看出对象下哪个函数在近一个月没有调用过,或者一个函数调用的平均耗时。
更上一层次,我们希望该功能以一种成熟的第三方模块给出,仅需配置极少内容即可使用。如作为 django 的第三方模块。
实现细节
我们将使用钩子或其它技术手段,接管函数的调用逻辑。我们将记录函数的如下信息:
梯度时间 | 函数或属性 | 调用次数 | 平均耗时 |
---|---|---|---|
2020-03-28 20:01 | views.WelcomeView.get() | 20 | 0.2 |
2020-03-28 20:01 | views.WelcomeView.post() | 3 | 0.5 |
2020-03-28 20:01 | views.WelcomeView.var | 2 | 0.5 |
由于请求量较大,可对数据做缓存处理,并定期刷新到数据库;由于数据量较大,可对过旧的数据进行压缩处理。
系统将对该数据制作报表,报表主要有两种:
第一种,一段时间内函数的调用次数热力图,并对不同函数按调用次数作升序排列。
第二种,一段时间内函数的调用耗时热力图,并对不同函数按调用平均耗时作降序排列。
django核心处理流程 3. wsgi
wsgi 是 python 服务器与 web 应用间交互的通用接口,实现该接口的应用可以通过任何实现了该接口的服务器部署。
wsgi 诞生的背景
我们知道,接口可以有效地解耦调用方与被调方。
拿 cmq 举例,如果我们实现了它所规定的相关接口,就可以提供给调用方使用,而不必关心它是否是原生的 cmq。
最开始的 web 框架花样繁多,服务的部署多种多样,往往框架的选择就已经限制了服务器的选择。而 wsgi 的目标,就是将框架的选择与Web服务器的选择解耦。
需要做哪些工作
我们先思考一下,如何分离这两者,提供一个统一的界面接口。
首先,在我们的框架里,是不需要关心 http 细节的,这就需要把 http 的相关信息以一种通用的格式打包过来。一种比较直观的结构是一维字典,或者是一个元组列表。
拿到处理的信息后,我们可以对其进行解析,然后执行处理逻辑,返回 http 响应。
响应当然可以直接返回一个 http 协议文本,但是 web 应用不该过多参与 http 细节。这里将返回分解为了三部分,返回状态说明、响应头、响应体。
我们知道,响应头是必须先于响应体的,那么在技术上如何保证呢?
wsgi 给出一个 start_response 参数,这是一个可调用函数,以返回状态说明和响应头为参数。如此我们便可以判断其与响应体的顺序了。
另外还有一个问题,一个请求的响应体可能会分多段返回,比如我们要对一个列表的每个元素做计算,由于计算量很大,我希望每算得一个结果就返回一个结果。所以这里我希望能以 yield 的方式来做返回。
跟据上面一些问题的考量,我们就可以得到一个近似 wsgi 的接口了:
1 | def simple_app(environ, start_response): |
实际返回的 headers 跟这里的 response_headers 并不完全一致,应用只负责它关心的 header,剩下的就交给 wsgi 服务器了。
wsgiref 模块
python 的 wsgiref 模块实现了一个简单的 wsgi 服务器,我们可以借助它来让我们前面写的一些代码变为运行时。
首先,我们把之前的逻辑包装为一个标准的 wsgi 应用:
1 | def get_wsgi_application(root_urlconf): |
然后,我们将该应用传递给 wsgi 服务器,让它把这个应用运行起来:
1 | wsgi_application = get_wsgi_application(settings.ROOT_URLCONF) |
此时,我们访问 http://127.0.0.1:5302/welcome/oog 就可以看到下面的提示了:
1 | Hello oog, welcome to jango. |
发挥 wsgi 的优势
我们前面说,wsgi 可以解耦应用和服务器,也就是说,我们的应用同样可以部署在其它 wsgi 服务器下。
这里我们尝试使用 gunicorn 来验证该服务是否可以在其它 wsgi 服务器下正常运行。
首先我们创建一个脚本来生成一个 wsgi 接口的函数:
1 | # demo/wsgi.py |
然后,我们在命令行启动 gunicorn,并将该 application 传递给它:
1 | gunicorn -b 127.0.0.1:5302 -w 4 demo.wsgi:application |
这里我们借助 gunicorn 的支持,启动了 4 个 worker,一切运行正常。wsgi 做得很好。
到这里,我们就已经成功把静态代码部署为一个服务器了。相关代码可以在 https://github.com/pysnow530/jango 找到。
虽然这里只有一些核心逻辑,但是已经看到了一些 django 的脉络。
当然,django 的能力不止于此,它实际上是一个框架的框架,中间件、灵活的配置系统、第三方模块,使得它可以在系统层面做进一步的抽象。它的思想远不及此,不过这些已超出本文的范围,就此打住了。
这只是旅途的一个开始,想要的答案,让我们去 django 的源码里找吧。
一些重要细节
上面的响应体使用了 yield 和 list 两种返回形式,本质上所有的可迭代对象都可以作为响应体返回使用。
同样的,这里所说的函数,也可以替换为其它可执行对象,包括实现了 __call__
方法的类。
扩展资料
wsgi 协议可参考
django核心处理流程 2. 请求响应逻辑
web 框架的设计,将每一个资源地址映射到一个处理逻辑。这篇文章将讲述 django 中对请求的解析处理,及处理结果的返回。
视图函数
资源地址对应的处理逻辑,在 django 中叫做视图(view)。
视图是一个可调用对象,一般为函数。
请求的上下文被打包到一个叫做 HttpRequest 的对象中,并传入视图函数。函数处理后,将结果打包到一个 HttpResponse 对象,并返回给主调方。
这里的主调方,其实就是我们的框架了。
下面是一个比较典型的视图函数:
1 | def welcome(request, nickname): |
这个示例函数的结构比较清晰。需要注意的是,参数里有一个 nickname,django 会把资源地址中的匹配参数一并作为视图参数传递过来。
与该函数绑定的 url 为:
1 | [r'/welcome/(?P<nickname>\w+)', views.welcome] |
可以看到 nickname 的来源及解析方式。
请求上下文
那么请求的上下文是如何构造出来的呢,下面就是构造的过程:
1 | class HttpRequest: |
我们可以看到,HttpRequest 是通过解析一个 environ 字典生成的。environ 字典里包含了请求的上下文信息。至于 environ 从何而来,它实际上是 wsgi 接口定义。我们在下篇文章展开。
environ 里包含了所有请求相关的信息,上例中的 path_info 实际上就是我们的请求地址了,比如前面的 '/welcome/oog'
。
HttpResponse 也会解析请求参数及 COOKIE 等,这样我们就可以很方便的获取需要的信息了。
http 响应
请求被处理完后,视图函数需要返回一个 HttpResponse 对象。该对象包含了需要返回的信息。
一个 http 请求返回的结果,比较典型的有下面几项:
- status code,它定义了请求被处理的结论,比如 200 表示成功,302 表示该资源需要跳转到其它资源地址等
- body,比如一段 html 代码,或者是一个故事的文本描述
- content type,标识 body 的类型,以方便资源请求方正确理解它。比如 text/html、text/plain 等类型
HttpResponse类的定义如下:
1 | class HttpResponse: |
请求、响应流程
现在我们已经有请求解析和响应对象的构造,一个请求过来是如何发生的呢?
我们先来看 8 行代码:
1 | def wsgi_application(environ, start_response): |
代码中,得到 request 后,调用函数并获取 response,然后把关键信息按需要的格式返回即可。
其中 handle_view() 是一个 10 行函数,功能是根据 url 找到视图函数并执行。具体参见 jango 的源码 https://github.com/pysnow530/jango。
这里的结果渲染部分初看会有点奇怪,这里其实是 wsgi 定义的规范,后面的文章会讲到。
到这里,我们就已经完成了一个 url 从请求匹配,到视图执行,最后到请求响应的完整过程。文章里讲到的 wsgi 将会在下一篇文章细说。
一些重要细节
我们上面看到的视图,都是一些函数的形式。如果看 django 的官方文档,会发现 django 也提供了一种类的方式,类名对应请求名。
1 | class Welcome(View): |
但是不要被外在形式迷惑,类形式的视图本质也是一个可调用对象,只是通过 Welcome.as_view() 包装的语法糖方便使用。具体可参考 django 源码。
另外一点,http 比例子中讲到的功能要更为丰富,它还涉及了文件的上传下载等功能。具体可参考扩展资源给出的 rfc 文档。
扩展资料
http 协议可参考 https://tools.ietf.org/html/rfc2616。
django核心处理流程 1. 路由
路由的设计是每一个 web 框架都必须优先考虑的问题,它决定了资源地址在代码中的组织方式。
数据结构
从本质上讲,路由表维护了 url 地址到业务逻辑代码的映射关系。
在 django 中,为了使 url 规则更为灵活,采用了正则表达式的方式来匹配 url 地址。
正则表达式是一种模糊匹配,同一个 url 可能会对应多个正则表达式。所以 django 中的路由是一个列表配置。列表中的每一项是一个 (pattern, view)
对。
为了便于模块化,django 还提供了第二种配置项 (pattern, conf_module)
,用于将一组 url 映射到某个前缀下。所以整个路由设计的结构上是一棵树。
下面是一个例子:
1 | # urls.py |
假如 suburls 是一个成熟的模块,我们就可以使用这种方式给该模块分配一个前缀,如此就可以投入使用了。
下面是一个很经典的例子,将 django 内置的后台管理模块配置到 /admin
下:
1 | urlpatterns = [ |
查找算法
上面提到,django 中的路由实质上是一棵树的结构,所以查找上也是树的遍历算法。由于我们想借助列表结构来说明优先级,这里必然要使用深度遍历。
我们首先会从 settings 配置找到根 url 模块,然后遍历模块中的 urlpatterns 列表,如果匹配到的是一个模块,就继续匹配模块中的列表。如果最后没有找到可执行的逻辑,就是我们常见的 404 了。
1 | # demo/settings.py |
其中,参数 path_info
是需要解析的url。解析后,我们可以获取执行的函数,及前端传过来的 url 参数。后面就可以调用具体函数执行了。
代码地址可参考 https://github.com/pysnow530/jango。
一些重要细节
当然,上面的模型只是一个简化,这里提一下原生 django 几个比较重要的细节。
第一,我们说匹配规则是一个正则表达式,但是 django 已对正则表达式做了封装,提供了更简单直白的用法,比如 /welcome/<name>
。更多的限制,必然是一种发展趋势,它让我们更好的关心逻辑代码而非技术。
第二,为减少封装了解本质,我们把路由中的每一个配置项定义为一个列表,实际 django 中是以一个类来替换的,作用类似,只是抽象程度更高。
第三,django 支持给路由配置项命名,并在需要时支持将名称反转为 url。如在模板中的 { % url 'api_welcome' 'name' % }
或代码中使用 reverse('api_welcome', args=('name',))
,这让修改 url 几乎零成本,是一个优秀的设计。
说了这么多,路由的核心简单且简洁,就是一个映射表。通过它,我们能找到资源地址对应的逻辑代码。
下一篇文章,我们来看一下逻辑代码是如何组织及工作的。
读《悟空传》
“””
有人说《悟空传》颠覆西游,其实我一点儿没觉得颠覆,我觉得我写的就是那个最真实的西游,西游就是一个很悲壮的故事,是一个关于一群人在路上想寻找当年失去的理想的故事,而不是我们一些改编作品里面表现的那样,就是打打妖怪说说笑话那样一个平庸的故事。
也正是这样,我十分受不了西游里面就只有打妖怪打妖怪,打你妹啊。西游的主题根本就不是打妖怪。妖怪只分两种:一种是当年跟着孙悟空一起反抗天庭的兄弟,像牛魔王之类的,孙悟空必须把当年和他一起战天斗地的结拜兄弟都干掉,就为了成佛,我觉得这就是最大的悲剧;另一种则是神仙安排下来的,不是这个的坐骑就是那个的宠物。这也太恶心了,一边让人去西天一边安排着九九八十一难,就想把你整死。所以整个西游就是一出悲剧,是一场阴谋,不论你怎么做,都是死路一条。你不服从神,不向西走,整死你;你向西走,一路上九九八十一难,都是神安排的,依然整死你。最后到了西天,你以为成功了,结果给你一部经书还是假的,全是白纸;你拿回去退货,送了礼,给你一部有字的,你以为是真的,是真的吗?其实还是假的,因为本来无一物,何处惹尘埃。所谓道不可道,我们说了别人的答案不是你的答案,如果有人要拿答案灌输给你,那不是为了让你聪明,更可能是想让你变傻。
最后四个人成了佛,成佛以后呢?没有了,什么都没有了。以前活生生的有血有肉有感情有梦想的四个人,一成了佛,就完全消失在这个世界上了。佛是什么?佛就是虚无,四大皆空。什么都没有了,没有感情没有欲望没有思想,当你放弃这些,你就不会痛苦了。但问题是,放弃了这些,人还剩下什么?什么都没了,直接就死了。所以成佛就是消亡,西天就是寂灭,西游就是一场被精心安排成自杀的谋杀。
“””
《得到阅读器》吐槽环节
得到阅读器的对外接口,真的是毫无设计感。
有图为证。
记一个关于星河的梦
不是真的星河,大家说的星河是很多星星在夜空中汇聚成一条大河的形状。
那时候已经是很晚了,感觉四下昏暗暗的。我路过一座桥,桥面却被月光照的很亮。
不经意间抬起头,发现天上竟然有一条河流在流淌。
河流很宽,在夜空中有点昏暗,能很清晰的看到水波。河流很长,一眼望不到边际,只是闪烁着流淌了去。
我呆呆地站在那里,竟然有种不知道从哪里来的感动。
正当我看得出神时,天空渐渐暗了下来,最后只剩漆黑一片,什么都看不到了。