- Тонкости построения сетевых моделей в Python
- Сначала создай работы, потом устанавливай связи
- Пересчитывай модель после её построения
- Контролируй глубину рекурсии
- И напоследок…
- What Is the Maximum Recursion Depth in Python
- Python Maximum Recursion Depth Exceded in Comparison
- Why Is There a Recursion Depth Limit in Python
- What Is a Stack Overflow Error in Python
- How to Change the Recursion Depth Limit in Python—Danger Zone!
- Temporarily Change the Recursion Depth Limit in Python
- Conclusion
- Максимальная глубина рекурсии в Python и как её увеличить
Тонкости построения сетевых моделей в Python
Что является основным инструментом, который использует руководитель при управлении проектом? Принято считать, что основным инструментом руководителя проекта является календарный план, в основе которого лежит сетевая модель работ по проекту. Однажды мне довелось реализовать сетевую модель работ на языке Python (код и описание здесь). Ниже приведены уроки, извлеченные по результатам проделанной работы.
Сначала создай работы, потом устанавливай связи
При построении сетевой модели часто возникает вопрос, в каком порядке создавать работы и устанавливать связи между ними? Наиболее очевидным является двухэтапный подход – сначала создаются все работы модели, затем между ними устанавливаются связи. Такой подход позволяет избежать ошибок типа KeyError: ‘101’, возникающих при параллельном выполнении этих двух операций, когда система пытается установить связь с работой, которая еще не была создана.
Конечно, суммарное время выполнения двух последовательных операций по созданию работ и установке связей между ними может быть оптимизировано за счет использования алгоритма, в котором эти операции выполняются параллельно. Однако даже на больших моделях с десятком тысяч работ последовательные алгоритмы работают достаточно быстро. Поэтому в условиях временных ограничений на реализацию проекта вполне можно обойтись классическим двухэтапным подходом.
Пересчитывай модель после её построения
Стоит ли выполнять пересчет всякий раз, когда происходит установка связи между работами при построении сетевой модели? С одной стороны, постоянный пересчет позволяет держать модель в актуальном состоянии. С другой, пересчет увеличивает время ее построения.
Для сравнения были реализованы две функции:
- build_model_by_method() – построение с пересчетом модели;
- build_model_by_assignment() – построение без пересчета модели.
from predict import Activity import xml.etree.ElementTree as ET import sys import timeit from timeit import Timer # вычисляем значение параметра задачи def get_child(child, activity_field): text = child.find(activity_field).text if text is None: return None return int(text) # строим модель с использованием метода пересчета def build_model_by_method(filename): sys.setrecursionlimit(10000) f = open(filename,'r') tree = ET.parse(f) root = tree.getroot() schedule = <> next = <> for child in root.findall('Activity'): start_date = get_child(child,'start_date') finish_date = get_child(child,'finish_date') duration = get_child(child,'duration') not_early_date = get_child(child,'not_early_date') a = Activity(id, start_date, finish_date, duration, not_early_date) schedule[id] = a next_activity = '' if child.find('next_activity').text is None else child.find('next_activity').text next[id] = next_activity for key in schedule: if nextУвеличение глубины рекурсии python != '': for next_id in nextУвеличение глубины рекурсии python.split(';'): scheduleУвеличение глубины рекурсии python.append_next(schedule[next_id]) sys.setrecursionlimit(1000) # строим модель без использования метода пересчета def build_model_by_assignment(filename): f = open(filename,'r') tree = ET.parse(f) root = tree.getroot() schedule = <> next = <> for child in root.findall('Activity'): start_date = get_child(child,'start_date') finish_date = get_child(child,'finish_date') duration = get_child(child,'duration') not_early_date = get_child(child,'not_early_date') a = Activity(id, start_date, finish_date, duration, not_early_date) schedule[id] = a next_activity = '' if child.find('next_activity').text is None else child.find('next_activity').text next[id] = next_activity for key in schedule: if nextУвеличение глубины рекурсии python != '': for next_id in nextУвеличение глубины рекурсии python.split(';'): scheduleУвеличение глубины рекурсии python.next_activity.append(schedule[next_id]) # считаем скорость построения модели print('Test for 100 activities:') t1 = Timer("build_model_by_method('data/activity_100.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t1.timeit(number = 1000)) t2 = Timer("build_model_by_assignment('data/activity_100.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t2.timeit(number = 1000)) print('Test for 1000 activities') t3 = Timer("build_model_by_method('data/activity_1000.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t3.timeit(number = 1000)) t4 = Timer("build_model_by_assignment('data/activity_1000.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t4.timeit(number = 1000)) print('Test for 10000 activities') t5 = Timer("build_model_by_method('data/activity_10000.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t5.timeit(number = 1000)) t6 = Timer("build_model_by_assignment('data/activity_10000.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t6.timeit(number = 1000))
$ python network.py Test for 100 activities: build_model_by_method 1.7820062519999738 build_model_by_assignment 1.426311435999878 Test for 1000 activities build_model_by_method 18.998158786999966 build_model_by_assignment 14.216093206999858 Test for 10000 activities build_model_by_method 249.93449528199994 build_model_by_assignment 148.85600239800033
Как видно, чем больше работ, тем медленнее работает функция построения сетевой модели с использованием пересчета по сравнению с функцией, в которой пересчет не используется.
Контролируй глубину рекурсии
Сетевая модель проекта состоит из работ и связей между ними. В отсутствии дополнительной информации о сети для ее пересчета используются рекурсивные алгоритмы. Такие алгоритмы состоят в последовательном проходе по работам сети и пересчете их параметров (например, длительности, дат начала и окончания). Чем больше работ в сети, тем больше глубина рекурсии.
Известно, что в Python по умолчанию установлено ограничение на глубину рекурсии – не больше 1000 рекурсивных вызовов. Для получения этого ограничения предназначен метод getrecursionlimit() модуля sys.
>>> import sys >>> sys.getrecursionlimit() 1000
При работе с большими сетями, число работ в которых измеряется десятками тысяч, обычной ситуацией является превышение глубины рекурсии и, как следствие, возникновение ошибки типа RecursionError: maximum recursion depth exceeded in comparison. Ошибка приводит к остановке построения либо пересчета модели и падению системы.
Чтобы предотвратить ошибку, построить и пересчитать большую сеть, необходимо увеличить ограничение на глубину рекурсии. И в этом нам поможет метод setrecursionlimit() модуля sys.
>>> import sys >>> sys.setrecursionlimit(10000) >>> sys.getrecursionlimit() 10000
До какого значения требуется увеличить глубину рекурсии? Ответ на этот вопрос зависит от структуры сетевой модели. Если структура неизвестна или достаточно сложна, рекомендую устанавливать ограничение в значение, равное количеству работ в сети. Рекурсивный алгоритм проходится либо по всем либо по части работ. Поэтому глубина рекурсии не должна превысить количество работ в сети.
И напоследок…
Управление проектами – это модно. Но модно еще не значит эффективно. На рынке существуют программные решения по пересчету сетевых моделей, такие как Microsoft Project. Однако алгоритмы, зашитые в них остаются доступными только вендерам соответствующего программного обеспечения.
Настоящая статья написана на основе опыта разработки открытого модуля по построению и пересчету сетевых моделей проектов. Я надеюсь, что приведенные в статье извлеченные уроки будут полезны читателю как с теоретической, так и с чисто практической точки зрения. Если настоящая статья вызовет интерес, то я поделюсь новыми знаниями, которые возникнут в дальнейшем, по мере развития модуля.
What Is the Maximum Recursion Depth in Python
The maximum recursion depth in Python is 1000.
You can verify this by calling sys.getrecursionlimit() function:
import sys print(sys.getrecursionlimit()) # Prints 1000
You can change the limit by calling sys.setrecursionlimit() method.
import sys print(sys.setrecursionlimit(2000))
Consider this a dangerous action!
If possible, instead of tweaking the recursion limit, try to implement your algorithm iteratively to avoid deep recursion.
Python Maximum Recursion Depth Exceded in Comparison
Whenever you exceed the recursion depth of 1000, you get an error in Python.
For example, if we try to compute a too large Fibonacci number, we get the recursion depth error.
# A function for computing Fibonacci numbers def fibonacci(n): if nFile "example.py", line 2, in fibonacci if nThis error says it all—maximum recursion depth exceeded in comparison. This tells you that Python’s recursion depth limit of 1000 is reached.
But why is there such a limit? More importantly, how can you overcome it?
Let’s answer these questions next.
Why Is There a Recursion Depth Limit in Python
A recursive function could call itself indefinitely. In other words, you could end up with an endless loop.
Also, a stack overflow error can occur even if the recursion is not infinite. This can happen due to too big of a stack frame.
In Python, the recursion depth limit takes these risks out of the equation.
Python uses a maximum recursion depth of 1000 to ensure no stack overflow errors and infinite recursions are possible.
This recursion limit is somewhat conservative, but it is reasonable as stack frames can become big in Python.
What Is a Stack Overflow Error in Python
Stack overflow error is usually caused by too deep (or infinite) recursion.
This means a function calls itself so many times that the space needed to store the information related to each call is more than what fits on the stack.
How to Change the Recursion Depth Limit in Python—Danger Zone!
You can change the maximum recursion depth in Python. But consider it a dangerous action.
To do this, call the sys.setrecursionlimit() function.
For example, let’s set the maximum recursion depth to 2000 :
import sys print(sys.setrecursionlimit(2000))Temporarily Change the Recursion Depth Limit in Python
Do you often need to tweak the recursion depth limit in your project?
If you do, consider using a context manager. This can improve the quality of your code.
For example, let’s implement a context manager that temporarily switches the recursion limit:
import sys class recursion_depth: def __init__(self, limit): self.limit = limit self.default_limit = sys.getrecursionlimit() def __enter__(self): sys.setrecursionlimit(self.limit) def __exit__(self, type, value, traceback): sys.setrecursionlimit(self.default_limit)Now you can temporarily change the recursion depth to perform a recursive task.
with recursion_depth(2000): print(fibonacci(1000, 0))When this operation completes, the context manager automatically switches the recursion depth limit back to the original value.
Learn more about the with statement and context managers in Python here.
Conclusion
The recursion depth limit in Python is by default 1000 . You can change it using sys.setrecursionlimit() function.
Thanks for reading. I hope you enjoy it.
Максимальная глубина рекурсии в Python и как её увеличить
Рекурсия — это техника в программировании, при которой функция вызывает саму себя напрямую или косвенно. Она может быть очень полезной для решения определенных задач, но имеет свои ограничения. Одно из них — максимальная глубина рекурсии.
В Python предусмотрено ограничение на максимальную глубину рекурсии, чтобы предотвратить переполнение стека и последующий сбой программы. Это ограничение обычно установлено на достаточно высоком уровне (обычно порядка 1000), но иногда, для решения определенных задач, может потребоваться увеличить этот лимит.
def factorial(n): if n == 1: return 1 else: return n * factorial(n-1) print(factorial(2000))В этом коде функция factorial рекурсивно вызывает сама себя для вычисления факториала числа. Но при попытке вычислить факториал числа больше 1000, получаем ошибку RecursionError: maximum recursion depth exceeded in comparison .
Чтобы увеличить максимальную глубину рекурсии, можно использовать функцию sys.setrecursionlimit(limit) . Эта функция устанавливает максимальную глубину рекурсии на указанное значение. Но стоит быть осторожным, увеличивая этот лимит, так как это может привести к переполнению стека и сбою программы.
import sys sys.setrecursionlimit(3000) print(factorial(2000)) # Теперь это работаетТаким образом, хотя увеличение максимальной глубины рекурсии может быть полезным, с ним следует обращаться с осторожностью. Более того, часто большие глубины рекурсии указывают на то, что задача, возможно, может быть решена более эффективным способом, не используя рекурсию.