Module Descriptor¶
pcapkit.corekit.module contains tuple
like class ModuleDescriptor,
which is originally designed as tuple[str, str].
- class pcapkit.corekit.module.ModuleDescriptor(module, name)[source]¶
Bases:
ModuleDescriptor,Generic[_T]Module descriptor contains module name and class name, the actual class can be imported by
from module import name.- property klass: Type[_T]¶
Import class from module.
- Raises:
pcapkit.utilities.exceptions.ProtocolError – If
nameisNULL– the caller building this descriptor omitted the class name rather than naming one that turned out wrong – or ifmodulehas no attribute namedname.
Important
The module is read from
sys.modulesfirst, andimportlib.import_module()is entered only when it is not loaded yet. That matters because this property is on a per-frame dispatch path: a next layer code nobody registered falls back to aModuleDescriptorforRawwhichProtocolBase._lookup_next_layerdeliberately does not write back, so every unrecognised frame resolves the same descriptor again – 48 of the 52 resolutions an extraction ofmany_interfaces.pcapngperforms.import_module()keeps real per-call work for an already-imported module (locks,ModuleSpecchecks, thefromlistwalk), so each repeat cost ~436 ns where this property now costs ~117 ns.The class is still read off the module with
getattr()on every access, and nothing is memoised here. That is the point:sys.modulesis the module cache, and it is the only one whose invalidation the interpreter maintains –importlib.reload()rebinds the class in place andsys.modules.pop()drops the entry, both of which this sees immediately. Holding the resolved class instead would serve the pre-reload class forever, and an instance of it failsisinstance()against the live one.
- property name: str | pcapkit.corekit.module.NullType¶
Class name, or
NULLwhen whatever built this descriptor never got one – seeklass.
Auxiliaries¶
- class pcapkit.corekit.module.NullType[source]¶
Bases:
objectType of
NULL, the omitted-class_/modulesentinel.A distinct class rather than a plain
str– which is what the registry helpers inpcapkit.foundation.registry.protocolsandpcapkit.foundation.registry.foundationused to define, independently of each other – so thatiscomparisons against it mean what they say: nostra caller passes, including one that happens to spell'(null)'itself, can compare equal to this sentinel by identity. See GitHub issue #833.Genuinely a singleton, not merely a class this module happens to instantiate once:
__new__()always hands back the one instance that already exists, rather than building a new one, so no caller – direct, orcopy/picklereconstructing an instance behind the scenes – can end up holding a second object that fails anis NULLcheck downstream.ModuleDescriptor.klass()makes exactly that check, and a stricter guard that raises on a second call would be truer to “singleton” in the abstract, but it would also mean the module’s ownNULL = NullType()below is the only call that is ever allowed to succeed – fragile for no real benefit, since nothing here needs rejecting a second construction, only preventing it from producing a distinct object.That still leaves
copy.deepcopy(),copy.copy()andpickleunhandled: none of them constructs a new instance by callingNullType()themselves, so the guard above never runs for them. Each is therefore given its own override below, rather than left to fall back to the default behaviour for a plain object:copy.copy()andcopy.deepcopy()check for__copy__()/__deepcopy__()before ever falling back to reduction, so__deepcopy__()in particular has to be defined – its absence is the actual defect this class used to have: deepcopying aModuleDescriptorrecursed into this sentinel, reduced it, and rebuilt a second, non-identicalNullTypethat then read as an ordinary attribute name togetattr(), downgrading a cleanProtocolErrorinto a bareTypeError(attribute name must be string, not 'NullType').pickleprotocols 2 and up reconstruct throughcls.__new__(cls), which the guarded__new__()already keeps to one instance – but protocols 0 and 1 reconstruct throughcopyreg._reconstructor(), which callsobject.__new__()directly, bypassing__new__()entirely.__reduce__()is defined so that every protocol, not only the ones that happen to go through this class’s own__new__(), is routed through the same module-level getter instead of through reconstruction at all.
A caveat rather than a defect:
importlib.reload()on this module re-executesNULL = NullType()below, producing a second singleton that the reloaded code compares against correctly but that every module which already imported the pre-reloadNULLstill holds – so a comparison spanning the reload sees two “singletons” that are not each other.ModuleDescriptor.klass()faces exactly this class of problem for the class it resolves, which is why it re-readssys.moduleson every call rather than memoising; nothing equivalent is possible here, because unlike a resolved class there is no live registry this sentinel could be re-read from. The pre-#833strsentinel had the same fragility for the same reason – it is a property of sharing one module-level binding across a reload, not something this class’s singleton guarantees claim to solve – and nothing in this package reloadspcapkit.corekit.moduleafter import.
- pcapkit.corekit.module.NULL = <NULL>¶
Sentinel for an omitted
class_argument to theregister_*helpers inpcapkit.foundation.registry.protocolsandpcapkit.foundation.registry.foundation. Defined once, here, rather than once per module: both already importModuleDescriptorfrom this module, so it is the shared home that needs no new module and creates no import cycle.- Type: