Могу ли я использовать декоратор для изменения локальной области функции в Python? - PullRequest
8 голосов
/ 26 февраля 2009

Есть ли способ написания декоратора, который бы работал следующим образом?

assert 'z' not in globals()

@my_decorator
def func(x, y):
   print z

РЕДАКТИРОВАТЬ: перенесено из anwser

В ответ на хоп "почему?": Синтаксис sugar / DRY.

Речь идет не о кэшировании, а о вычислении z (и z1, z2, z3, ...) на основе значений x & y.

У меня есть много функций, которые делают связанные вещи, и я не хочу, чтобы я написал

z1, z2, z3=calculate_from(x, y)

в начале каждой функции - где-то я ошибаюсь. Если бы это было c, я бы сделал это с помощью cpp (если бы это был lisp, я бы сделал это с макросами ...), но я хотел посмотреть, могут ли декораторы сделать то же самое.

Если бы это помогло, я бы почти наверняка назвал декоратор «precalculate_z», и он определенно не был бы частью какого-либо публичного API.

Вероятно, я мог бы получить аналогичный эффект от использования инфраструктуры классов, но я хотел посмотреть, выполнимо ли это с необработанными функциями.

Ответы [ 7 ]

11 голосов
/ 26 февраля 2009

Отголосок ответа Хопа

  1. Не делай этого.
  2. Серьезно, не делай этого. Lisp и Ruby - более подходящие языки для написания собственного синтаксиса. Используйте один из них. Или найди более чистый способ сделать это
  3. Если необходимо, вы хотите динамические переменные в области, а не в лексической области.

Python не имеет динамически изменяемых переменных, но вы можете имитировать его. Вот пример, который имитирует его путем создания глобальной привязки, но восстанавливает предыдущее значение при выходе:

http://codepad.org/6vAY8Leh

def adds_dynamic_z_decorator(f):
  def replacement(*arg,**karg):
    # create a new 'z' binding in globals, saving previous
    if 'z' in globals():
      oldZ = (globals()['z'],)
    else:
      oldZ = None
    try:
      globals()['z'] = None
      #invoke the original function
      res = f(*arg, **karg)
    finally:
      #restore any old bindings
      if oldZ:
        globals()['z'] = oldZ[0]
      else:
        del(globals()['z'])
    return res
  return replacement

@adds_dynamic_z_decorator
def func(x,y):
  print z

def other_recurse(x):
  global z
  print 'x=%s, z=%s' %(x,z)
  recurse(x+1)
  print 'x=%s, z=%s' %(x,z)

@adds_dynamic_z_decorator
def recurse(x=0):
  global z
  z = x
  if x < 3:
    other_recurse(x)

print 'calling func(1,2)'
func(1,2)

print 'calling recurse()'
recurse()

Я не даю никаких гарантий в отношении полезности или вменяемости вышеуказанного кода. На самом деле, я гарантирую, что это безумие , и вы должны избегать его использования, если только вы не хотите порку от своих коллег по Python.

Этот код аналогичен как коду eduffy, так и к коду Джона Монтгомери, но гарантирует, что z создается и правильно восстанавливается «как» локальной переменной - например, обратите внимание, как «other_recurse» может видеть привязку для 'z', указанного в теле 'recurse'.

8 голосов
/ 26 февраля 2009

Я не знаю о локальной области видимости, но вы могли бы временно предоставить альтернативное глобальное пространство имен. Что-то вроде:



import types

def my_decorator(fn):
    def decorated(*args,**kw):
        my_globals={}
        my_globals.update(globals())
        my_globals['z']='value of z'
        call_fn=types.FunctionType(fn.func_code,my_globals)
        return call_fn(*args,**kw)
    return decorated

@my_decorator
def func(x, y):
    print z

func(0,1)


Который должен печатать "значение z"

7 голосов
/ 26 февраля 2009

а) не делай этого.

б) серьезно, зачем ты это делаешь?

в) вы можете объявить z как глобальный в вашем декораторе, так что z не будет в globals () до тех пор, пока декоратор не будет вызван впервые, поэтому assert не будет лаять.

г) почему ???

2 голосов
/ 26 февраля 2009

Сначала я повторю «пожалуйста, не надо», но это ваш выбор. Вот решение для вас:

assert 'z' not in globals ()

class my_dec:
    def __init__ (self, f):
        self.f = f
    def __call__ (self,x,y):
        z = x+y
        self.f(x,y,z)

@my_dec
def func (x,y,z):
    print z

func (1,3)

Требуется z в формальных параметрах, но не фактические.

1 голос
/ 27 февраля 2009

Явное лучше, чем неявное.

Это достаточно хорошо?

def provide_value(f):
    f.foo = "Bar"
    return f

@provide_value
def g(x):
    print g.foo

(Если вы действительно хотите зла, назначение f.func_globals кажется забавным.)

1 голос
/ 26 февраля 2009

Вероятно, я мог бы получить аналогичный эффект от использования инфраструктуры классов, но я хотел посмотреть, выполнимо ли это с необработанными функциями.

Ну, Python - это объектно-ориентированный язык. Вы должны сделать это в классе, по моему мнению. Создание хорошего интерфейса класса, безусловно, упростит вашу проблему. Это не , для чего были созданы декораторы.

0 голосов
/ 27 февраля 2009

Другие предложили несколько способов создания работающего декоратора, многие советуют не делать этого, потому что он настолько стилистически отличается от обычного поведения python, что действительно запутает любого, кто пытается понять код.

Если вам нужно много пересчитывать, имеет ли смысл сгруппировать их в объект? Вычислите z1 ... zN в конструкторе, тогда функции, которые используют эти значения, могут получить доступ к предварительно вычисленным ответам как части экземпляра.

...