【问题标题】:Duplicating class with deep-copy causes infinite recursion somehow用深拷贝复制类会以某种方式导致无限递归
【发布时间】:2021-04-25 23:10:05
【问题描述】:

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

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

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

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

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

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

url_dict[current_check_link] = deepcopy(current_crawl_job)

url_dict[current_check_link] = current_crawl_job

上面的 sn-p 运行没有错误。同样,如果我完全删除 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')

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

【问题讨论】:

  • 发布运行并在运行时重现错误的内容。此外,您似乎正在通过 IDE 运行此代码,可能使用调试器。如果直接通过命令行运行,是否还会出现错误?
  • @user2357112supportsMonica 是的,我直接从 CLI 运行时遇到同样的错误。而且我会开发一个可以真正快速运行的 sn-p。
  • 这似乎完全是由于其他原因,可能与我的其他递归有关,因为我无法在较小的 sn-p 中重现它。我会继续努力,但似乎它可能与我已经在使用递归搜索树的事实有关。一旦我遇到功能性问题 sn-p,我将更新问题。我真的不希望人们为我调试我的整个文件,但我认为我应该在处理这个问题时至少提供一些东西
  • 您可以使用sys.settrace() 来跟踪代码中的调用并确定递归发生的位置。见Tracing a Program As It Runs
  • 添加了一个产生相同错误的工作代码 sn-p。我也会看看@martineau,谢谢。

标签: python deep-copy


【解决方案1】:

This Article 声明,

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

所以我的理解是这样的,

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())

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

可能的解决方案

你可以告诉 python 设置一个更高的最大递归限制,

limit = 2000
sys.setrecursionlimit(limit)

或者你可能会随着你的程序的进展而幻想并增加这个限制。更多信息请访问this link

我不是 100% 确定增加限制是否可以完成这项工作,但我很确定您的 类对象子对象 /strong> 有太多的内部对象,这让 deepcopy 变得疯狂!

编辑

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

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

当您执行current_crawl_job.set_soup() 时,您班级的None 汤对象将被一个复杂的BeautifulSoup 对象替换。这给 deepcopy 方法带来了麻烦。

建议

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

【讨论】:

  • 你知道吗,我认为你是对的。很快就会对其进行测试,但我现在相当肯定它一定是self.soup,因为它包含很多关于网页的信息,包括源代码本身。我可以将它更改为类内的 str ,并在它之外处理实际的汤。我们会看看情况如何。
  • 是的,就是这样。我在不知不觉中编辑了我的答案,告诉了同样的事情哈哈!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 2012-05-26
  • 2012-02-20
  • 1970-01-01
  • 2013-09-07
相关资源
最近更新 更多