
首先定义一个类,作用是获取代理
class Proxies: def __init__(self): self.bin_file = Path(__name__).resolve().parent / 'deque.bin' if self.bin_file.exists(): with open(self.bin_file.as_posix(), 'rb') as fpr: self.qproxy = pickle.load(fpr) print('deque loaded') else: self.qproxy = deque() print('deque created') self.get_ip = "API" self.get_proxies() def __del__(self): with open(self.bin_file.as_posix(), 'wb') as fpw: pickle.dump(self.qproxy, fpw) print('deque dumped') def get_proxies(self): logging.info("Getting proxies from API.") with HTMLSession() as ip_session: with ip_session.get(self.get_ip) as resp_ip: resp_ip2 = resp_ip.text.split('\r\n') proxies_http = [{"http": "http://{}".format(i), "cd": 3 } for i in resp_ip2 if i is not None] proxies_https = [{"https": "https://{}".format(i), "cd": 3 } for i in resp_ip2 if i is not None] [self.put(p) for p in proxies_http] [self.put(ps) for ps in proxies_https] logging.info("Proxies get.") def get(self): if len(self.qproxy) <= 1: self.get_proxies() return self.qproxy.popleft() def put(self, p): self.qproxy.append(p) return None @property def size(self): return len(self.qproxy) 在此之前我已经创建一个实例 p = Proxies() 然后打印出了 deque created 没说明 bin 文件已经被创建,创建后实例获取了 40 个代理 然后执行 del p 然而此时 deque dumped 没有被打印出来,也就是析构函数没有被执行
然后从新创建一个实例 p = Proxies() 输出 deque loaded deque dumped
deque dumped 在此时打印了出来,这是为什么? 此时 p.size 为 40,这是正确的
然后手动获取代理 p.get_proxies() 输出 deque dumped 此时为什么又输出了析构函数中的 print ?
p.size 输出 80,这个也是正确的
del p 输出 deque dumped 此时析构函数又正常执行了。。。。
python 版本 3.7
谁能给分析分析是为什么吗
1 owenliang 2018-10-30 12:55:45 +08:00 描述没怎么看懂,你贴一段调用的完整复现代码看看? |
2 no1xsyzy 2018-10-30 13:11:18 +08:00 你是在 REPL 里执行后面这些的吗? REPL 会把最后一次非 None 返回值保存到 `_` 上。 |
3 kindjeff 2018-10-30 13:14:35 +08:00 via iPhone 垃圾回收的时候才会触发__del__,换句话说,你没法保证什么时候__del__被触发,所以不要把有用的逻辑写在__del__里。 |
4 princelai OP @no1xsyzy 是的,在 ipython 里,我看流畅的 python 确实说过_为 None 的时候才会执行析构,但是之后也是同样的操作,也没有输出 None,为什么又正常了?而且我在第二次执行 p.get_proxies() 时,这里也出现了一次执行析构函数 |
6 princelai OP @owenliang 大致意思就是两次执行了创建实例和手动销毁实例,第一次的销毁没有触发析构,第二次手动销毁正确的触发了析构,但是多出了一次莫名其妙的执行析构函数 |
7 owenliang 2018-10-30 13:29:02 +08:00 流程还是没怎么理解。 但是对于这种准确回收的问题,可以了解一下 weakref 观察者模式,当程序中不再持有对象的强引用之后可以 callback 通知到观察者,这种实现应该比__del__靠谱。 |
8 owenliang 2018-10-30 13:29:15 +08:00 |
9 princelai OP @owenliang 谢谢,我本来是想让__del__自动帮我执行 pickle,没想到这么不靠谱,还是写个函数手动调用吧 |
10 no1xsyzy 2018-10-30 13:46:31 +08:00 @princelai #4 你输出 p.size 的时候覆盖了原本的 _,在第二个 del 前你可以看一眼 _,应该是 80,int 是个 immutable。看上去将同一块析了两次,iPython 还会以奇怪的方式保留引用。 #5 del 并不能触发,那个只是解引用(从 top frame 中删去 p )。 #6 析构的触发没有保证,和具体实现有关。 > It is implementation-dependent whether __del__() is called a second time when a resurrected object is about to be destroyed; the current CPython implementation only calls it once. > It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits. |
11 princelai OP # 第一次执行 In[67]: p = Proxies() deque created <---这个是 print 出来的 In[68]: p.size Out[69]: 40 In[70]: p.get() Out[71]: {'http': 'http://110.74.199.68:47218', 'cd': 3} In[72]: del p <---这里没输出任何东西 # 第二次执行 In[78]: p = Proxies() deque loaded deque dumped <---怀疑这里是第一次的__del__执行,但是注意是在 loaded 后面输出的 In[79]: p.size Out[79]: 40 <---如果是先 loaded 再执行第一次的 dumped,这里应该是 0,而不是 40 In[81]: p.get_proxies() deque dumped <---这里不知道为什么又有一次 In[82]: p.size Out[82]: 80 In[83]: del p deque dumped <---这里正确执行了__del__ |
12 owenliang 2018-10-30 13:51:56 +08:00 有点奇怪,怎么会多析构一次。。 |
13 princelai OP @no1xsyzy 谢谢了,可能真的和 ipython 也有关系。另外想问一下,只要在整个程序完全结束前,__del__能被执行就可以了,这样__del__能保证肯定会被执行一次吗? |
15 kuroismith 2018-11-04 17:33:54 +08:00 所以一定要用 __del__ 的理由是? 想要序列化的话写一个方法显示调用不好吗 |
16 princelai OP @kuroismith 程序退出自动序列化,但是事实证明不可行,只能手动调用了 |