python - 用深拷贝复制类会以某种方式导致无限递归
问题描述
我试图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
变量值并将其复制到新类。如果不是这样,那么我会喜欢一些启蒙。我搜索结果中的所有文章都与此类似,对我的情况没有太大帮助。
请注意,我不只是在寻找修改后的代码片段来解决这个问题。我主要想了解这里到底发生了什么,这样我既可以现在修复它,也可以在将来避免它。
编辑:这似乎是deepcopy
和set_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')
并且错误再次消失,程序正常运行。
解决方案
该条规定,
深拷贝是一个复制过程递归发生的过程。这意味着首先构造一个新的集合对象,然后递归地用在原始集合中找到的子对象的副本填充它。
所以我的理解告诉我,
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对象。这将解决您的深拷贝问题。