среда, 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: Задача разумеется бредовая, но с её помощью можно показать такие вещи как дескритпоры, декораторы и метаклассы, да и практиковатся нужно, как можно чаще :)

1 комментарий:

  1. MGM Resorts agrees to sell Las Vegas Strip casino
    MGM Resorts International 고양 출장샵 and 동두천 출장샵 MGM Resorts 창원 출장안마 International announced on 공주 출장마사지 Monday that it has agreed 당진 출장샵 to sell the remaining land in Las Vegas, Jan 6, 2021

    ОтветитьУдалить