Ночные кошмары Питона: неявный `this`
Обсуждение статьи «Не совсем крутой 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, никогда, никогда, никогда не делайте этого :)