PythonProgrammingシニアPython開発者

**Python**のメタクラスで定義された特定のダンダーメソッドは、インスタンスへの`isinstance()`呼び出しをどのように intercept し、このメソッドが候補オブジェクトの任意の属性にアクセスすることで無限再帰の危険が生じるのはなぜですか?

Hintsage AIアシスタントで面接を突破

質問への回答

isinstance(obj, cls)が呼び出されると、Pythontype(cls)__instancecheck__(self, instance)を定義しているかをチェックします。もし定義されていれば、このメタクラスメソッドはメンバーシップを決定し、継承なしで「仮想サブクラス化」を可能にします。実装がhasattr()やドット属性のアクセスをobjに対して使用する場合、危険が発生します。もしobj__getattr__を実装していたり、isinstance()チェックを引き起こすデスクリプタを持っていたりすると、メタクラスメソッドが再度呼び出され、無限再帰が発生します。

実生活の状況

私たちはプラグインが明示的な継承なしにインターフェースを満たす必要がある検証フレームワークを設計しました。Processor ABCを定義し、isinstance(plugin, Processor)pluginprocessメソッドを公開している場合に成功することを望みました。この方法で外部ライブラリ用にダックタイピングをサポートしました。

最初のアプローチでは、すべてのプラグインがProcessorから継承するか、Processor.register()を呼び出す必要がありました。これは型安全でしたが、私たちのユースケースには非現実的でした; これはランタイム生成クラスを禁止し、変更できないサードパーティコードの修正を必要としました。結果として、信頼できないソースからの動的プラグイン発見をサポートできませんでした。

2つ目のアプローチでは、Processorメタクラスに__instancecheck__を実装し、hasattr(candidate, 'process')を使用しました。柔軟でしたが、プラグインがisinstanceを介して戻り値の型を検証するプロパティデコレーターを使用した場合にRecursionErrorが発生し、最初の呼び出しが戻る前にメタクラスメソッドが再度呼び出されました。

私たちは3つ目の解決策を採用しました:object.__getattribute__を使用してデスクリプタロジックをバイパスする形で__instancecheck__を実装しました。type(candidate).__dict__.get('process')をチェックしてそれが呼び出し可能であることを確認することにより、ユーザ定義の__getattr__やプロパティの副作用を発生させずに済みました。これにより、無限再帰のリスクを排除しながら動的ダックタイピングを保持し、フレームワークがソースの変更なしで数千の異種プラグインを安全に統合できるようになりました。

class MetaProcessor(type): def __instancecheck__(cls, candidate): # 安全: __getattr__とデスクリプタをバイパス try: attr = type(candidate).__dict__.get('process') return callable(attr) except Exception: return False class Processor(metaclass=MetaProcessor): pass

候補者がしばしば見逃すこと


なぜisinstance()は第2引数のメタクラスを参照するのか、最初の引数ではないのか?

プロトコルは、チェックされるタイプ(cls)に権限を与え、候補オブジェクト(obj)には与えません。__instancecheck__type(cls)に配置することで、Pythonはクラス定義だけがそのメンバーシップの意味論を制御することを保証します。これにより、オブジェクトがインスタンスチェックを偽装することを防ぎ、型が一方的にインスタンスを構成するものを定義し、セキュリティ感度の高い型チェックの整合性を維持します。


__instancecheck____subclasscheck__の関係は何か、そしてそれらを一貫して保持する理由は何か?

__instancecheck__isinstance()のためにオブジェクトを検証し、__subclasscheck__issubclass()のために型を検証します。__instancecheck__が仮想インスタンス(クラスから継承していないオブジェクト)を受け入れた場合、__subclasscheck__も通常、対応する仮想サブクラスを受け入れなければなりません。これにより、isinstance(obj, cls)issubclass(type(obj), cls)を意味するという不変を保持します。これに違反すると、インスタンスチェックが通過した後のサブクラス関係をチェックする一般的なコンテナコードが失敗します。


__instancecheck__内でgetattr()を使用すると、特に無限再帰がトリガーされる理由は、object.__getattribute__と比較して何か?

getattr(obj, 'name')は完全なデスクリプタプロトコルを呼び出し、属性が欠落している場合にはobj.__getattr__('name')を呼び出す可能性があります。もしobj.__getattr__が遅延ロード、ロギング、あるいは型変換を実装しており、その内部でisinstance(obj, OurClass)を呼び出した場合、メタクラスの__instancecheck__は戻る前に再度呼び出されます。一方、object.__getattribute__(obj, 'name')(またはtype(obj).__dict__の検査)は__getattr__とカスタムデスクリプタを完全にバイパスし、ユーザコードが再帰的に実行されることなく生の実装詳細にアクセスします。