python

Ночные кошмары Питона: неявный `this`

  • пятница, 15 мая 2015 г. в 02:11:14
http://habrahabr.ru/post/257887/

Обсуждение статьи "Не совсем крутой Ruby" зашло достаточно далеко: недостатки и достоинства Ruby между делом перетекали в обсуждение недостатков и достоинств Python. Не сильно удивило то, что передача self в качестве первого аргумента метода класса, некоторым хабравчанам кажется лишней. Ну что ж, не хотите явного self, будет вам неявный this! Под катом, немного магии на чистом Python.

Но сначала, давайте всё-таки поговорим о том, почему self передаётся явным образом. Как мне кажется, причины на то две. Первая — это The Zen of Python, в котором чёрным по белому написано:
Explicit is better than implicit (явное лучше неявного).
Это относится и к передачи данного объекта в метод явным образом, через self.

Вторая причина не менее важна — это дескрипторы. ООП в Python реализован на уровне функций, которые привязываются к объекту динамически посредством механизма дескрипторов (обязательно прочтите статью Руководство к дескрипторам). Итак, вернёмся к функциям: многие ли из нас любят волшебные переменные, через которые могут передаваться аргументы функции? Это например $ в Perl, arguments в JS, func_get_args() в PHP. В Python нет таких волшебных переменных, всё, что передаётся в функцию, передаётся явным образом (в т.ч. и через *args и **kwargs). Так почему же для методов, которые Python обрабатывает как обыкновенные функции, должно быть сделано исключение в виде неявной передачи self?

Однако, в качестве упражнения сделать это совсем несложно. Давайте начнём с простого декоратора:

# Все примеры на Python 3!

def add_this(f):
    def wrapped(self, *args, **kwargs):
        f.__globals__['this'] = self
        return f(*args, **kwargs)
    return wrapped

class C:
    name = 'Alex'

    @add_this
    def say(phrase):
        print("{} says: {}".format(this.name, phrase))

c = C()
c.say('Can you believe it? There is no `self` here!')

На выходе:

Alex says: Can you believe it? There is no `self` here!

Как видите, декоратор add_this добавляет переменную this в область видимости функции, и присваивает ей значение self. Вспомните, что __globals__ — это поле ссылающееся на словарь содержащий глобальные переменные функции, т.е. глобальное пространство имён модуля, в котором эта функция объявлена. Таким образом, вышенаписанный код — это грязнющий хак, добавляющий (и затирающий!) переменную this в глобальное пространство модуля. Всё это подойдёт для наших экспериментов, но упаси вас писать такое в настоящем коде!

Предвкушая комментарии аудитории о том, что так каждую функцию придётся обрамлять в декоратор, предлагаю взвалить эту задачу на плечи метакласса:

import types

class AddThisMeta(type):
    def __new__(cls, name, bases, classdict):
        new_classdict = {
            key: add_this(val) if isinstance(val, types.FunctionType) else val
            for key, val in classdict.items()
        }
        new_class = type.__new__(cls, name, bases, new_classdict)
        return new_class

class D(metaclass=AddThisMeta):
    name = 'Daniel'

    def say(phrase):
        print("{} says: {}".format(this.name, phrase))

    def run():
        print("{} runs away :)".format(this.name))

d = D()
d.say('And now, there is only AddThisMeta!')
d.run()

На выходе:

Daniel says: And now, there is only AddThisMeta!
Daniel runs away :)

Метакласс проходит по всем полям класса и их значениям, выбирает подходящие по типу (важный момент: простая проверка на callable() не подойдёт, т.к. она также сработает для classmethod и staticmethod) и обрамляет эти функции декоратором add_this.

Как вы видите, добавить неявный self (или this) в методы классa совсем не сложно. Но прошу вас, ради всего хорошего, что есть в Python, никогда, никогда, никогда не делайте этого.