backref和back_populates在表示两个表之间的关系时,很有用。

查看backref的文档,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User(db.Model):
__tablename__ = 'users'
id = db.Column(Integer, primary_key=True)
name = db.Column(String(10))
addresses = relationship("Address", backref="user")

def __repr__(self):
return u'<user id={0}, name={1}>'.format(self.id, self.name).encode('utf-8')


class Address(db.Model):
__tablename__ = 'addresses'
id = db.Column(Integer, primary_key=True)
email = db.Column(String(64))
user_id = db.Column(Integer, ForeignKey('users.id'))

def __repr__(self):
return '<address id={0}, email={1} user_id={2}>'.format(self.id, self.email, self.user_id)

之后在命令行中,可以得到如下结果

1
2
3
4
5
6
7
8
9
10
11
12
>>> u = User(name='Bob')
>>> db.session.add(u)
>>> db.session.commit()
>>> u.addresses
[]
>>> a = Address(email='bob@163.com', user_id=u.id)
>>> db.session.add(a)
>>> db.session.commit()
>>> u.addresses
[<address id=2, email=bob@163.com user_id=2>]
>>> a.user
<user id=2, name=Bob>

即通过u.addresses可以访问到用户的addresses, 而a.user可以访问到用户。注意到u.addresses返回的是列表,而a.user返回的是单个元素,即User与Address是一对多的关系。

也可以使用back_populates实现相同的功能,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class User(db.Model):
__tablename__ = 'users'
id = db.Column(Integer, primary_key=True)
name = db.Column(String(10))
addresses = relationship("Address", back_populates = "user")

def __repr__(self):
return u'<user id={0}, name={1}>'.format(self.id, self.name).encode('utf-8')


class Address(db.Model):
__tablename__ = 'addresses'
id = db.Column(Integer, primary_key=True)
email = db.Column(String(64))
user_id = db.Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="addresses")

def __repr__(self):
return '<address id={0}, email={1} user_id={2}>'.format(self.id, self.email, self.user_id)

在交互环境中,得到如下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> u = User(name='Clack')
>>> db.session.add(u)
>>> db.session.commit()
>>> u.addresses
[]
>>> a = Address(emial='Clack@163.com', user_id=u.id)
>>> a = Address(email='Clack@163.com', user_id=u.id)
>>> db.session.add(a)
>>> db.session.commit()
>>> u.addresses
[<address id=3, email=Clack@163.com user_id=3>]
>>> a.user
<user id=3, name=Clack>

从文档中得知,back_populates是用来取代backref的,虽然backref也是一直支持使用。倾向于使用back_populates, 因为它比backref更直接明了。

联系作者

最近做前端开发,发现Chrome开发者工具真是前端开发的一大利器。这里做个简单记录。

Chrome在Mac上, 可以使用快捷键Option + Command + I打开开发者工具。查看源码是Option + Command + U。

Network标签下,有一个XHR请求,XHR代表XMLHttpRequest,所以简单来说,就是AJAX请求。

Chrome的一大功能是可以修改Javascript文件中的代码,保存后就可以生效。这是一个非常有用的功能,在表单提交时,如果Javascript出错,一般来说是去修改源文件,然后重新加载,然后填入表单字段,重新提交。这种方式,需要反复填入表单,如果表单字段很多,则非常麻烦。有了Chrome这个功能后,直接修改source标签下的Javascript文件,保存后即可生效,然后就可以提交,这样非常方便。

需要注意的是,修改source下的js文件,并不会修改源代码,调试完成后,还需要将修改同步到源文件中。

参考资料

联系作者

日志是程序员的生命线,在Python中使用logging来记录日志。之前写过Django配置logging, 这次在Flask里配置日志。查看Flask日志配置文档, 最后在一个日志等级上遇到问题。

给创建app时,添加日志配置

1
2
3
4
5
6
7
file_handler = RotatingFileHandler("lovehate.log")
file_handler.setLevel(logging.WARNING)
file_handler.setFormatter(Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'
))
app.logger.addHandler(file_handler)

