写在前面
- 作者电脑 CPU 为 4 核,因此使用 4 个线程测试是合理的
- 本文使用的
cpython
版本为3.6.4
- 本文使用的
pypy
版本为5.9.0-beta0
,兼容 Python 3.5 语法 - 本文使用的
jython
版本为2.7.0
,兼容 Python 2.7 语法 - 若无特殊说明,作语言解时,
python
指 Python 语言;作解释器解时,python
指cpython
cpython
版本为 3.6.4
pypy
版本为 5.9.0-beta0
,兼容 Python 3.5 语法jython
版本为 2.7.0
,兼容 Python 2.7 语法python
指 Python 语言;作解释器解时,python
指 cpython
前几天工作时遇到了一个匪夷所思的问题。经过几次尝试后问题得以解决,但问题产生的原因却仍令人费解。查找 SO 无果,我决定翻看 Python 的源码。断断续续地研究了几天,终于恍然大悟。撰此文以记。
Linux 中的每一个文件都有其 所属用户 及 所属用户组,根据这两个属性可将文件访问者分为三类:所属用户自己、所属用户组中的用户 和 其他用户,我们可以针对不同的访问者设置不同的用户权限。
“访问”可分为三类:读、写 与 执行。我们可以用 ls -l
命令查看一个文件的权限:
$ touch test
$ ls -l test
-rw-rw-r-- 1 hsfzxjy hsfzxjy 0 Jul 3 23:44 test
首部的 -rw-rw-r--
即为文件的权限位。权限应该分为四部分来看:-/rw-/rw-/r--
。第一部分标志文件的类型,如 普通文件(-
)、目录(d
)、UNIX 套接字(s
)、符号链接(l
)、块设备(b
)等等。接下来的三个部分依次代表 所属用户、所属用户组、其他用户 的权限,每部分由三个标志位组成:读标志位、写标志位、执行标志位。
目录是一种特殊的文件,因此也拥有文件权限的概念,但权限的语义与普通文件稍有差异:
ls
touch
(当文件不存在时)、rm
等cd
出于某些特殊目的,Linux 中存在两个特殊的权限位:粘滞位(t
)、Set Id(s
)。这两个权限可以 叠加 在执行权限位上,其中 Set Id 可以置于 所属用户 和 所属用户组 的权限组上,而 粘滞位 只能置于 其他用户 权限组上。当特殊权限被设置时,执行权限位上即会显示 s/t
(已有 x
权限)或 S/T
(尚未有 x
权限)。
粘滞位的作用是 防止他人误删自己的文件。当某个目录的其他用户权限组有 w
权限时,系统中的其他用户即可随意删除目录中的文件。而一旦叠加上 t
权限,只有文件的所有者方能删除文件。一个经典的例子是 /tmp
:
$ ls -l /
drwxrwxrwt 13 root root 12288 Jul 4 00:15 tmp/
Linux 中的进程也有自己所属用户与用户组。一般而言,进程的所属用户即为其发起者,但这会引起一些麻烦。一个例子是 passwd
命令,该命令需要修改属于 root
用户的系统文件以保存密码,倘若进程所属用户即为所属者,此功能则无法实现。
Set Id 权限的作用是:在文件被执行时,将其有效用户/用户组设置为文件的用户/用户组,而不是当前执行者。下面是一个演示:
设当前用户为 hsfzxjy
,我们在 /tmp
下创建一个 test
文件,并删去其他用户的 r
权限:
$ cd /tmp
$ echo test text > test
$ chmod o-r test
$ ll test
-rw-rw---- 1 hsfzxjy hsfzxjy 0 Jul 4 00:28 test
由于 test
文件的所属用户是 hsfzxjy
,其他用户没有权限读取其中的内容:
$ sudo -u mysql cat test
cat: test: Permission denied
现在我们修改一下 cat
命令的权限,为了不影响系统文件,我们拷贝一份 cat
副本至当前目录:
$ cp /bin/cat .
$ chmod u+s cat
$ ll cat
-rwsr-xr-x 1 hsfzxjy hsfzxjy 52080 Jul 4 00:34 cat*
再以 mysql
的身份执行命令:
$ sudo -u mysql ./cat test
test text
可见 ./cat
在执行时所属用户是 hsfzxjy
。我们可以使用 ps
命令更清楚地看到这点:
$ sudo -u mysql cat
# 在另一个终端中
$ ps -eo euser,ruser,comm | grep cat
mysql mysql cat
# -----------
$ sudo -u mysql ./cat
# 在另一个终端中
$ ps -eo euser,ruser,comm | grep cat
hsfzxjy mysql cat
技术流水账一篇,记录踩过的坑
Django Channels 官方文档宣称 channels 的最佳配置是使用其自带的服务器组件 Daphne,但在开发中我发现 daphne 处理普通请求比在 WSGI 架构下慢了好几倍,更何况使用 daphne 派发静态文件是十分不切实际的。于是我将 http.request
和 websocket.*
两个 channel 解耦,前者使用 nginx 配合 uwsgi 处理,后者使用 nginx 反向代理至 daphne 处理。这样一来便可充分利用两种架构的优势。
旧架构:
新架构:
神的时代已离去 神的故事却化为传说 流落凡间 供凡人传颂、膜拜
在上世纪 90 年代,出现过一款不可思议的游戏——雷神之锤(Quake series)。除了优秀的情节设定和精美的画面,最让人称道的莫过于它的运行效率——要知道在那个计算机配置低下的时代,一段小动画都是一个奇迹,但 Quake 却能流畅地运行于各种配置的电脑上。
直至 2005 年,当 Quake Engine 开源时,Quake 系列的秘密才被揭开。在代码库中,人们发现了许多堪称神来之笔的算法。它们以极其变态的高效率,压榨着计算机的性能,进而才支撑起了 90 年代 3D 游戏的传奇。其中的某些算法,甚至比系统原生的实现还要快!
我们今天的主角——快速平方根倒数算法(Fast Inverse Square Root)就是其中一个。
《神坑》系列将会不定期更新一些可遇而不可求的坑 防止他人入坑,也防止自己再次入坑
现有两个 View
类:
class View(object):
def method(self):
# Do something...
pass
class ChildView(View):
def method(self):
# Do something else ...
super(ChildView, self).method()
以及一个用于修饰该类的装饰器函数 register
——用于装饰类的装饰器很常见(如 django.contrib.admin
的 register
),通常可极大地减少定义相似类时的工作量:
class Mixin(object):
pass
def register(cls):
return type(
'DecoratedView',
(Mixin, cls),
{}
)
这个装饰器为被装饰类附加上一个额外的父类 Mixin
,以增添自定义的功能。
完整的代码如下:
class Mixin(object):
pass
def register(cls):
return type(
cls.__name__,
(Mixin, cls),
{}
)
class View(object):
def method(self):
# Do something...
pass
@register
class ChildView(View):
def method(self):
# Do something else ...
super(ChildView, self).method()
看上去似乎没什么问题。然而一旦调用 View().method()
,却会报出诡异的 无限递归 错误:
# ...
File "test.py", line 23, in method
super(ChildView, self).method()
File "test.py", line 23, in method
super(ChildView, self).method()
File "test.py", line 23, in method
super(ChildView, self).method()
RuntimeError: maximum recursion depth exceeded while calling a Python object
【一脸懵逼】
从 Traceback 中可以发现:是 super(ChildView, self).method()
在不停地调用自己——这着实让我吃了一惊,因为 按理说 super
应该沿着继承链查找父类,可为什么在这里 super
神秘地失效了呢?
为了验证 super(...).method
的指向,可以尝试将该语句改为 print(super(ChildView, self).method)
,并观察结果:
<bound method ChildView.method of <__main__.ChildView object at 0xb70fec6c>>
输出表明: method
的指向确实有误,此处本应为 View.method
。
super
是 python 内置方法,肯定不会出错。那,会不会是 super
的参数有误呢?
super
的签名为 super(cls, instance)
,宏观效果为 遍历 cls
的继承链查找父类方法,并以 instance
作为 self
进行调用。如今查找结果有误,说明 继承链是错误的,因而极有可能是 cls
出错。
因此,有必要探测一下 ChildView
的指向。在 method
中加上一句: print(ChildView)
:
<class '__main__.DecoratedView'>
原来,作用域中的 ChildView
已经被改变了。
一切都源于装饰器语法糖。我们回忆一下装饰器的等价语法:
@decorator
class Class:
pass
等价于
class Class:
pass
Class = decorator(Class)
这说明:装饰器会更改该作用域内被装饰名称的指向。
这本来没什么,但和 super
一起使用时却会出问题。通常情况下我们会将本类的名称传给 super
(在这里为 ChildView
),而本类名称和装饰器语法存在于同一作用域中,从而在装饰时被一同修改了(在本例中指向了子类 DecoratedView
),进而使 super(...).method
指向了 DecoratedView
的最近祖先也就是 ChildView
自身的 method
方法,导致递归调用。
找到了病因,就不难想到解决方法了。核心思路就是:不要更改被装饰名称的引用。
如果你只是想在内部使用装饰后的新类,可以在装饰器方法中使用 DecoratedView
,而在装饰器返回时 return cls
,以保持引用不变:
def register(cls):
decorated = type(
'DecoratedView',
(Mixin, cls),
{}
)
# Do something with decorated
return cls
这种方法的缺点是:从外部无法使用 ChildView.another_method
调用 Mixin
上的方法。可如果真的有这样的需求,可以采用另一个解决方案:
def register(cls):
cls.another_method = Mixin.another_method
return cls
即通过赋值的方式为 cls
添加 Mixin
上的新方法,缺点是较为繁琐。
两种方法各有利弊,要根据实际场景权衡使用。
- 本文为科普文
- 本文中的例子在 Ubuntu 14.04 / Python 2.7.11 下运行成功,Python 3+ 的接口有些许不同,需要读者自行转换
先看一段代码:
example.py
:
# -*- conding=yi -*-
从 math 导入 sin, pi
打印 'sin(pi) =', sin(pi)
这是什么?!是 Python 吗?可以运行吗?——想必你会问。
我可以明确告诉你:这不是 Python,但它可以用 Python 解释器运行。当然,如果你愿意,可以叫它“Yython” (易语言 + Python)。
怎么做到的?也许你已经注意到第一行的奇怪注释——没错,秘密全在这里。
这种黑魔法,还要从 PEP 263 说起。
- 本文默认读者对 Python 生成器 有一定的了解,不了解者请移步至生成器 - 廖雪峰的官方网站。
- 本文基于 Python 3.5.1,文中所有的例子都可在 Github 上获得。
学过 Python 的都知道,Python 里有一个很厉害的概念叫做 生成器(Generators)。一个生成器就像是一个微小的线程,可以随处暂停,也可以随时恢复执行,还可以和代码块外部进行数据交换。恰当使用生成器,可以极大地简化代码逻辑。
也许,你可以熟练地使用生成器完成一些看似不可能的任务,如“无穷斐波那契数列”,并引以为豪,认为所谓的生成器也不过如此——那我可要告诉你:这些都太小儿科了,下面我所要介绍的绝对会让你大开眼界。
生成器 可以实现 协程,你相信吗?
解析几何的巅峰 是 向量 那无关过程的狂妄与简洁 映射着大自然无与伦比的美
如何判断两条直线是否相交?
这很容易。平面直线,无非就是两种关系:相交 或 平行。因此,只需判断它们是否平行即可。而直线平行,等价于它们的斜率相等,只需分别计算出它们的斜率,即可做出判断。
但倘若我把“直线”换成“线段”呢——如何判断两条线段是否相交?
这就有些难度了。和 直线 不同,线段 是有固定长度的,即使它们所属的两条直线相交,这两条线段也不一定相交。
也许你会说:分情况讨论不就行了嘛:
的确,从理论上这是一个可行的办法,这也是人们手动计算时普遍采用的方法。
然而,这个方法并不怎么适用于计算机。原因如下:
那么,有更好的方法?
当然有。
这是源于两年前,当我在做人生中第一个真正意义上的网站时遇到的一个问题。该网站采用前后端分离的方式,由后端的 REST 接口返回 JSON 数据,再由前端渲染到页面上。
同许多初学 Javascript 的菜鸟一样,起初,我也是采用拼接字符串的形式,将 JSON 数据嵌入 HTML 中。开始时代码量较少,暂时还可以接受。但当页面结构复杂起来后,其弱点开始变得无法忍受起来:
+
和 "
。十分容易出错。<template>
标签。这是 HTML5 中新增的一个标签,标准极力推荐将 HTML 模板放入 <template>
标签中,使代码更简洁。为了解决这个问题,我暂时放下了手上的项目,花了半个小时实现一个极简易的字符串模板。