Skip to content

Decorators

danling.utils.decorators

flexible_decorator

Python
flexible_decorator(maybe_decorator: Optional[Callable] = None)

Meta decorator to allow bracket-less decorator when no arguments are passed.

Examples:

For decorator defined as follows:

Python Console Session
1
2
3
4
5
>>> @flexible_decorator
... def decorator(*args, **kwargs):
...     def wrapper(func, *args, **kwargs):
...         pass
...     return wrapper

The following two are equivalent:

Python Console Session
1
2
3
>>> @decorator
... def func(*args, **kwargs):
...     pass
Python Console Session
1
2
3
>>> @decorator()
... def func(*args, **kwargs):
...     pass
Source code in danling/utils/decorators.py
Python
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
def flexible_decorator(maybe_decorator: Optional[Callable] = None):
    r"""
    Meta decorator to allow bracket-less decorator when no arguments are passed.

    Examples:
        For decorator defined as follows:

        >>> @flexible_decorator
        ... def decorator(*args, **kwargs):
        ...     def wrapper(func, *args, **kwargs):
        ...         pass
        ...     return wrapper

        The following two are equivalent:

        >>> @decorator
        ... def func(*args, **kwargs):
        ...     pass

        >>> @decorator()
        ... def func(*args, **kwargs):
        ...     pass
    """

    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if len(args) == 1 and isfunction(args[0]):
                return func(**kwargs)(args[0])
            return func(*args, **kwargs)

        return wrapper

    if maybe_decorator is None:
        return decorator
    return decorator(maybe_decorator)

print_exc

Python
print_exc(exc, func, args, kwargs, verbosity: int = 40)

Print exception raised by func with args and kwargs to stderr. This function serves as the default callback for catch.

Parameters:

Name Type Description Default

verbosity

int

What level of traceback to print. 0-: No traceback. 0-10: Full information of arguments and key word arguments. 10-20: Stack trace to function calls. 40+: Function name and error messages.

40
Source code in danling/utils/decorators.py
Python
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def print_exc(exc, func, args, kwargs, verbosity: int = 40):  # pylint: disable=W0613
    r"""
    Print exception raised by `func` with `args` and `kwargs` to `stderr`.
    This function serves as the default callback for catch.

    Args:
        verbosity: What level of traceback to print.
            0-: No traceback.
            0-10: Full information of arguments and key word arguments.
            10-20: Stack trace to function calls.
            40+: Function name and error messages.
    """

    if verbosity >= 0:
        message = traceback.format_exc()
        message += f"\nencountered when calling {func}"
        if verbosity <= 20:
            message += "\n\nstack:\n" + "\n".join(traceback.format_stack()[:-2])
        if verbosity <= 10:
            message += "\n" + f"args: {args}\nkwargs: {kwargs}"
        try:
            print(message, file=stderr, force=True)  # type: ignore
        except TypeError:
            print(message, file=stderr)

catch

Python
catch(error: Exceptions = Exception, exclude: Exceptions | None = None, callback: Callable = print_exc, *callback_args, **callback_kwargs)

Decorator to catch error except for exclude. Detailed traceback will be printed to stderr.

catch is extremely useful for unfatal errors. For example, Runner saves checkpoint regularly, however, this might break running if the space is full. Decorating save method with catch will allow you to catch these errors and continue your running.

Parameters:

Name Type Description Default

error

Exceptions

Exceptions to be caught.

Exception

exclude

Exceptions | None

Exceptions to be excluded.

None

callback

Callable

Callback to be called when an error occurs. The first four arguments to callback are exc, func, args, kwargs. Additional arguments should be passed with *callback_args and **callback_kwargs.

print_exc

*callback_args

object

Additional positional arguments passed to callback.

()

**callback_kwargs

object

Additional keyword arguments passed to callback.

{}

Examples:

Python Console Session
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> def file_not_found(*args, **kwargs):
...     raise FileNotFoundError
>>> func = file_not_found
>>> func()
Traceback (most recent call last):
FileNotFoundError
>>> func = catch(OSError)(file_not_found)
>>> func()
>>> func = catch(IOError)(file_not_found)
>>> func()
>>> func = catch(ZeroDivisionError)(file_not_found)
>>> func()
Traceback (most recent call last):
FileNotFoundError
Source code in danling/utils/decorators.py
Python
 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
143
144
145
146
147
148
149
150
151
152
153
@flexible_decorator
def catch(  # pylint: disable=keyword-arg-before-vararg
    error: Exceptions = Exception,
    exclude: Exceptions | None = None,
    callback: Callable = print_exc,
    *callback_args,
    **callback_kwargs,
):
    r"""
    Decorator to catch `error` except for `exclude`.
    Detailed traceback will be printed to `stderr`.

    `catch` is extremely useful for unfatal errors.
    For example, `Runner` saves checkpoint regularly, however, this might break running if the space is full.
    Decorating `save` method with `catch` will allow you to catch these errors and continue your running.

    Args:
        error: Exceptions to be caught.
        exclude: Exceptions to be excluded.
        callback: Callback to be called when an error occurs.
            The first four arguments to `callback` are `exc`, `func`, `args`, `kwargs`.
            Additional arguments should be passed with `*callback_args` and `**callback_kwargs`.
        *callback_args (object): Additional positional arguments passed to `callback`.
        **callback_kwargs (object): Additional keyword arguments passed to `callback`.

    Examples:
        >>> def file_not_found(*args, **kwargs):
        ...     raise FileNotFoundError
        >>> func = file_not_found
        >>> func()
        Traceback (most recent call last):
        FileNotFoundError
        >>> func = catch(OSError)(file_not_found)
        >>> func()
        >>> func = catch(IOError)(file_not_found)
        >>> func()
        >>> func = catch(ZeroDivisionError)(file_not_found)
        >>> func()
        Traceback (most recent call last):
        FileNotFoundError
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):  # pylint: disable=inconsistent-return-statements
            try:
                return func(*args, **kwargs)
            except error as exc:  # pylint: disable=broad-exception-caught
                if exclude is not None and isinstance(exc, exclude):
                    raise exc
                callback(exc, func, args, kwargs, *callback_args, **callback_kwargs)

        return wrapper

    decorator.__doc__ = catch.__doc__

    return decorator

method_cache

Python
method_cache(maxsize: int | None = 128, typed: bool = False)

Decorator to cache the result of an instance method.

functools.lru_cache uses a strong reference to the instance, which will make the instance immortal and break the garbage collection.

method_cache uses a weak reference to the instance to resolve this issue.

See Also
Source code in danling/utils/decorators.py
Python
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@flexible_decorator
def method_cache(maxsize: int | None = 128, typed: bool = False):
    r"""
    Decorator to cache the result of an instance method.

    `functools.lru_cache` uses a strong reference to the instance,
    which will make the instance immortal and break the garbage collection.

    `method_cache` uses a weak reference to the instance to resolve this issue.

    See Also:
        https://rednafi.github.io/reflections/dont-wrap-instance-methods-with-functoolslru_cache-decorator-in-python.html
    """

    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            self_ref = ref(self)

            @wraps(func)
            @lru_cache(maxsize=maxsize, typed=typed)
            def cached_method(*args, **kwargs):
                return func(self_ref(), *args, **kwargs)

            setattr(self, func.__name__, cached_method)
            return cached_method(*args, **kwargs)

        return wrapper

    return decorator