最近在看Django文档Making queries时看到descriptor, 发现描述器很强大.

Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super(). They are used used throughout Python itself to implement the new style classes introduced in version 2.2. Descriptors simplify the underlying C-code and offer a flexible set of new tools for everyday Python programs.

但是我还是不懂为什么需要描述符,直到看了Python描述符(descriptor)解密, 才知道描述符的用途。按照视频中的讲解,在理解一遍,加深印象。

假设在做一个农产品销售系统,每个订单是一个产品,每个产品有description, weight, price三个字段

1
2
3
4
5
6
7
8
9
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price

但这里存在一个问题,即weight可以为负的。

1
2
3
4
5
6
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.subtotal()
69.5
>>> raisins.weight = -20 #负值
>>> raisins.subtotal()
-139.0

这是一个严重的问题,亚马逊刚起步时就有这个问题。 在Jeff Bezos and Amazon: Birth of a Salesman里有描述。

传统的做法是添加getter和setter方法

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
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.set_weight(weight)
self.price = price

def subtotal(self):
return self.get_weight() * self.price

def get_weight(self):
return self.__weight

def set_weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
raisins.weight
print raisins.subtotal()

这种方法存在一些问题,

  • 之前可以使用raisins.weight, 现在不能使用
  • 和以前的代码不兼容,以前可以使用raisins.weight, 现在必须raisins.get_weight和set_weight

好在Python提供更好的解决办法, property是其中一种

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
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price

@property
def weight(self):
return self.__weight

@weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
print raisins.weight
print raisins.subtotal()
raisins.weight = -2.0

使用property存在一个问题是,当我们需要对price也做非0限制时,需要重复setter设置。此时Descriptor派上用场了。

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
33
34
35
36
37
class Quantity(object):
__counter = 0
def __init__(self):
prefix = '_' + self.__class__.__name__
key = self.__class__.__counter
self.target_name = '%s_%s' % (prefix, key)
self.__class__.__counter += 1

def __get__(self, instance, owner):
return getattr(instance, self.target_name)

def __set__(self, instance, value):
if value > 0:
setattr(instance, self.target_name, value)
else:
raise ValueError('value must be > 0')

class LineItem(object):
weight = Quantity()
price = Quantity()

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
print raisins.weight
print raisins.subtotal()
raisins.weight = -2.0
raisins.price = -1

在Quantity类里, instance是指LineItem实例, owner指LineItem类。

具体查看视频,非常不错。

参考资料

联系作者

因为服务器上HTTP和HTTPS一起存在,所以分享接口这里出了一些问题。

在调用分享接口生成签名时,签名用的url必须是调用JS接口页面的完整URL,但因为存在http和https两种,而Django其实不知道客户端到底是访问http还是https,所以产生了问题。如果写死http, 则访问https时,微信分享签名出错;相反的,如果写死https, 则访问https时,微信分享签名会出错。有什么解决的办法?

查看request对象,知道scheme这个属性,于是看到SECURE_PROXY_SSL_HEADER设置。

大意就是当在settings里配置了SECURE_PROXY_SSL_HEADER,Django就会到request.META里读取相关参数,如果有设置https,则这是一个https请求。而相关参数需要代理服务器设置,我这里使用Nginx。

于是在Nginx配置里,当访问的是https时,就加上

1
proxy_set_header HTTP_X_FORWARDED_PROTO https;

然后在Django的settings配置里加上

1
SECURE_PROXY_SSL_HEADER = ('HTTP_HTTP_X_FORWARDED_PROTO', 'https')

这里之所以会多一个HTTP_是因为Django默认会给request.ME如此配置后,request.scheme就会返回http, 当请求是https时,则会返回https。分享的签名正确,问题得到解决。

联系作者

前几天同事推荐了djano-extensions,说是用来执行一些Django脚本特别爽,尝试之后,果然如此。

RunScript里有编写脚步的方法,有了它,之后写脚本时不需要导入Django环境,因为django-extensions帮你做了,很不错。

1
2
3
4
5
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
django.setup()

联系作者

在PDF中需要添加签名信息,在网上找到
JSignPdf, 虽然两年多没更新了,但至少还能工作。

查看文档,发现美中不足的一点是只能一页一页的签名。

需要注意的一点是,图片必须是透明的,要不能签到PDF上会覆盖文字。

尝试之后,一个可行的签名如下

1
java -jar JSignPdf.jar -kp 111111 -ksf temp.pfx -llx 100 -lly 100 -urx 200 -ury 200 --bg-path Wechat2.png --l2-text '' -V 007_overview.pdf

参数的具体意义可以看文档。

联系作者

最近需要将Word文档转为PDF,正好在阿里云的同学做过,于是请教他,他给了一些参考资料,解决了转换的问题。

其中Jacob因为只能用于Windows平台,所以没有尝试。尝试了JODConverter之后,可以转换,效果还不错,暂定使用它。

只是还是存在一点乱码问题,之后再看看怎么解决。

代码博主已经开源,我把它放在了Github上,加上命令行输入,可以见这里,主要使用Apache的common-cli这个库。

之后是在Intellij IDEA导出jar包,参考
Idea 导出 jar包即可。主要步骤如下

1
2
3
4
5
6
7
8
9
10
11
(1)File→Project Structure...→Artifacts→+→jar→From modules with .... → 选择一个要执行的main方法

(2)extract to jar

(3)选择manifest的位置:d:\idea\myproject\src

(4)勾选build on make

(5)build -- make project, (如不行,在此之前,执行下mvn clean)

(6)D:\idea\myproject\out\artifacts\ 寻找jar

参考资料

联系作者

之前同事做微信公众平台的扫描带参数二维码事件时老是提示”该公众号暂时无法提供服务,请稍后再试”, 当时没有得到解决,现在好好看了文档,尝试之后,终于得到解决,记录下来。

被动回复用户消息里看到如下一段话

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:

1、(推荐方式)直接回复success

2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)

一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:

1、开发者在5秒内未回复任何内容

2、开发者回复了异常数据,比如JSON数据等

1
2

看到这里推荐返回success, 而公司后台的api使用Django-REST-Framework,于是简单返回`return Response("success")`, 但还是一直报错,后来发现代码里写着

def get_renderers(self):
    if self.request.method == 'GET':
        return [TextRenderer()]
    return [XMLRenderer()]
1
于是将它注释掉试试,发现还是不行,默认情况下返回的使JSON字符串,于是将这个函数改为
def get_renderers(self):
    return [TextRenderer()]

```
错误消失了,问题解决。这里使用TextRenderer后,返回的格式是text/plain。

另外还发现一个问题, 生成带参数的二维码一共有两个文档, 文档1文档2, 一个有效期最长可以设置为在二维码生成后的7天,一个有效期最长可以设置为在二维码生成后的30天,真是蛋疼。

联系作者

最近从公司APP分享出去的一篇文章,访问量暴增,而访问计数直接访问的数据库,数据库产生行级锁,许多访问超时。

1
News.objects.filter(pk=self.object.id).update(view_num=F('view_num') + 1)

临时把访问计数去掉。之后找到Redis计数器。在访问文章时,使用incr自增。只是在访问文章列表时,如果对每篇文章都要读一次Redis会影响性能,于是只好做了延时处理,每个一段时间同步到数据库,并进行清零,等想到更好的解决办法再说。

清零时,最好不要使用set key 0这种用法,在多线程情况下会出问题,目前使用incrby key -view_num这种方式, 也就是减去目前Redis中浏览量的方式。

Redis确实是好东西,需要深入学习。

参考资料

联系作者