Как стать автором
Обновить

Комментарии 14

def __get__(self, instance, cls):  
self.instance = instance  
self.cls = cls  
return self  

А не будет ли тут проблем с мутабельностью? Если создать два объекта, и у каждого попытаться получить bound method — не получится ли, что это будет один и тот же метод, привязанный ко второму объекту?


Ну и еще поправка. Интерпретатор помещает в словарь не unbound функции, а простые функции. Unbound function — это отдельная сущность, которая ведет себя немного иначе.


Кстати, вот еще одна проблема. Допустим, у нас есть вот такой класс:


class Foo:
    bar = t1000.terminate

foo = Foo()

Если теперь взять метод foo.bar — он окажется привязан к объекту foo, хотя должен бы к t1000.

А не будет ли тут проблем с мутабельностью?


А какие именно? У двух разных объектов будет 2 разных instance, поэтому привязка должна быть точной.

Интерпретатор помещает в словарь не unbound функции, а простые функции.


Насколько я помню термина unbound function в питоне нет, есть unbound method, но для Python 3 он не актуален, в тексте статьи я написал «unbound функции» подразумевая, что это обычные функции, которые после привязки к объекту станут методами. Может быть стоит переписать этот участок, чтобы он никого не смущал.

Если теперь взять метод foo.bar — он окажется привязан к объекту foo, хотя должен бы к t1000.


Это интересный кейс, спасибо) Но привязка к определенному объекту\классу достаточно сильно усложнит код примера, поэтому я использовал самый простой и короткий вариант. Он конечно оказался с сайд-эффектами.
А какие именно? У двух разных объектов будет 2 разных instance, поэтому привязка должна быть точной.

Зато у них будет одинаковый self. Тот самый, который возвращается из __get__


Насколько я помню термина unbound function в питоне нет, есть unbound method, но для Python 3 он не актуален

Да, вы правы, для Python 3 он не актуален. Тем страньше что вам потребовалось сохранение cls — ведь обычные методы его не сохраняют...

Зато у них будет одинаковый self. Тот самый, который возвращается из __get__

Ну если хранить какое-то состояние в классе PolyMethod, то да, возможны проблемы, но это легко решается созданием каждый раз нового объекта в __get__.

Тем страньше что вам потребовалось сохранение cls — ведь обычные методы его не сохраняют...

classmethod требует объект класса первым параметром.
Ну если хранить какое-то состояние в классе PolyMethod, то да, возможны проблемы, но это легко решается созданием каждый раз нового объекта в __get__.

Но вы же так не делаете.


classmethod требует объект класса первым параметром.

Декоратор classmethod сам этот объект класса туда засовывает. Если этого не происходит — значит, для полиметодов поломался сам механизм декораторов.

Только что проверил вот такой пример:


class Test(metaclass=PolyMeta):
  @classmethod
  def baz(self, x :str):
    pass

Как и ожидалось, classmethod не заработал. Более того, он упал с ошибкой 'classmethod' object has no attribute '__annotations__' в методе add_implementation. До вызова __get__ дело даже не дошло.

Да, спасибо что заметили – код в статье разъехался с конечной его версией, я обновил реализацию PolyMethod.

Но вы же так не делаете.

Я и состояние не храню.

Декоратор classmethod сам этот объект класса туда засовывает.

Только в том случае, если вы вызываете этот метод у класса «CLASS.method», потому что classmethod – это декоратор, который создает хитрый дескриптор. В момент создания PolyMethod-ов, класса еще нет, поэтому и classmethod о нем ничего не знает.
Я и состояние не храню.

А это, блин, что по-вашему?


        self.instance = instance  
        self.cls = cls

Вот к чему это приводит:


class Foo(metaclass=PolyMeta):
    def __init__(self, name):
      self.name = name

    def bar(self, x: str):
      print(self.name)

a = Foo("a").bar
b = Foo("b").bar

a("") #"b", хотя должно быть "a"
b("") #"b"
Только в том случае, если вы вызываете этот метод у класса «CLASS.method», потому что classmethod – это декоратор, который создает хитрый дескриптор. В момент создания PolyMethod-ов, класса еще нет, поэтому и classmethod о нем ничего не знает.

Ну так и надо ему в этом помочь. Вместо этой сложной конструкции из _get_callable_func и двух условий можно сделать вот так:


def __call__(self, arg):
    impl = self.implementations[type(arg)]
    return impl.__get__(self.instance, self.cls).__call__(arg)

А в текущем виде декораторы classmethod и staticmethod вообще никак не влияют на поведение функции — это же неправильно.

Ну так и надо ему в этом помочь.

Этим приведенный вами код и занимается, как и мой. Но ваш вариант более элегантен, спасибо.

А в текущем виде декораторы classmethod и staticmethod вообще никак не влияют на поведение функции — это же неправильно.

Это очевидно, потому что в примере мы заменяем все эти методы на один PolyMethod, который by-design обертка, которую я не старался сделать похожей во всем на оригинальные методы. Задачи такой не было: «Совсем не production-ready код, но очень хороший пример».

Вот к чему это приводит:

Выглядит как очень вырожденный случай, которым никто не воспользуется. Будем считать что в моем коде есть минорный баг, куда же без них.
Нет, это выглядит как уничтожение концепции bound method.
Мета программирование позволяет вмешаться в процесс создания типа данных. Круто. Как быть с кейсами, когда это реально облегчает жизнь?
— Абстрактные классы — принято. А если мне требуется, например, форсировать объявление всех свойств инстанса слотами и сделать класс абстрактным?
— Про плагины не убедили.
— Про метаданные тоже. dir(...) вроде ни кто не отменял? Хотя да, можно запрятать атрибуты от любителей подглядывать в потроха класса в обход его публичного интерфейса.
— Это вообще можно использовать как вопрос на собеседовании:
def get_meta(name, bases, attrs):
    if SOME_SETTING:
        return MetaClass1(name, bases, attrs)
    else:
        return MetaClass2(name, bases, attrs)

class A(metaclass=get_meta):
    pass
Изменится поведение класса A, если в процессе работы изменится значение SOME_SETTING?
— Singletone вполне можно построить и без metaprogramming.
— Про джангу с ее моделями и формами — наверное да, метаклассы там работают.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий