Обсуждение статьи "Не совсем крутой Ruby" зашло достаточно далеко: недостатки и достоинства Ruby между делом перетекали в обсуждение недостатков и достоинств Python. Не сильно удивило то, что передача
Но сначала, давайте всё-таки поговорим о том, почему
Вторая причина не менее важна — это дескрипторы. ООП в 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, никогда, никогда, никогда не делайте этого.