Комментарии 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__
дело даже не дошло.
Но вы же так не делаете.
Я и состояние не храню.
Декоратор 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 код, но очень хороший пример».
Вот к чему это приводит:
Выглядит как очень вырожденный случай, которым никто не воспользуется. Будем считать что в моем коде есть минорный баг, куда же без них.
— Абстрактные классы — принято. А если мне требуется, например, форсировать объявление всех свойств инстанса слотами и сделать класс абстрактным?
— Про плагины не убедили.
— Про метаданные тоже. 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.
— Про джангу с ее моделями и формами — наверное да, метаклассы там работают.
Python: метапрограммирование в продакшене. Часть вторая