Эмуляция членского теста в Python: правильно делегировать __contains__ в содержащий объект - PullRequest
6 голосов
/ 21 июня 2009

Я привык к тому, что Python позволяет некоторым хитрым трюкам делегировать функциональность другим объектам. Одним из примеров является делегирование содержащихся объектов.

Но кажется, что мне не везет, когда я хочу делегировать __contains __:

class A(object):
    def __init__(self):
       self.mydict = {}
       self.__contains__ = self.mydict.__contains__

a = A()
1 in a

Я получаю:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable

Что я делаю не так? Когда я вызываю .__ содержит __ (1), все идет гладко. Я даже пытался определить метод __iter __ в A, чтобы A выглядел более итеративным, но это не помогло. Что мне здесь не хватает?

1 Ответ

18 голосов
/ 21 июня 2009

Специальные методы, такие как __contains__, являются специальными, только когда они определены в классе, но не в экземпляре (за исключением устаревших классов в Python 2, которые вы должны не использовать в любом случае).

Итак, ваша делегация на уровне класса:

class A(object):
    def __init__(self):
       self.mydict = {}

    def __contains__(self, other):
       return self.mydict.__contains__(other)

Я бы предпочел записать последнее как return other in self.mydict, но это незначительная проблема стиля.

Редактировать : если и когда «абсолютно динамическое перенаправление для каждого экземпляра специальных методов» (например, предлагаемые классы старого стиля) необходимо, это не сложно реализовать с помощью классов нового стиля: вы просто нужно, чтобы каждый экземпляр, который имеет такую ​​особенность, был обернут в свой специальный класс. Например:

class BlackMagic(object):
    def __init__(self):
        self.mydict = {}
        self.__class__ = type(self.__class__.__name__, (self.__class__,), {})
        self.__class__.__contains__ = self.mydict.__contains__

По сути, после небольшой чёрной магии переназначение self.__class__ на новый объект класса (который ведет себя так же, как и предыдущий, но имеет пустой dict и никаких других экземпляров, кроме этого self), где-нибудь в старом -стиль класса, который вы бы присвоили self.__magicname__, вместо этого присвойте self.__class__.__magicname__ (и убедитесь, что это встроенная или staticmethod, а не обычная функция Python, если, конечно, в другом случае вы не хотите, чтобы она получала self при вызове экземпляра).

Между прочим, оператор in в экземпляре этого класса BlackMagic на быстрее , как это бывает, чем с любым из ранее предложенных решений - или, по крайней мере, так я измеряю с моим обычным верным -mtimeit (переходя непосредственно к built-in method, вместо того, чтобы следовать обычным маршрутам поиска, включающим наследование и дескрипторы, снимает немного накладных расходов).

Метакласс для автоматизации идеи self.__class__ -per-instance написать несложно (он может выполнить грязную работу в методе __new__ сгенерированного класса и, возможно, также установить все магические имена, которые фактически назначаются на класс, если он назначен экземпляру, либо через __setattr__, либо через многие свойства many ). Но это было бы оправдано, только если бы потребность в этой функции была действительно широко распространена (например, портирование огромного древнего проекта Python 1.5.2, который свободно использует «специальные методы для каждого экземпляра», на современный Python, включая Python 3).

Рекомендую ли "умные" или "черные магические" решения? Нет, не знаю: почти всегда лучше делать вещи простыми и понятными способами. Но слово «почти» здесь очень важно, и приятно иметь под рукой такие продвинутые «зацепки» для редких, но не несуществующих ситуаций, где их использование действительно оправдано.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...