Комментарии 18
Замечательная статья, спасибо!
Дык есть же
зачем ещё что-то городить? ;)
PS: для полноты ощущений можно ещё все аттрибуты тоже заглобалить:
Правда записать в атрибуты так ничего не получится, только через this.
import this
зачем ещё что-то городить? ;)
PS: для полноты ощущений можно ещё все аттрибуты тоже заглобалить:
def add_this(f):
def wrapped(self, *args, **kwargs):
f.__globals__.update(self.__class__.__dict__)
f.__globals__.update(self.__dict__)
f.__globals__['this'] = self
return f(*args, **kwargs)
return wrapped
class D(metaclass=AddThisMeta):
name = 'Daniel'
def say(phrase):
print("{} says: {}".format(name, phrase))
Правда записать в атрибуты так ничего не получится, только через this.
вообще, принцип «Explicit is better than implicit» и его реализация в языке — это то, за что стоит низко поклониться ван Россуму и Петерсу
Но иногда так хочется немного синтаксического сахара. Главное не заработать синтаксический сахарный диабет, а также знать что скрывается за конкретным кусочком сахара.
Import hooks вам в помощь.
Это была скорее общая фраза, нежели конкретно про питон. Язык программирования — это инструмент. В каких-то языках программист вынужден писать всё без какого-либо сахара, где-то его вынуждают использовать тонну сахара (иначе засмеют), а где-то этот сахар присутствует ровно в том количестве и в тех местах, что становится приятно, но не приторно.
Да, я понял. Я имел ввиду то, что конкретно в питоне, если очень хочется добавить свой сахар, то это возможно сделать штатными средствами.
Утащил в продакшен. Полёт нормальный, спасибо.
8O
Главное теперь — не делать бекапы.
hsto.org/storage1/b8ef9be1/67c1e381/2ca10012/0cdd913b.jpg
hsto.org/storage1/b8ef9be1/67c1e381/2ca10012/0cdd913b.jpg
На самом деле необходимо не только обновлять this, но и возвращать предыдущее значение при выходе из функции, ибо потом будет неловно, когда после вызова другого метода в вашем методе будет некорректный this
EDIT: сори, уже вижу, что внизу описали эту проблему
EDIT: сори, уже вижу, что внизу описали эту проблему
А как это будет работать, если при рекурсивном вызове?
Я конечно понимаю, что это все не серьезно, но в коде есть баг — обертка, возвращаемая add_this, должна восстанавливать прежнее значение this перед возвратом результата. Если этого не делать, то вызов метода с неявным this внутри другого такого же метода затрет this первого.
Выводит:
Вместо:
Что бы исправить, нужно переписать add_this вот так:
P.S. Да, мне говорили, что я зануда :)
class C:
name = 'Alex'
@add_this
def say(phrase):
print("{} says: {}".format(this.name, phrase))
class Echo:
name = 'Echo'
@add_this
def say(c, phrase):
c.say(phrase)
print("{} says: {}".format(this.name, phrase))
c = C()
e = Echo()
e.say(c, "does it work?")
Выводит:
Alex says: does it work? Alex says: does it work?
Вместо:
Alex says: does it work? Echo says: does it work?
Что бы исправить, нужно переписать add_this вот так:
def add_this(f):
def wrapped(self, *args, **kwargs):
old_this = f.__globals__.pop('this', None)
f.__globals__['this'] = self
result = f(*args, **kwargs)
f.__globals__['this'] = old_this
return result
return wrapped
P.S. Да, мне говорили, что я зануда :)
Вообще‐то для неявного this есть гораздо более безопасное решение: изменение AST (пример: habrahabr.ru/post/153949) и import hooks (либо изменение кода в setup.py перед установкой).
И это потокобезопасно. Ещё можно в декораторе/метаклассе «перекомпилировать» функцию, используя
func.__code__
, но я не вижу для этого стандартных модулей (модуль dis успешно читает func.__code__
, но я не знаю, как превратить изменённый dis.Instruction в байткод). Код будет выглядеть как‐то так:#!/usr/bin/python3.4
import dis
def f(foo):
print(this, foo)
fc = f.__code__
new_code = []
for instruction in dis.get_instructions(fc):
if instruction.opname in {'LOAD_GLOBAL', 'STORE_GLOBAL', 'DELETE_GLOBAL'} and instruction.argval == 'this':
newopname = instruction.opname.replace('GLOBAL', 'FAST')
instruction = dis.Instruction(
opname = newopname,
opcode = dis.opmap[newopname],
arg = 0,
argval = 0,
argrepr = 'this',
offset = instruction.offset,
starts_line = instruction.starts_line,
is_jump_target = instruction.is_jump_target,
)
elif instruction.opname in {'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST'}:
instruction.arg += 1
instruction.argval += 1
# Convert instruction to byte code here.
new_fc = f.__code__.__class__(
fc.co_argcount + 1,
fc.co_kwonlyargcount,
fc.co_nlocals + 1,
fc.co_stacksize,
fc.co_flags,
b''.join(new_code),
fc.co_consts,
fc.co_names,
('this',) + fc.co_varnames,
fc.co_filename,
fc.co_name,
fc.co_firstlineno,
fc.co_lnotab,
fc.co_freevars,
fc.co_cellvars,
)
new_f = f.__class__(new_fc, f.__globals__)
new_f(1, 2)
, но не хватает критического куска на месте «convert instruction to byte code here». Кроме того, байткод официально нестабилен и данный способ может не подходить для не‐CPython реализаций Python.Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Ночные кошмары Питона: неявный `this`