首页 > 解决方案 > 用深拷贝复制类会以某种方式导致无限递归

问题描述

我试图URL在 python 中简单地制作我的类的独立副本,这样我就可以在不影响原始文件的情况下修改副本。

以下是我的问题代码的精简可执行版本:

from bs4 import BeautifulSoup
from copy import deepcopy
from urllib import request

url_dict = {}


class URL:
    def __init__(self, url, depth, log_entry=None, soup=None):
        self.url = url
        self.depth = depth  # Current, not total, depth level
        self.log_entry = log_entry
        self.soup = soup
        self.indent = '    ' * (5 - self.depth)
        self.log_url = 'test.com'

        # Blank squad
        self.parsed_list = []

    def get_log_output(self):
        return self.indent + self.log_url

    def get_print_output(self):
        if self.log_entry is not None:
            return self.indent + self.log_url + ' | ' + self.log_entry

        return self.indent + self.log_url

    def set_soup(self):
        if self.soup is None:
            code = ''

            try:  # Read and store code for parsing
                code = request.urlopen(self.url).read()
            except Exception as exception:
                print(str(exception))

            self.soup = BeautifulSoup(code, features='lxml')


def crawl(current_url, current_depth):
    current_check_link = current_url
    has_crawled = current_check_link in url_dict
    
    if current_depth > 0 and not has_crawled:
        current_crawl_job = URL(current_url, current_depth)
        current_crawl_job.set_soup()
        url_dict[current_check_link] = deepcopy(current_crawl_job)


for link in ['http://xts.site.nfoservers.com']:  # Crawl for each URL the user inputs
    crawl(link, 3)

产生的异常:

Traceback (most recent call last):
File "/home/[CENSORED]/.vscode-oss/extensions/ms-python.python-2020.10.332292344/pythonFiles/lib/python/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_trace_dispatch_regular.py", line 374, in __call__
if cache_skips.get(frame_cache_key) == 1:
RecursionError: maximum recursion depth exceeded in comparison
Fatal Python error: _Py_CheckRecursiveCall: Cannot recover from stack overflow.
Python runtime state: initialized

我不知道这个特定的无限递归发生在哪里。我在 python copy.deepcopy 时通读了诸如 RecursionError 之类的问题,但我什至不确定它是否适用于我的用例。如果它确实适用,那么我的大脑似乎无法理解它,因为我的印象deepcopy()应该只是获取每个self变量值并将其复制到新类。如果不是这样,那么我会喜欢一些启蒙。我搜索结果中的所有文章都与类似,对我的情况没有太大帮助。

请注意,我不只是在寻找修改后的代码片段来解决这个问题。我主要想了解这里到底发生了什么,这样我既可以现在修复它,也可以在将来避免它。

编辑:这似乎是deepcopyset_soup()方法之间的冲突。如果我更换

url_dict[current_check_link] = deepcopy(current_crawl_job)

url_dict[current_check_link] = current_crawl_job

上面的代码片段运行没有错误。同样,如果我完全删除current_crawl_job.set_soup(),我也不会收到任何错误。我就是不能两者兼得。

Edit2:我可以删除任何一个

try:  # Read and store code for parsing
    code = request.urlopen(self.url).read()
except Exception as exception:
    print(str(exception))

或者

self.soup = BeautifulSoup(code, features='lxml')

并且错误再次消失,程序正常运行。

标签: pythondeep-copy

解决方案


该条规定,

深拷贝是一个复制过程递归发生的过程。这意味着首先构造一个新的集合对象,然后递归地用在原始集合中找到的子对象的副本填充它。

所以我的理解告诉我,

A = [1,2,[3,4],5]

B = deepcopy(A) #This will make 1 level deep recursive call to copy the inner list

C = [1,[2,[3,[4,[5,[6]]]]]]

D = deepcopy(C) #This will make 5 levels deep recursive call (recursively copying inner lists)

我最好的猜测

Python 有一个最大递归深度限制以防止堆栈溢出

您可以使用以下方法找到最大递归深度限制,

import sys
print(sys.getrecursionlimit())

在您的情况下,您正在尝试深度复制Class Object。对该类对象的深度复制的递归调用必须超过最大递归限制。

可能的解决方案

您可以使用以下命令告诉 python 设置更高的最大递归限制,

limit = 2000
sys.setrecursionlimit(limit)

或者随着程序的进行,您可能会喜欢并增加该限制。有关此链接的更多信息。

我不是100%肯定增加限制会完成这项工作,但我很确定你的类对象的某些子对象有这么多的内部对象,这让deepcopy发疯了!

编辑

有些东西告诉我下面的行是罪魁祸首,

self.soup = BeautifulSoup(code, features='lxml')

当你这样做时,current_crawl_job.set_soup()你的类的None汤对象将被一个复杂的BeautifulSoup对象替换。这给deepcopy方法带来了麻烦。

建议

set_soup方法中,将self.soup属性保留为原始html字符串,并在您尝试修改它时将其转换为BeautifulSoup对象。这将解决您的深拷贝问题。


推荐阅读