Repository navigation
Expand file tree
/
Copy pathmxutils_decorators.py
More file actions
142 lines (118 loc) · 5.99 KB
/
Copy pathmxutils_decorators.py
File metadata and controls
142 lines (118 loc) · 5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""Demonstrates mxutils helpers for diagnosing Cinema 4D scene data.
Run this example in the Cinema 4D Script Manager. It prints a container, parameter,
and scene graph tree for the active document and its selected object.
Topics:
* mxutils.TIMEIT
* mxutils.SET_STATUS
* mxutils.REPORT
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027.0"
import math
import time
import c4d
import mxutils
doc: c4d.documents.BaseDocument # The active Cinema 4D document.
def main() -> None:
"""Runs the example for the active document.
"""
@mxutils.REPORT()
def foo() -> None:
print("foo called")
# mxutils.REPORT() will simply report if a function was called. When it is attached to a method,
# it will also report the object instance on which the method was called. This can help with
# debugging call chains without setting breakpoints to see call stacks (which often also contain
# a lot of unrelated information).
foo()
@mxutils.SET_STATUS("Computing ...")
def bar() -> None:
time.sleep(2)
print("bar done")
# SET_STATUS helps with providing visual feedback to the user while your code is running. It will
# set the status bar text to the provided message while the decorated function is running.
bar()
# And last but not least, there is the TIMEIT decorator, which can be used to measure the
# execution time of a function.
IS_DEBUG: bool = True
# We define a recursive function and decorate it with the default timeit decorator.
@mxutils.TIMEIT(IS_DEBUG)
def factorial(n: int) -> int:
return 1 if n == 0 else n * factorial(n - 1)
# We call the function and see the time measurements. Because the function is recursive, we
# see the a report for each call in the recursion chain.
factorial(3)
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'factorial()' in 0.0 sec
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'factorial()' in 6e-05 sec
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'factorial()' in 0.00011 sec
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'factorial()' in 0.00013 sec
# We redefine the function but this time enable in-call protection. We also enable seeing
# the arguments and result of a function call.
@mxutils.TIMEIT(IS_DEBUG, showArgs=True, showResult=True, inCallProtection=True)
def factorial(n: int) -> int:
return 1 if n == 0 else n * factorial(n - 1)
# Now we call the function again. This time we only see the the timing for the outmost
# call of the recursion chain.
factorial(10)
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'factorial((10,), {}) -> 3628800' in 1e-05 sec
# And finally a more complex example where we decorate class methods and use full profiling.
class Processor:
# The arguments we pass to the timeit decorator in this class.
TIMEIT_KWARGS: dict[str, any] = {
"enabled": IS_DEBUG, "showArgs": True,
"showResult": True, "inCallProtection": True,
"useProfiler": True
}
def __init__(self, n: int) -> None:
self.recursive(n)
self.iterative(n)
@mxutils.TIMEIT(**TIMEIT_KWARGS)
def recursive(self, n: int) -> int:
return 1 if n == 0 else n * self.recursive(n - 1)
@mxutils.TIMEIT(**TIMEIT_KWARGS)
def iterative(self, n: int) -> int:
x: int = n
for i in range(int(n * 1000)):
x += math.sqrt(x)
x *= math.sin(i)
return x
p: Processor = Processor(50)
# The output will look something like this. For the recursive call we mostly see the overhead
# of the decorator. Profiling recursive functions in this manner that are not very expensive
# (execute in the microsecond range) is not very useful.
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'recursive((<__main__.Processor object at 0x7fd18c0696a0>, 50), {}) ->
# 30414093201713378043612608166064768844377641568960512000000000000' with 302 function
# calls (203 primitive calls) in 0.000 seconds
#
# Ordered by: cumulative time
#
# ncalls tottime percall cumtime percall filename:lineno(function)
# 51/1 0.000 0.000 0.000 0.000 scriptmanager:476(recursive)
# 50/1 0.000 0.000 0.000 0.000 scriptmanager:401(wrapper)
# 50 0.000 0.000 0.000 0.000 /Applications/Maxon Cinema 4D 2024.4.0/...
# 50 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
# 50 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}
# 50 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}
# 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
# --------------------------------------------------------------------------------
# TIMEIT: Ran 'iterative((<__main__.Processor object at 0x7fd18c0696a0>, 50), {}) -> -0.0' with
# 100002 function calls in 0.037 seconds
#
# Ordered by: cumulative time
#
# ncalls tottime percall cumtime percall filename:lineno(function)
# 1 0.025 0.025 0.037 0.037 scriptmanager:480(iterative)
# 50000 0.007 0.000 0.007 0.000 {built-in method math.sin}
# 50000 0.005 0.000 0.005 0.000 {built-in method math.sqrt}
# 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
import importlib
if __name__ == "__main__":
c4d.ClearPythonConsole()
main()