之后使用app.logger.info输出日志,日志只在终端中显示,没有写到文件中。百思不得其解,叫同事过来看看,才发现是日志等级的原因。

以前一直认为INFO的等级比WARNING更高,所以一直认为上面的配置,应该会输出日志。后来看了文档才知道日志等级从高到低是DEBUG, INFO, WARNING, ERROR, CRITICAL。或许这还是不合理的,WARNING应该比INFO低才对啊。

联系作者

Zabbix作为一款老牌监控,在服务器规模还不是很大时很有用。

安装Zabbix有很多种方式,这里使用rpm方式安装。因为服务器是CentOS 32位的,所以选择下面的rpm包,相应的包可以在阿里云上找到。rpm -ivh http://mirrors.aliyun.com/zabbix/zabbix/3.0/rhel/6/i386/zabbix-release-3.0-1.el6.noarch.rpm, 具体安装见参考资料。

参考资料

联系作者

可迭代的和迭代器是两个经常容易混淆的概念,这里说说这两个概念

iterable可迭代的">iterable可迭代的

任何调用iter函数可以获得一个迭代器的都是可迭代的。实现__iter__方法,返回一个迭代器的对象是可迭代的;序列是可迭代的,因为它们实现了__getitem__方法。

iter函数执行时会做如下操作

  1. 检查一个对象是否实现了__iter__, 从其中获得一个迭代器
  2. 如果没有实现__iter__方法,查看是否实现__getitem__方法,从中创建一个迭代器,从0开始获取每一个元素。正是因为这一点,序列是可迭代的
  3. 如果都失败,抛出异常,对象是不可迭代的。

当需要自己实现的迭代对象时,需要实现__iter__方法,因为从__getitem__中创建迭代器是为了向后兼容,未来也许会被废弃。

iterator迭代器">iterator迭代器

任何实现无参next方法,每次返回一个元素,当没有更多元素时,返回StopIteration异常的对象,都是迭代器。迭代器也实现了iter方法,所以也是可迭代的。也就是迭代器提供了访问可迭代对象的接口,用户不需要知道其内部实现。

把可迭代对象变成一个迭代器是一个坏主意

迭代器模式的用途主要是

  1. 访问一个聚合对象的内容而无需暴露它的内部表示;
  2. 支持多次遍历聚合对象
  3. 为遍历不同的聚合结构提供一个统一的接口,即支持多态迭代。

为了支持多次遍历,调用iter函数时,需要返回一个全新的迭代器。如果可迭代器对象变成一个迭代器,那么它将返回原先的迭代器,它将只支持一次遍历。

联系作者

在《Fluent Python》里看到Why len is not method,有些困惑,于是开始找答案。

在Python词汇表里,对函数和方法有如下说明
function: A series of statements which returns some value to a caller. It can also be passed zero or more arguments which may be used in the execution of the body.

method: A function which is defined inside a class body. If called as an attribute of an instance of that class, the method will get the instance object as its first argument (which is usually called self).

简单来说,就是函数在类里定义就叫方法。所以这里len是一个函数,而不是方法。那么问题是,为什么不把len做成方法,像list.len() str.len()这样呢?

What makes Python so AWESOME里《Fluent Python》的作者Luciano Ramalho向Raymond Hettinger提了这个问题,Raymond Hettinger说是因为”practicality beats purity”, 因为把len做成一个函数更加直观和符合直觉。

实际实现时,len函数对于内置类型,会去C底层实现的结构体里取一个字段值,这个字段值记录了长度。而对于用户自定义类型,用户实现len方法的话,len函数会去调用len方法。

联系作者

因为新公司要使用Flask开发,于是需要熟悉它。找到《Flask Web Developmen》这本书,于是开始学习使用Flask编写Web站点。

