Windard +
Github Zhihu RSS

线上踩坑实录

迭代器只能遍历一次

迭代器

迭代器只能循环一遍,在循环之后迭代器为空。

正常情况下不会用到迭代器,用到的时候需要小心。 同样的列表生成式,tuple 生成的就是迭代器,list 生成的还是列表

>>> [i for i in [1,2,3]]
[1, 2, 3]
>>> {i for i in [1,2,3]}
{1, 2, 3}
>>> (i for i in [1,2,3])
<generator object <genexpr> at 0x105bfd150>

迭代器,转化成 list 之后就为空,经过一次遍历就成空

>>> a = (i for i in [1,2,3])
>>> list(a)
[1, 2, 3]
>>> list(a)
[]
>>>
>>>
>>> b = (i for i in [1,2,3])
>>> [_ for _ in b]
[1, 2, 3]
>>> [_ for _ in b]
[]

线上问题

所以在双重列表生成式中,需要注意

>>> c = (i for i in [1,2,3])
>>> [i for i in [5,4,3] if i not in c]
[5, 4, 3]

这里的时间复杂度是 O(m*n)

会对迭代器做 m 次循环,但实际上只有第一次循环的时候迭代器中有值,剩下的 m-1 次循环都是空

竟然是和 insgram 遇到一样的问题 Python向来以慢著称,为啥Instagram却唯独钟爱它?

整形类型转换

>>> int('12')
12
>>> int('12.0')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '12.0'
>>> int(float('12.0'))
12

int 整型不能强行转换浮点数

线程安全

一般在 python 中不会遇到线程安全问题,说明见过的世面还不够。

对于高并发的场景下,用类变量或者单例的实例变量,需要注意线程安全问题。

布尔运算

正数的布尔值为正,0 的布尔值为负,这很符合常识的吧,但是负数的布尔值也为正,😂。

>>> bool(1243)
True
>>> bool(45)
True
>>> bool(-45)
True
>>> bool(-0)
False
>>> bool(0)
False
>>> bool(-734251)
True

引用路径问题

有的时候,同一个对象的不同路径引用,其实是同一个对象。

In [140]: from marshmallow import missing as onemissing

In [141]: from marshmallow.utils import missing as twomissing

In [142]: onemissing
Out[142]: <marshmallow.missing>

In [143]: twomissing
Out[143]: <marshmallow.missing>

In [144]: onemissing is twomissing
Out[144]: True

但是有时候,同一个对象的不同路径引用方式,却是不同的对象。

这种情况一般发生在项目根目录和项目子目录都在 PYTHONPATH 中的时候,源自从项目根目录开始引用或者子目录开始引用的差异

In [161]: from cherry.error import CustomException as ce

In [162]: from error import CustomException as ee

In [163]: ce
Out[163]: cherry.error.CustomException

In [164]: ee
Out[164]: error.CustomException

In [165]: ce is ee
Out[165]: False

In [166]: ce == ee
Out[166]: False

这里 error.py 是在 cherry 目录下,不过 cherry 的根目录和 cherry 子目录都在 PYTHONPATH 中。

error.py 文件内容。

# -*- coding: utf-8 -*-
from socket import timeout


class CustomException(Exception):
    pass


但是从 error 中引用 timeout 其实都是 socket.timout ,即为同一个对象。

In [1]: import sys

In [2]: sys.path.insert(0, '/xx/yy/cherry')

In [3]: sys.path.insert(0, '/xx/yy')

In [4]: from cherry.error import timeout

In [5]: from cherry.error import timeout as ct

In [6]: from error import timeout as et

In [7]: ct
Out[7]: socket.timeout

In [9]: et
Out[9]: socket.timeout

In [10]: ct is et
Out[10]: True

这里其实使用 type 看一下对象类型就能发现差异或者使用 id 看下内存地址的差异,有的时候从不同路径引入的其实是同一个对象,但是有的时候是不同对象。

In [4]: from marshmallow import missing as onemissing

In [5]: from marshmallow.utils import missing as twomissing

In [6]: type(onemissing)
Out[6]: marshmallow.utils._Missing

In [7]: type(twomissing)
Out[7]: marshmallow.utils._Missing

In [8]: from cherry.error import CustomException as ce

In [9]: from error import CustomException as ee

In [10]: type(ce)
Out[10]: type

In [11]: type(ee)
Out[11]: type

In [12]: ce
Out[12]: cherry.error.CustomException

In [13]: ee
Out[13]: error.CustomException

In [14]: id(onemissing)
Out[14]: 140658795796368

In [15]: id(twomissing)
Out[15]: 140658795796368

In [16]: id(ce)
Out[16]: 140658767337664

In [17]: id(ee)
Out[17]: 140658759614048

序列化

非常低级的错误,json 不能序列化 set 集合,当多线程或其他无法及时查看到报错日志的时候,很难发现集合竟然不能被序列化。

>>> data = {"/asdsf"}
>>> import json
>>> json.dumps(data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/bytedance/miniconda/envs/byted/lib/python2.7/json/__init__.py", line 244, in dumps
    return _default_encoder.encode(obj)
  File "/Users/bytedance/miniconda/envs/byted/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/bytedance/miniconda/envs/byted/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/Users/bytedance/miniconda/envs/byted/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: set(['/asdsf']) is not JSON serializable

内存泄露

一般来说,python 作为高级语言是有垃圾回收机制的,会自动做垃圾回收。但是如果出现内存泄露的情况,有如下几种可能的情况:

  1. 对象一直被全局变量所引用, 全局变量生命周期长.比如类变量上的 dict 类型,一直在扩增。
  2. 垃圾回收机被禁用或者设置成debug状态, 垃圾回收的内存不会被释放.
  3. 用户不自定类的__del__方法, gc可以回收带有自引用的对象, 但是你自己实现了__del__方法就不行了。即有 __del__ 方法的类自引用。

数据库分页问题

数据库分页获取,根据更新时间排序获取的两个请求,一次 ORDER BY updated_at desc LIMIT 20, 一次 ORDER BY updated_at desc LIMIT 20 OFFSET 20,取到的数据有重复。

因为多条数据中更新时间 updated_at 一致,所以在返回数据里,根据更新时间倒排,最后都会取到前几条记录。

如果需要分页获取到全量数据,最好根据无重复数据的字段进行排序获取,才能够保证每次分页的数据不重复。

参考链接

Does Python GC deal with reference-cycles like this?


纯享阅读~ Click me
headlogo   Windard

但行好事,莫问前程

Blog

Opinion

Project

页阅读量:  ・  站访问量:  ・  站访客数: