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 143 144 145 146 147 148 149 150 151 152 153 154 155 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 186 187 188 189 190 191
|
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.
'''
Project-wide **weak reference** (i.e., references to objects explicitly
allowing those objects to be garbage-collected at *any* time) utilities.
This private submodule is *not* intended for importation by downstream callers.
'''
# ....................{ IMPORTS }....................
from beartype.roar._roarexc import _BeartypeUtilPythonWeakrefException
from beartype.typing import (
Tuple,
)
from weakref import ref as weakref_ref
# ....................{ GETTERS }....................
def make_obj_weakref_and_repr(obj: object) -> Tuple[object, str]:
'''
2-tuple ``(weakref, repr)`` weakly referring to the passed object.
Parameters
----------
obj : object
Arbitrary object to be weakly referred to.
Returns
----------
Tuple[object, str]
2-tuple ``(weakref, repr)`` weakly referring to this object such that:
* ``weakref`` is either:
* If this object supports weak references, a **weak reference** (i.e.,
:class:`weakref.ref` instance) to this object.
* If this object prohibits weak references (e.g., due to being a
common C-based variable-sized container like a tuple or string),
``None``.
* ``repr`` is the machine-readable representation of this object,
truncated to ~10KB to minimize space consumption in the worst case of
an obscenely large object.
'''
# Avoid circular import dependencies.
from beartype._util.text.utiltextrepr import represent_object
# Weak reference to this object if this object supports weak references *OR*
# "None" otherwise (e.g., if this object is a variable-sized container).
obj_weakref = None
# Machine-readable representation of this object truncated to minimize space
# consumption for the worst case of an obscenely large object.
obj_repr = represent_object(
obj=obj,
# Store at most 1KB of the full representation, which should
# certainly suffice for most use cases. Note that the
# default of 96B is far too small to be useful here.
max_len=1000,
)
# If this object is "None", substitute "None" for this non-"None"
# placeholder. Since the "weakref.ref" class ambiguously returns "None" when
# this object has already been garbage-collected, this placeholder enables
# subsequent calls to the get_obj_weakref_or_repr() getter to disambiguate
# between these two common edge cases.
if obj is None:
obj_weakref = _WEAKREF_NONE
# Else, this object is *NOT* "None". In this case...
else:
# Attempt to classify a weak reference to this object for safety.
try:
obj_weakref = weakref_ref(obj)
# If doing so raises a "TypeError", this object *CANNOT* be weakly
# referred to. Sadly, builtin variable-sized C-based types (e.g.,
# "dict", "int", "list", "tuple") *CANNOT* be weakly referred to. This
# constraint is officially documented by the "weakref" module:
# Several built-in types such as list and dict do not directly
# support weak references but can add support through subclassing.
# CPython implementation detail: Other built-in types such as tuple
# and int do not support weak references even when subclassed.
#
# Since this edge case is common, permitting this exception to unwind
# the call stack is unacceptable; likewise, even coercing this exception
# into non-fatal warnings would generic excessive warning spam and is
# thus also unacceptable. The only sane solution remaining is to
# silently store the machine-readable representation of this object and
# return that rather than this object from the "object" property.
except TypeError:
pass
return obj_weakref, obj_repr
def get_weakref_obj_or_repr(obj_weakref: object, obj_repr: str) -> object:
'''
Object weakly referred to by the passed object if this object is indeed a
weak reference to another existing object *or* the passed machine-readable
representation otherwise (i.e., if this object is either ``None`` *or* is a
weak reference to a dead garbage-collected object).
This function is typically passed the pair of objects returned by a prior
call to the companion :func:`make_obj_weakref_and_repr` function.
Parameters
----------
obj_weakref : object
Either:
* If the **referent** (i.e., target object being weakly referred to) is
the ``None`` singleton, the :data:`_WEAKREF_NONE` placeholder.
* Else if the referent supports weak references, a **weak reference**
(i.e., :class:`weakref.ref` instance) to that object.
* Else, ``None``.
obj_repr : str
Machine-readable representation of that object, typically truncated to
some number of characters to avoid worst-case space consumption.
Returns
----------
object
Either:
* If this weak reference is the :data:`_WEAKREF_NONE` placeholder, the
``None`` singleton.
* Else if this referent support weak references, either:
* If this referent is still alive (i.e., has yet to be
garbage-collected), this referent.
* Else, this referent is now dead (i.e., has already been
garbage-collected). In this case, the passed representation.
* Else, this referent does *not* support weak references (i.e., this
weak reference is ``None``). In this case, the passed representation.
Raises
----------
_BeartypeUtilPythonWeakrefException
If ``obj_weakref`` is invalid: i.e., neither ``None``,
:data:`_WEAKREF_NONE`, nor a weak reference.
'''
assert isinstance(obj_repr, str), f'{repr(obj_repr)} not string.'
# If this weak reference is "None", the prior call to
# make_obj_weakref_and_repr() was passed an object that could *NOT* be
# weakly referred to (e.g., C-based container). In this case, fallback to
# the machine-readable representation of that object.
if obj_weakref is None:
return obj_repr
# Else, this weak reference is *NOT* "None".
#
# If this weak reference is "_WEAKREF_NONE", the prior call to
# make_obj_weakref_and_repr() was passed the "None" singleton. In this case,
# substitute this placeholder for "None". See that factory.
elif obj_weakref is _WEAKREF_NONE:
return None
# Else, this weak reference is *NOT* that placeholder.
#
# If this weak reference is *NOT* a weak reference, raise an exception.
elif not isinstance(obj_weakref, weakref_ref):
raise _BeartypeUtilPythonWeakrefException(
f'Weak reference {repr(obj_weakref)} invalid '
f'(i.e., neither weak reference, "None", nor "_WEAKREF_NONE").'
)
# Else, this weak reference is a weak reference.
# Object weakly referred to by this weak reference if this object is alive
# *OR* "None" otherwise (i.e., if this object was garbage-collected).
obj = obj_weakref()
# Return either...
return (
# If this object is still alive, this object;
obj if obj is not None else
# Else, this object is now dead. In this case, the machine-readable
# representation of this object instead.
obj_repr
)
# ....................{ PROPERTIES ~ constants }....................
_WEAKREF_NONE = object()
'''
Singleton substitute for the ``None`` singleton, enabling
:class:`BeartypeCallHintViolation` exceptions to differentiate between weak
references to ``None`` and weak references whose referents are already dead
(i.e., have already been garbage-collected).
'''
|