想到把之前用Django开发的站点,爱与生的苦恼还不够完善,于是决定用Flask重写。之所以叫爱与生的苦恼,是因为之前看过叔本华写的《爱与生的苦恼》。想想每个人很多时候都是在爱与恨中度过,就像我喜欢跑步,囚徒健身,现代牧业,旭辉控股,莱尔斯丹等等,讨厌工商银行,赌博,垄断,Windows命令行等等。于是我想把它记下来,也可以看看自己喜欢与讨厌的变化。或许随着时间的推移,自己会变得更加宽容,更加开放。也许那个时候,不在讨厌工商银行,Windows命令行也是有可能的。记微信朋友圈显得有些杂乱,于是干脆记在自己写的网站里。

《Flask Web Development》的作者Miguel Grinberg显然具有多年开发经验,将Web开发需要注意的地方,通过一个完整的博客应用娓娓道来,令人倾佩。许多代码稍作更改,就可以用在自己的站点中,大呼过瘾。如果要学习Flask开发,极力推荐看看这本书。

相比于Django, Flask显得更加轻量。没有了Django的admin,但好在应用比较简单,写个简单的后台没有什么问题。基本上Django里有的功能,Flask都能找到合适替代品。整个开发过程还是很顺畅的,唯一不好的一点是Flask的migrate功能有问题,当使用SQLite做数据库时,自动生成的SQL语句会出问题,主要是SQLite不支持Alter字段。

刚开始时,访问速度很慢,因为显示的都是原始图片,动不动就是10M,用户体验不是很好。曾考虑过使用七牛图床,只是这样增加了开发成本,而且需要把照片寄存在第三方应用中,好处是图片清晰。考虑之后,还是决定使用Pillow模块进行图片裁剪,目前来看,效果还不错。

之后添加了粉丝,评论功能这些社交元素。除了界面显示效果略差,整体效果还算满意。

站点地址可移步爱与生的苦恼, 欢迎在此记录爱与生的苦恼。

联系作者

这是一道关于元组+=赋值的谜题, 据说,能答对这道题的都是Python老司机。 请在不执行下面的代码的情况下,回答这个问题

1
2
3
4
5
6
7
>>> t = (1,2,[30,40])
>>> t[2] += [50, 60]
会发生什么, 选择最佳答案:
a) t变成(1, 2, [30, 40, 50, 60])
b 抛出TypeError, 'tuple' object does not support item assignment异常,即元祖对象不支持赋值
c) 上面两种情况都没有发生.
d) a 和 b都发生

看到这题时,第一反应是b。之后使用dis模块查看执行语句, 得到

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> dis.dis("t[2] += [50, 60]")
1 0 LOAD_NAME 0 (t)
3 LOAD_CONST 0 (2)
6 DUP_TOP_TWO
7 BINARY_SUBSCR
8 LOAD_CONST 1 (50)
11 LOAD_CONST 2 (60)
14 BUILD_LIST 2
17 INPLACE_ADD
18 ROT_THREE
19 STORE_SUBSCR
20 LOAD_CONST 3 (None)
23 RETURN_VALUE

可以看到第17条的INPLACE_ADD操作,因为列表是可变对象,所以这条会执行成功。之后第19条进行保存,而元祖是不可变对象,所以第19条会报错。

所以答案是d, 即a和b都发生了。

联系作者

Python词汇表里对,可哈希的有这样一段说明:

An object is hashable if it has a hash value which never changes during its lifetime (it needs a hash() method), and can be compared to other objects (it needs an eq() method). Hashable objects which compare equal must have the same hash value.

也就是说,一个对象的哈希值(实现hash方法)在对象的生命周期里永远不变的,这个对象可以与其它对象进行比较(实现eq方法), 相等的对象哈希值一定相等,那么这个对象就是可哈希的。

也就是说,可哈希对象必须满足下面三个条件

  1. 支持hash函数,也就是实现hash方法。这个方法的返回值在对象的生命周期里永远不变
  2. 支持相等判断,也就是实现eq方法
  3. 如果a == b, 那么hash(a)一定等于hash(b),也就是相等的对象,哈希值一定相等。

