|
2 | 2 | import sys |
3 | 3 | import copy |
4 | 4 | import types |
5 | | -import inspect |
6 | 5 | import keyword |
7 | 6 | import itertools |
8 | 7 | import annotationlib |
@@ -432,6 +431,38 @@ def _tuple_str(obj_name, fields): |
432 | 431 | # Note the trailing comma, needed if this turns out to be a 1-tuple. |
433 | 432 | return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)' |
434 | 433 |
|
| 434 | +# NOTE: This is a vendored copy of `inspect.unwrap` to speed up import time |
| 435 | +def _unwrap(func, *, stop=None): |
| 436 | + """Get the object wrapped by *func*. |
| 437 | +
|
| 438 | + Follows the chain of :attr:`__wrapped__` attributes returning the last |
| 439 | + object in the chain. |
| 440 | +
|
| 441 | + *stop* is an optional callback accepting an object in the wrapper chain |
| 442 | + as its sole argument that allows the unwrapping to be terminated early if |
| 443 | + the callback returns a true value. If the callback never returns a true |
| 444 | + value, the last object in the chain is returned as usual. For example, |
| 445 | + :func:`signature` uses this to stop unwrapping if any object in the |
| 446 | + chain has a ``__signature__`` attribute defined. |
| 447 | +
|
| 448 | + :exc:`ValueError` is raised if a cycle is encountered. |
| 449 | +
|
| 450 | + """ |
| 451 | + f = func # remember the original func for error reporting |
| 452 | + # Memoise by id to tolerate non-hashable objects, but store objects to |
| 453 | + # ensure they aren't destroyed, which would allow their IDs to be reused. |
| 454 | + memo = {id(f): f} |
| 455 | + recursion_limit = sys.getrecursionlimit() |
| 456 | + while not isinstance(func, type) and hasattr(func, '__wrapped__'): |
| 457 | + if stop is not None and stop(func): |
| 458 | + break |
| 459 | + func = func.__wrapped__ |
| 460 | + id_func = id(func) |
| 461 | + if (id_func in memo) or (len(memo) >= recursion_limit): |
| 462 | + raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) |
| 463 | + memo[id_func] = func |
| 464 | + return func |
| 465 | + |
435 | 466 |
|
436 | 467 | class _FuncBuilder: |
437 | 468 | def __init__(self, globals): |
@@ -982,6 +1013,28 @@ def _hash_exception(cls, fields, func_builder): |
982 | 1013 | # See https://bugs.python.org/issue32929#msg312829 for an if-statement |
983 | 1014 | # version of this table. |
984 | 1015 |
|
| 1016 | +class AutoDocstring: |
| 1017 | + """A non-data descriptor to autogenerate class docstring |
| 1018 | + from the signature of its __init__ method. |
| 1019 | + """ |
| 1020 | + |
| 1021 | + def __get__(self, _obj, cls): |
| 1022 | + import inspect |
| 1023 | + |
| 1024 | + try: |
| 1025 | + # In some cases fetching a signature is not possible. |
| 1026 | + # But, we surely should not fail in this case. |
| 1027 | + text_sig = str(inspect.signature( |
| 1028 | + cls, |
| 1029 | + annotation_format=annotationlib.Format.FORWARDREF, |
| 1030 | + )).replace(' -> None', '') |
| 1031 | + except (TypeError, ValueError): |
| 1032 | + text_sig = '' |
| 1033 | + |
| 1034 | + doc = cls.__name__ + text_sig |
| 1035 | + setattr(cls, '__doc__', doc) |
| 1036 | + return doc |
| 1037 | + |
985 | 1038 |
|
986 | 1039 | def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, |
987 | 1040 | match_args, kw_only, slots, weakref_slot): |
@@ -1209,23 +1262,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, |
1209 | 1262 | if hash_action: |
1210 | 1263 | cls.__hash__ = hash_action(cls, field_list, func_builder) |
1211 | 1264 |
|
1212 | | - # Generate the methods and add them to the class. This needs to be done |
1213 | | - # before the __doc__ logic below, since inspect will look at the __init__ |
1214 | | - # signature. |
| 1265 | + # Generate the methods and add them to the class. |
1215 | 1266 | func_builder.add_fns_to_class(cls) |
1216 | 1267 |
|
1217 | 1268 | if not getattr(cls, '__doc__'): |
1218 | | - # Create a class doc-string. |
1219 | | - try: |
1220 | | - # In some cases fetching a signature is not possible. |
1221 | | - # But, we surely should not fail in this case. |
1222 | | - text_sig = str(inspect.signature( |
1223 | | - cls, |
1224 | | - annotation_format=annotationlib.Format.FORWARDREF, |
1225 | | - )).replace(' -> None', '') |
1226 | | - except (TypeError, ValueError): |
1227 | | - text_sig = '' |
1228 | | - cls.__doc__ = (cls.__name__ + text_sig) |
| 1269 | + # Create a class doc-string lazily via descriptor protocol |
| 1270 | + # to avoid importing `inspect` module. |
| 1271 | + cls.__doc__ = AutoDocstring() |
1229 | 1272 |
|
1230 | 1273 | if match_args: |
1231 | 1274 | # I could probably compute this once. |
@@ -1378,7 +1421,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): |
1378 | 1421 | # given cell. |
1379 | 1422 | for member in newcls.__dict__.values(): |
1380 | 1423 | # If this is a wrapped function, unwrap it. |
1381 | | - member = inspect.unwrap(member) |
| 1424 | + member = _unwrap(member) |
1382 | 1425 |
|
1383 | 1426 | if isinstance(member, types.FunctionType): |
1384 | 1427 | if _update_func_cell_for__class__(member, cls, newcls): |
|
0 commit comments