Эмуляция теста членства в Python:правильное делегирование __contains__ содержащемуся объекту
-
06-07-2019 - |
Вопрос
Я привык к тому, что 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
Что я делаю неправильно?Когда я вызываю .__contains __(1), все проходит гладко.Я даже пытался определить метод __iter__ в A, чтобы A выглядел как итерируемый, но это не помогло.Что мне здесь не хватает?
Решение
Специальные методы, такие как __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__
к новому объекту класса (который ведет себя так же, как предыдущий, но имеет пустой словарь и никаких других экземпляров, кроме этого self
), в любом месте класса старого стиля, который вы бы назначили self.__magicname__
, назначить в self.__class__.__magicname__
вместо этого (и убедитесь, что это встроенный или staticmethod
, а не обычная функция Python, если, конечно, в каком-то другом случае вы не хотите, чтобы она получала self
при вызове на экземпляре).
Кстати, in
оператор в экземпляре этого BlackMagic
класс это Быстрее, как это бывает, чем с любым из ранее предложенных решений - или, по крайней мере, я так измеряю своим обычным верным -mtimeit
(переход непосредственно к built-in method
, вместо следования обычным маршрутам поиска, включающим наследование и дескрипторы, это немного сокращает накладные расходы).
Метакласс для автоматизации self.__class__
Идею -per-instance не составит труда написать (она может выполнить грязную работу в сгенерированном классе). __new__
и, возможно, также установить все магические имена для фактического назначения классу, если они назначены на экземпляре, либо через __setattr__
или многие, много характеристики).Но это было бы оправдано только в том случае, если бы потребность в этой функции была действительно широко распространенной (например,перенос огромного древнего проекта Python 1.5.2, в котором широко используются «специальные методы для каждого экземпляра», на современный Python, включая Python 3).
Я рекомендовать «умные» или «черные магические» решения?Нет, я не:почти всегда лучше делать что-то простым и понятным способом.Но «почти» здесь является важным словом, и приятно иметь под рукой такие продвинутые «крючки» для редких, но не несуществующих ситуаций, когда их использование действительно может быть оправдано.