Воскресный батхерт-разогрев перед началом рабочей недели. Очередной рассказ про «слабую логическую связанность, составленность из груды костылей, неподдатливость расширению», то есть " питоничность".
Как вы декорируете блоки кода в Python? Ну то есть все мы знаем, что можно декорировать функцию:
@decorator
def funcname(arg):
    passи даже декорировать класс:
@decorator
class classname:
    def __init__(self):
        pass
Но вот беда — как передать кусок кода в другую функцию? Вечная проблема лямбды в питоне, которая не решена до сих пор и никогда не будет решена:
>>> decorator(lambda: a = 2)
  File "<stdin>", line 1
SyntaxError: lambda cannot contain assignmentМне стыдно, но я до сих пор не подозревал, что в лямбдах нельзя использовать присваивание. Я подозреваю, что я не один такой, на самом деле, потому что лямбды почти никто не использует — в том числе по этой причине.
«Хорошо, наш язык говно, но давайте не отчаиваться, с этим что-то можно сделать» — сказал когда-то Гвидо, и предложил:
https://www.python.org/dev/peps/pep-0343/ — PEP 343 — The «with» Statement
Этот чудесный костыль позволяет вам обрамить любой блок кода процедурами инициализации и финализации. Проблема — что мне делать, если я хочу обрамить блок кода сложнее, например, чтобы он оказался в цикле? То есть
for filename in filenames:
   with try_infinitely():
      print(open(filename, 'r').readlines())В данном случае код должен пытаться бесконечно открывать и читать файл, пока не сможет это сделать. На питоне это можно написать как:
for filename in filenames:
    while True:
        try:
            print(open(filename, 'r').readlines())
        except Exception as e:
            print(e)
        else:
            break;Но писать такую стенку каждый раз весьма утомительно. Как и заставлять юзверя объявлять вложенную функцию и передавать ее функции-декоратору, поскольку это будет нарушать обычную читаемость кода из-за того, что код вложенной функции будет выполняться позже своего объявления, в отличие от тех же лямбд JS, которые выполняются там, где определены. Чтобы была чуть более очевидна блевотность такого кода, я специально добавил цикл for filename in filenames снаружи:
for filename in filenames:
    @try_infinitely
    def nestedfunc():
        print(open(filename, 'r').readlines())
    nestedfunc()или
@try_infinitely
def nestedfunc():
    print(open(filename, 'r').readlines())
for filename in filenames:
    nestedfunc()Последнее, к моему удивлению, работает, потому что области видимости в питоне поделены на уровне функций, а не блоков, и потому filename будет видима в любом месте функции.
А хотелось бы чего-то простого и понятного, плана:
for filename in filenames:
    @try_infinitely:
        print(open(filename, 'r').readlines())Чувствуете, как код сразу стал намного проще? Единственный выход, который я пока что вижу — это городить свой DSL через
source = inspect.get_source(func)
ast.parse(source, func.__code__.co_filename, 'exec')
К сожалению, питон из коробки не позволяет кормить AST декоратору, а сам AST он нигде не хранит, потому остается только парсить сырцы.







