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 · 2834 次点击
    这是一个创建于 1976 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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   ·   我们的愿景   ·   实用小工具   ·   3609 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:45 · PVG 18:45 · LAX 03:45 · JFK 06:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.