Python 类的析构函数行为非常奇怪 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
princelai
V2EX    Python

Python 类的析构函数行为非常奇怪

  •  
  •   princelai 2018-10-30 12:45:13 +08:00 3567 次点击
    这是一个创建于 2605 天前的主题,其中的信息可能已经有所发展或是发生改变。

    首先定义一个类,作用是获取代理

    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

    谁能给分析分析是为什么吗

    16 条回复    2018-11-05 10:03:50 +08:00
    owenliang
        1
    owenliang  
       2018-10-30 12:55:45 +08:00
    描述没怎么看懂,你贴一段调用的完整复现代码看看?
    no1xsyzy
        2
    no1xsyzy  
       2018-10-30 13:11:18 +08:00
    你是在 REPL 里执行后面这些的吗?
    REPL 会把最后一次非 None 返回值保存到 `_` 上。
    kindjeff
        3
    kindjeff  
       2018-10-30 13:14:35 +08:00 via iPhone
    垃圾回收的时候才会触发__del__,换句话说,你没法保证什么时候__del__被触发,所以不要把有用的逻辑写在__del__里。
    princelai
        4
    princelai  
    OP
       2018-10-30 13:16:45 +08:00
    @no1xsyzy 是的,在 ipython 里,我看流畅的 python 确实说过_为 None 的时候才会执行析构,但是之后也是同样的操作,也没有输出 None,为什么又正常了?而且我在第二次执行 p.get_proxies() 时,这里也出现了一次执行析构函数
    princelai
        5
    princelai  
    OP
       2018-10-30 13:17:40 +08:00
    @kindjeff 显示的 del 并不能触发__del__吗
    princelai
        6
    princelai  
    OP
       2018-10-30 13:19:19 +08:00
    @owenliang 大致意思就是两次执行了创建实例和手动销毁实例,第一次的销毁没有触发析构,第二次手动销毁正确的触发了析构,但是多出了一次莫名其妙的执行析构函数
    owenliang
        7
    owenliang  
       2018-10-30 13:29:02 +08:00
    流程还是没怎么理解。

    但是对于这种准确回收的问题,可以了解一下 weakref 观察者模式,当程序中不再持有对象的强引用之后可以 callback 通知到观察者,这种实现应该比__del__靠谱。
    owenliang
        8
    owenliang  
       2018-10-30 13:29:15 +08:00
    princelai
        9
    princelai  
    OP
       2018-10-30 13:33:26 +08:00
    @owenliang 谢谢,我本来是想让__del__自动帮我执行 pickle,没想到这么不靠谱,还是写个函数手动调用吧
    no1xsyzy
        10
    no1xsyzy  
       2018-10-30 13:46:31 +08:00   1
    @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.
    princelai
        11
    princelai  
    OP
       2018-10-30 13:47:52 +08:00
    # 第一次执行

    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__
    owenliang
        12
    owenliang  
       2018-10-30 13:51:56 +08:00
    有点奇怪,怎么会多析构一次。。
    princelai
        13
    princelai  
    OP
       2018-10-30 13:52:27 +08:00
    @no1xsyzy 谢谢了,可能真的和 ipython 也有关系。另外想问一下,只要在整个程序完全结束前,__del__能被执行就可以了,这样__del__能保证肯定会被执行一次吗?
    guog
        14
    guog  
       2018-10-30 14:01:43 +08:00
    @princelai #13 del 不能保证完全被执行是出于程序会提前异常(不论是内部还是外部强制杀死)退出吧
    kuroismith
        15
    kuroismith  
       2018-11-04 17:33:54 +08:00
    所以一定要用 __del__ 的理由是?
    想要序列化的话写一个方法显示调用不好吗
    princelai
        16
    princelai  
    OP
       2018-11-05 10:03:50 +08:00
    @kuroismith 程序退出自动序列化,但是事实证明不可行,只能手动调用了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4423 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 05:37 PVG 13:37 LAX 21:37 JFK 00:37
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86