Flask使用Blinker来处理信号订阅和发送, 查看文档Blinker看看简单的使用方法。

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
from blinker import signal

def subscriber(sender):
print("Got a signal sent by %r" % sender)


class Processor:
def __init__(self, name):
self.name = name

def go(self):
ready = signal('ready')
temp = ready.send(self)
print(temp)
print("Processing.")
complete = signal('complete')
complete.send(self)

def __repr__(self):
return '<Processor %s>' % self.name

ready = signal('ready')
ready.connect(subscriber)
temp = ready.send('sdfsdf')
print(temp)
processor_a = Processor('a')
processor_a.go()

使用signal创建一个信号, connect添加一个订阅,send发送信号,send的返回值是一个数组,数组元素是元祖,包含信号接收者和信号接收后的返回结果。

联系作者

JQuery的事件代理主要用来解决通过js添加的HTML元素无法监听事件的问题。

JQuery event delegation中给了如下一个简单例子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
<div id="container">
<ul id="list">
<li><a href="http://domain1.com">Item #1</a></li>
<li><a href="/local/path/1">Item #2</a></li>
<li><a href="/local/path/2">Item #3</a></li>
<li><a href="http://domain4.com">Item #4</a></li>
</ul>
</div>
<script>
$( "#list a" ).on( "click", function( event ) {
event.preventDefault();
console.log( $( this ).text() );
});
$( "#list" ).append( "<li><a href='http://newdomain.com'>Item #5</a></li>" );
</script>

</body>
</html>

点击item1到item4,对于click事件都会有响应,在console中可以看到输出,而点击item5时click事件不会响应,这是因为将click事件绑定时,item5还没有生成。

解决这个问题的方法是使用事件代理,原理是利用了事件传播机制。也就是在某一个元素中的事件,如果没有得到处理,一直会向父节点触发,直到根节点。如上面的a元素的click事件,如果没有被捕获,一直会向上触发,路径如下

  • <a>
  • <li>
  • <ul #list>
  • <div #container>
  • <body>
  • <html>
  • document root

<ul #list>元素上添加click事件,就可以看到效果。

1
2
3
4
$( "#list" ).on( "click", function( event ) {
event.preventDefault();
console.log( $( this ).text() );
});

最后,如果使用事件代理,我们可以把它绑定到<ul #list>元素,然后通过’a’去找到<a>元素,这样就可以监听通过js添加的HTML元素。

1
2
3
4
$( "#list" ).on( "click",  'a', function( event ) {
event.preventDefault();
console.log( $( this ).text() );
});

联系作者

时间日期选择器可以使用bootstrap-datetimepicker, 中文文档见Bootstrap日期和时间表单组件

使用时有一些注意点,其中一点是不要使用datetimepicker class, 会出bug,主要现象是两个时间框叠在一起。

对于不同的时间输入框,要独立初始化,如下面两个输入框

1
2
3
4
5
6
7
8
9
10
11
12
<div class="row">
<label for="begin-time" class="col-sm-2 control-label">开始时间</label>
<div class="col-sm-4">
<input id="begin-time" readonly type="text" class="form-control" data-date-format="yyyy-mm-dd hh:ii:ss">
</div>
</div>
<div class="row">
<label for="end-time" class="col-sm-2 control-label">结束时间</label>
<div class="col-sm-4">
<input id="end-time" readonly type="text" class="form-control" data-date-format="yyyy-mm-dd hh:ii:ss">
</div>
</div>

要像如下独立初始化

1
2
3
4
5
6
$('#begin-time').datetimepicker({
language: 'zh-CN'
});
$('#end-time').datetimepicker({
language: 'zh-CN'
});

对于中文,还需要加载ootstrap-datetimepicker.zh-CN.js

联系作者

需要给每个user添加一个leader, 于是在user表里添加leader, 而leader也是一个user, 于是构成了自引用。

1
2
3
4
5
6
7
8
9
10
11
class User(db.Model):
__tablename__ = 'users'
id = db.Column(Integer, primary_key=True)
name = db.Column(String(10))
addresses = relationship("Address", back_populates="user")
leader_id = db.Column(Integer, ForeignKey('users.id'))
users = relationship("User", back_populates="leader")
leader = relationship("User", back_populates="users", remote_side=[id])

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

提示如下错误,

ArgumentError: User.users and back-reference User.leader are both of the same direction symbol(‘MANYTOONE’). Did you mean to set remote_side on the many-to-one side ?

最后参考StackOverFlow上One to one self relationship in SQLAlchemySQLAlchemy One-to-Many relationship on single table inheritance - declarative
添加remote_side解决问题.

联系作者

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方法。

联系作者