记录python开发时遇到的一些令人眼前一亮的坑!
python 对象重用 下面代码目的是构造2个 提供不同默认参数的 testfn
函数。正常的思路,如下。。
1 2 3 4 5 6 7 8 9 10 11 12 watch_list = { 'a' : 1 , 'b' : 2 , } def testfn (name, data) : print(name, data) for i in watch_list.keys(): fn = functools.partial(testfn, name=i, data=watch_list[i]) print(fn, lambda : testfn(i, watch_list[i]))
理想中的结果应该print出来2个函数是不同的…
然而…
1 2 (<functools.partial object at 0x10df9e208>, <function <lambda> at 0x10dfcf140>) (<functools.partial object at 0x10dfd1310>, <function <lambda> at 0x10dfcf140>)
lambda 出来的函数竟然是一样的!!!
原因是lambda 里面存的是引用,也就是指针,这又牵扯python的一个设定,他会重用对象。
1 2 3 4 5 6 7 8 In [10 ]: id('a' ) Out[10 ]: 4328343392 In [11 ]: id('a' ) Out[11 ]: 4328343392 In [12 ]: id('a' ) Out[12 ]: 4328343392
于是上面的 lambda 对象也被重用了,于是发生这种结果。
sof的解决方法 详
加一层工厂,这样工厂里面的入参引用必定不同
使用 functools.partial 建新函数
或者 循环创建函数的同时直接 消费或激活函数… (如果可以的话)
1 2 3 4 for i in watch_list.keys(): fn = functools.partial(testfn, name=i, data=watch_list[i]) fn() print(fn, lambda : testfn(i, watch_list[i]))
用 dis 看python bytecode 比如
1 2 3 4 a = 0 b = 0 a = a + 1 b += 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 0 LOAD_CONST 0 (0) 3 STORE_NAME 0 (a) 2 6 LOAD_CONST 0 (0) 9 STORE_NAME 1 (b) 3 12 LOAD_NAME 0 (a) 15 LOAD_CONST 1 (1) 18 BINARY_ADD 19 STORE_NAME 0 (a) 4 22 LOAD_NAME 1 (b) 25 LOAD_CONST 1 (1) 28 INPLACE_ADD 29 STORE_NAME 1 (b) 32 LOAD_CONST 2 (None) 35 RETURN_VALUE
所以 a的写法更好些。。。
文件上传 用 bottles 做文件上传很方便
最近想做一个功能,想把上到的文件内容加密,然后返回回去,为了更快,所以希望不落地,也就是省去保存到文件的过程,内存读,改,write back
写了个demo, 将上传的内容翻倍后返回。
1 2 3 4 5 6 7 @route('/upload', method='POST') def upload () : uf = request.files.get('tobe_encrpty' ) print(uf.file) print(type(uf.file)) return uf.file.getvalue() * 2
然后你会惊讶的发现,对于 1000 byte 以内
的文件上面的代码是ok的,超过 1000 byte
的文件就会报错,因为对于 超过 1000 byte 的文件 uf.file 的类型不是 cStringIO.StringO
代码调用链
(bottles.py) BaseRequest.POST -> data = cgi.FieldStorage(**args)
(cgi.py) FieldStorage.__write(self, line) -> 下面
1 2 3 4 5 6 7 8 9 10 11 def __write (self, line) : if self.__file is not None : if self.__file.tell() + len(line) > 1000 : self.file = self.make_file('' ) self.file.write(self.__file.getvalue()) self.__file = None self.file.write(line) def make_file (self, binary=None) : import tempfile return tempfile.TemporaryFile("w+b" )
于是
正确的做法是, 利用 StringIO (有和file相同的api) 再利用 bottle 的private api _copy_file
做一次内存拷贝
1 2 3 4 5 6 7 8 9 10 11 12 @route('/upload', method='POST') def upload () : uf = request.files.get('tobe_encrpty' ) output = cStringIO.StringIO() uf._copy_file(output) file_content = output.getvalue() response.set_header('Content-type' , 'application/octet-stream' ); response.set_header('Content-Disposition' , 'attachment; filename=patchencrypted.data' ); return file_content*2
为什么 1000, 这个1000 意义何在,看这个 issue
貌似是 2001 年的时候认为 1000 就是很大了 。。。 wtf