среда, 22 апреля 2009 г.

А что если не было бы таких вещей как instancemethod...


Иногда мне в голову приходят бредовые идеи, которые однако помогают мне отдыхать от реальных задач, и одна из таких идей - а что если в python`е не было бы таких вещей как instancemethod? И пришлось бы писать примерно в таком духе:

class A(object):

  def hello(self):
       print self, "hello"

a=A()
a.hello(a)
Я понимаю что всемогущий Гвидо может покарать меня молнией за богохульство:), но всё же, это интересная практика (оговорюсь, на конструкторы и дескрипторы сие ужасающее правило не действует).

Шаг первый, напишем дескриптор method, по семантике схожий с instancemethod, который будет оборачивать все методы, объявленные в классе.
import functools

class method(object):

   def __init__(self, func):
       self.func =  func

   def __get__(self, instance, owner):
       return functools.partial(self.func, instance)

тут в методе __get__, мы используем реализацию карринга, из модуля functools (по другому это можно записать так, lambda *args, **kwargs: instance, *args, **kwargs) для связывания первого аргумента с экземпляром класса (тот самый self), и возвращаем уже связанную функцию.

Второе, нам нужна тестовая функция, давайте же напишем её:
def hello(self):
     print self, "hello"

Шаг третий, определить класс в котором с помощью дескриптора объявить наш новый метод:
class A(object):

   hm = method(hello)

попробуем:
>> a=A()
>> a.hello() # и вауля, работает!
<__main__.A object at 0x00......> hello
   

однако с помощью декораторов можно сделать так чтобы метод определялся на месте, а не заворачивался потом:
class A(object):
   @method
   def hello(self):
       print self, "hello"

тут важно понять что method не декоратор в привычном его понимании, в качестве декораторов часто служат функции или классы с методом __call__. Однако python динамический язык, поэтому декоратором может быть всё что отвечает простому требованию - это что-то можно вызвать (для method, это конструктор в который и передаётся функция hello), при декорировании и оно должно вернуть некий результат при вызове - тут происходит вот что, hello заворачивается в дескриптор method, а когда происходит вызов hello срабатывает метод __get__ дескриптора, который в свою очередь возвращает функцию, первый параметр которой уже предопределен. Кстати декораторы classmethod и staticmethod тоже являются дескрипторами.

И так, этот вариант почти идеален, однако нет предела совершенству, для того чтобы всё смотрелось как в настоящем python`е, а не в том что я себе выдумал, осталось только избавится от декоратора, и запись станет привычной. Добиться этого можно с помощью метакласса, который автоматически будет заворачивать все функции определенные в классе в дескриптор method, реализация тривиальна:
import types

class NewObjectMeta(type):

   def __init__(cls, clsname, bases, namespace):
       for name, attr in namespace.iteritems():
           if not issubclass(type(name), types.FunctionType):
               continue
     
           setattr(cls, name, method(attr))

# Для удобства определим класс с уже определенным метаклассом
class NewObject(object):
     __metaclass__ = NewObjectMeta
   

проверим:
class A(NewObject):

   def hello(self):
       print self, "hello"


>> a=A()
>> a.hello()
<__main__.A object hello at 0x00......>
   


всё работает как и ожидалось, хотя если вы замените NewObject, на стандартный object, в своём нормальном питоне, вывод на консоль будет одним и тем же

p.s: Задача разумеется бредовая, но с её помощью можно показать такие вещи как дескритпоры, декораторы и метаклассы, да и практиковатся нужно, как можно чаще :)

Комментариев нет:

Отправить комментарий