PythonProgrammatieSenior Python Ontwikkelaar

Welke specifieke dunder-methode gedefinieerd in een **Python** metaklasse onderschept `isinstance()`-oproepen die gericht zijn op zijn instanties, en welk oneindig recursiegevaar ontstaat er als deze methode willekeurige attributen op het kandidaatobject benadert?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord op de vraag.

Wanneer isinstance(obj, cls) wordt aangeroepen, controleert Python of type(cls) __instancecheck__(self, instance) definieert. Als dit aanwezig is, bepaalt deze metaklasse-methode het lidmaatschap en maakt "virtuele subclassing" zonder overerving mogelijk. Het gevaar ontstaat wanneer de implementatie hasattr() of puntattributen gebruikt op obj; als obj __getattr__ of descriptors implementeert die isinstance()-controles activeren, komt de metaklasse-methode opnieuw binnen, waardoor ongebonden recursie ontstaat.

Situatie uit het leven

We hebben een validatiekader ontworpen waarbij plugins moesten voldoen aan interfaces zonder expliciete overerving. We hebben een Processor ABC gedefinieerd en wilden dat isinstance(plugin, Processor) succesvol was als plugin een process-methode blootstelde, ter ondersteuning van duck typing voor externe bibliotheken.

De eerste benadering vereiste dat alle plugins van Processor moesten erven of Processor.register() moesten aanroepen. Dit was type-veilig maar impractisch voor onze gebruiksgeval; het verhinderde runtime-ge genereerde klassen en vereiste wijzigingen in derde-partijcode die we niet konden wijzigen. Daarom faalde het om dynamische plugin-ontdekking van onbetrouwbare bronnen te ondersteunen.

De tweede benadering implementeerde __instancecheck__ op de Processor metaklasse met hasattr(candidate, 'process'). Hoewel flexibel, crashte dit met RecursionError wanneer een plugin een property-decorator gebruikte die zijn retourtype valideerde via isinstance, waardoor de metaklasse-methode opnieuw werd aangeroepen voordat de eerste oproep terugkeerde.

We hebben een derde oplossing aangenomen: __instancecheck__ implementeren met object.__getattribute__ om de descriptorlogica te omzeilen. Door type(candidate).__dict__.get('process') te controleren en te verifiëren of deze aanroepbaar is, vermeden we het activeren van door de gebruiker gedefinieerde __getattr__ of property-side-effecten. Dit elimineerde het risico op recursie terwijl het dynamische duck typing behield, waardoor het kader veilig duizenden heterogene plugins kon integreren zonder bronwijziging.

class MetaProcessor(type): def __instancecheck__(cls, candidate): # Veilig: omzeilt __getattr__ en descriptors try: attr = type(candidate).__dict__.get('process') return callable(attr) except Exception: return False class Processor(metaclass=MetaProcessor): pass

Wat kandidaten vaak missen


Waarom raadpleegt isinstance() de metaklasse van het tweede argument in plaats van het eerste?

Het protocol wijst de autoriteit toe aan het type dat wordt gecontroleerd (cls), niet het kandidaatobject (obj). Door __instancecheck__ op type(cls) te plaatsen, zorgt Python ervoor dat alleen de klassedefinitie de lidmaatschapssemantiek beheerst. Dit voorkomt dat objecten instantiecontroles vervalsen; het type definieert unilateraal wat zijn instantie vormt, waardoor de integriteit in beveiligingsgevoelige typecontroles wordt behouden.


Wat is de relatie tussen __instancecheck__ en __subclasscheck__, en waarom moeten ze consistent blijven?

__instancecheck__ valideert objecten voor isinstance(), terwijl __subclasscheck__ types valideert voor issubclass(). Als __instancecheck__ virtuele instanties accepteert (objecten die niet van de klasse erven), moet __subclasscheck__ doorgaans de bijbehorende virtuele subclasses accepteren om de invariant te behouden dat isinstance(obj, cls) impliceert issubclass(type(obj), cls). Het schenden hiervan zorgt ervoor dat generieke containercode faalt wanneer deze subclassrelaties controleert na het passeren van instantiecontroles.


Hoe activeert het gebruik van getattr() binnen __instancecheck__ specifiek oneindige recursie in vergelijking met object.__getattribute__?

getattr(obj, 'name') roept het volledige descriptorprotocol aan en kan obj.__getattr__('name') aanroepen als het attribuut ontbreekt. Als obj.__getattr__ lazy-loading, logging of type coercion implementeert die intern isinstance(obj, OurClass) aanroept, komt de metaklasse __instancecheck__ opnieuw binnen voordat het terugkeert. In tegenstelling tot object.__getattribute__(obj, 'name') (of het inspecteren van type(obj).__dict__), omzeilt het __getattr__ en aangepaste descriptors volledig, waardoor ruwe implementatiedetails worden benaderd zonder dat gebruikerscode wordt geactiveerd die mogelijk recursief is.