这里不明白的是第三点,也就是为什么两个对象相等,哈希值一定要相等,不相等难道不行吗?

下面编写的一个测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UnHashable(object):
"""
>>> a = UnHashable(3)
>>> b = UnHashable(3)
>>> a == b
True
"""

def __init__(self, x):
self.x = x

def __eq__(self, other):
return self.x == other.x

def __hash__(self):
import random
return hash("{} {}".format(self.x, random.random()))

如果我们执行

1
2
3
4
d = dict()
a = UnHashable(3)
b = UnHashable(3)
d[a] = 4

之后我们会希望d[b]返回4,但是并不会如此,因为hash[a]并不一定等于hash[b]. 这就是为什么要求可哈希对象一定要a == b时,hash(a) == hash(b)

在Python中,对于内置不可变对象,都是可哈希的,而字典和列表等可变对象不是可哈希的。

联系作者

什么是装饰器

在《Fluent Python》里说, A decorator is a callable that takes another function as argument。也就是装饰器就是一个可执行对象,它的参数是一个函数。

1
2
3
@decorate
def target():
print('running target()')

相当于

1
2
3
def target():
print('running target()')
target = decorate(target)

另一个重要的问题是当模块导入时,装饰器就执行了。

编写registration.py

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
32
registry = []


def register(func):
print('running register(%s)' % func)
registry.append(func)
return func


@register
def f1():
print('running f1()')


@register
def f2():
print('running f2()')


def f3():
print('running f3()')


def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()

if __name__ == '__main__':
main()

之后导入模块,可以看到registration.registry的值

1
2
3
4
5
>>> import registration
running register(<function f1 at 0x10063b1e0>)
running register(<function f2 at 0x10063b268>)
>>> registration.registry
[<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]

编写一个装饰器

下面编写一个记录函数执行时间的装饰器,保存到clockdeco.py中

1
2
3
4
5
6
7
8
9
10
11
import time
def clock(func):
def clocked(*args): #
t0 = time.perf_counter()
result = func(*args) #
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked

之后使用这个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
from clockdeco import clock
@clock
def snooze(seconds):
time.sleep(seconds)

@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)

if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))

可以看到结果

1
2
3
4
5
6
7
**************************************** Calling snooze(123) [0.12405610s] snooze(.123) -> None **************************************** Calling factorial(6) [0.00000191s] factorial(1) -> 1
[0.00004911s] factorial(2) -> 2
[0.00008488s] factorial(3) -> 6
[0.00013208s] factorial(4) -> 24
[0.00019193s] factorial(5) -> 120
[0.00026107s] factorial(6) -> 720
6!=720

使用内置装饰器

上面编写的clock装饰器存在一个问题是不支持关键字参数,可以使用标准库里的functools.wraps来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
return result
return clocked

Python内置了三个常用的装饰器,property,classmethod和staticmethod,
标准库functools里还有两个有趣的装饰器lru_cache和singledispatch。

编写带参数的装饰器

要编写带参数的装饰器,一个解决的办法是编写一个装饰器工厂,然后根据参数不同,返回不同的装饰器。

拿上面的clock装饰器来说,如果要允许传入时间格式来输出不同的日期格式,可以如下编写装饰器工厂, 保存到clockdeco_param.py中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT):
def decorate(func):
def clocked(*_args):
t0 = time.time()
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
print(fmt.format(**locals()))
return _result
return clocked
return decorate

编写如下测试代码,可以看到不同的时间格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
from clockdeco_param import clock
@clock()
def snooze(seconds):
time.sleep(seconds)

for i in range(3):
snooze(.123)


@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
time.sleep(seconds)


for i in range(3):
snooze(.123)

输出结果如下

1
2
3
4
5
6
[0.12581110s] snooze(0.123) -> None
[0.12463617s] snooze(0.123) -> None
[0.12825012s] snooze(0.123) -> None
snooze(0.123) dt=0.127s
snooze(0.123) dt=0.126s
snooze(0.123) dt=0.126s

联系作者