Вопрос

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

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

@static(x=0)
def f():
    x += 1
    print x

f() #prints 1
f() #prints 2

Меня не волнует, будет ли реализация static является длинным или хакерским, при условии, что это работает, как описано выше.

Я создал эту версию, но она допускает только <function>.<varname> синтаксис, который довольно быстро становится громоздким из-за более длинных имен функций и переменных.

def static(**assignments):
    def decorate(func):
        for var, val in assignments.items():
            setattr(func, var, val)
        return func
    return decorate

Я думал о разных вещах, но не мог приступить к работе.:

  1. Изменение f (оформленной функции) в вызываемый класс и каким-то образом сохранение статических переменных в self прозрачно.
  2. Изменение глобальных значений f() внутри декоратора и каким-то образом вставка операторов 'global x' в код для f .
  3. Превращаем f в генератор, где мы вручную связываем переменные, а затем выполняем код f напрямую.
Это было полезно?

Решение

Вот декоратор, который, кажется, работает.Обратите внимание, что для этого требуется return locals() в конце функции из-за невозможности установить locals извне (у меня нет большого опыта программирования, поэтому, если есть способ, я его не знаю).

class Static(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, f):
        def wrapped_f():
            try:
                new_kwargs = {}
                for key in self.kwargs:
                    i = getattr(f, key)
                    new_kwargs[key] = i
                self.kwargs = new_kwargs
            except:
                pass
            for key, value in f(**self.kwargs).items():
                setattr(f, key, value)
        return wrapped_f

@Static(x=0, y=5, z='...')
def f(x, y, z):
    x += 1
    y += 5
    print x, y, z
    return locals()

Результатом было бы:

>>> f()
1 10 ...
>>> f()
2 15 ...
>>> f()
3 20 ...

Редактировать:

Я кое-что нашел в http://code.activestate.com/recipes/410698/ и решил попробовать добавить его к этому.Теперь это работает без возврата.

ОТРЕДАКТИРУЙТЕ еще раз:Изменено на, чтобы сделать это на несколько секунд быстрее.Правка 3;изменен на функцию вместо класса

def static(**kwargs):
    def wrap_f(function):
        def probeFunc(frame, event, arg):
            if event == 'call':
                frame.f_locals.update(kwargs)
                frame.f_globals.update(kwargs)
            elif event == 'return':
                for key in kwargs:
                    kwargs[key] = frame.f_locals[key]
                sys.settrace(None)
            return probeFunc
        def traced():
            sys.settrace(probeFunc)
            function()
        return traced
    return wrap_f

проверенный:

@static(x=1)
def f():
    x += 1

global_x = 1
def test_non_static():
    global global_x
    global_x += 1


print 'Timeit static function: %s' % timeit.timeit(f)
print 'Timeit global variable: %s' % timeit.timeit(test_non_static)

выходной сигнал:

Timeit static function: 5.10412869535
Timeit global variable: 0.242917510783

Использование settrace довольно сильно замедляет процесс.

Другие советы

К тому времени, когда ваш декоратор получает объект функции f, он уже скомпилирован - в частности, он был скомпилирован с учетом того, что x является локальным (потому что он назначен с присваиванием +=), обычный оптимизация (в 2.* вы можете победить оптимизацию по невероятной цене, начав exec '' с f.x; в global вы не сможете победить оптимизацию). По сути, чтобы использовать нужный вам синтаксис, вы должны перекомпилировать nonlocal (восстанавливая его источники, если вы знаете, что они будут доступны во время выполнения, или, что намного сложнее, путем взлома байт-кода) с каким-то образом измененными источниками - один раз вы решили пойти по этому пути, простейший подход, вероятно, состоит в том, чтобы заменить __builtin__ на with_static в теле globvar.

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

В любом случае, я отказываюсь от попыток исказить язык слишком далеко от его очевидных замыслов: даже если бы я придумал какой-то хакерский, хрупкий клудж, это, несомненно, было бы несостоятельным. В этом случае желательные намерения Python очень ясны: голые имена, которые связываются внутри функции, являются локальными для этой функции, если явно не обозначены как глобальные - точка. Таким образом, ваша попытка создать голые имена (которые переопределяются внутри функции) означает нечто совершенно иное, чем & Quot; localals & Quot; именно такой вид борьбы.

Изменить . Если вы не хотите настаивать на использовании голых имен для своего " statics " ;, то вдруг вы ты больше не сражаешься с Python, а скорее " идти с зерном " языка (несмотря на проблемы с дизайном staticg], но это отдельная напыщенная речь ;-). Так, например:

class _StaticStuff(object):
  _static_stack = []
  def push(self, d):
    self._static_stack.append(d)
  def pop(self):
    self._static_stack.pop()
  def __getattr__(self, n):
    return self._static_stack[-1][n]
  def __setattr__(self, n, v):
    self._static_stack[-1][n] = v
import __builtin__
__builtin__.static = _StaticStuff()

def with_static(**variables):
  def dowrap(f):
    def wrapper(*a, **k):
      static.push(variables)
      try: return f(*a, **k)
      finally: static.pop()
    return wrapper
  return dowrap

@with_static(x=0)
def f():
    static.x += 1
    print static.x

f()
f()

Это работает так же, как вы хотите, печатая 1, а затем 2. (я использую <=>, чтобы упростить использование <=> для украшения функций, живущих в любом модуле, конечно). У вас может быть несколько разных реализаций, но ключевым моментом любой хорошей реализации является то, что & Quot; статические переменные & Quot; будут квалифицированными именами, не голыми именами, и будет ясно, что они не являются локальными переменными, играют с языком и т. д. (Подобные встроенные контейнеры и квалифицированные имена на их основе, должен использовался в дизайне Python вместо сбоев дизайна <=> и <=>, чтобы указать другие типы переменных, которые не требуются. не локальные и, следовательно, не должны использовать голые имена ... ну, вы можете реализовать специальный <=> контейнер в тех же строках, что и выше <=>, даже без необходимости декорирования, хотя я не очень уверен, что это вполне выполнимо для случая <=> [возможно, с некоторыми украшениями и малейшим количеством черной магии ...; =)]).

Изменить : в комментариях указывается, что указанный код не работает, когда вы украшаете только функцию, которая возвращает замыкание (вместо того, чтобы украшать само замыкание). Правильно: конечно, вы должны украсить конкретную функцию, которая использует <=> (и может быть только одну, по определению функции - <=> переменные!), А не случайную функцию, которая на самом деле не использует <=> а скорее просто в некоторой лексической связи с той, что делает . Например:

def f():
  @with_static(x=0)
  def g():
    static.x += 1
    print static.x
  return g

x = f()
x()
x()

это работает, а перемещение декоратора на <=> вместо <=> - нет (и не могло быть возможно).

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

Вот действительно простое решение, которое работает так же, как обычные статические переменные Python.

def static(**kwargs):
  def wrap(f):
    for key, value in kwargs.items():
      setattr(f, key, value)
    return f
  return wrap

Пример использования:

@static(a=0)
def foo(x):
  foo.a += 1
  return x+foo.a

foo(1)  # => 2
foo(2)  # => 4
foo(14) # => 17

Это более точно соответствует обычному способу создания статических переменных Python

def foo(x):
  foo.a += 1
  return x+foo.a
foo.a = 10

Вы могли бы сделать что-то подобное (но я не тестировал это подробно; использовал CPython 2.6):

import types

def static(**dict):
    def doWrap(func):
        scope = func.func_globals
        scope.update(dict)
        return types.FunctionType(func.func_code, scope)
    return doWrap

# if foo() prints 43, then it's wrong
x = 42

@static(x = 0)
def foo():
   global x
   x += 1
   print(x)

foo() # => 1
foo() # => 2

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

Как насчет этого, без декораторов?

class State(dict):
    """Object interface to dict."""
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError, name

def f(d=State(x=0)):
    d.x += 1
    return d.x

И вот оно в действии:

>>> f()
1
>>> f()
2
>>> f()
3

Вот кое-что, что может быть намного понятнее. Это не требует никаких декораторов или взлома.

class F( object ):
    def __init__( self ):
        self.x= 0
    def __call__( self ):
        self.x += 1
        print self.x

f= F()

Теперь у вас есть функция f со статической переменной.

f() #prints 1
f() #prints 2

Когда вам нужно сохранить состояние между вызовами функции, вам почти всегда лучше использовать генератор / сопрограмму или объект. Поскольку вы хотите использовать & Quot; bare & Quot; имена переменных, тогда вам понадобится версия сопрограммы.

# the coroutine holds the state and yields rather than returns values
def totalgen(x=0, y=0, z=0):
    while True:
       a, b, c = (yield x, y, z)
       x += a
       y += b
       z += c

# the function provides the expected interface to callers
def runningtotal(a, b, c, totalgen=totalgen()):
    try:
        return totalgen.send((a, b, c))    # conveniently gives TypeError 1st time
    except TypeError:
        totalgen.next()                    # initialize, discard results
        return totalgen.send((a, b, c))

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

Небольшая настройка другого ответчика :

def static(**kwargs):
    def decorator(func):
        return type(func)(func.func_code, dict(func.func_globals, **kwargs))
    return decorator

message = "goodbye, world!"
@static(message="hello, world!")
def hello(): print message

hello()

Мне показалось странным переопределить встроенное имя именем аргумента функции, поэтому я изменил **dict на более канонический **kwargs. Я также сохранил несколько строк, и IMO сделал код чище, создав новый тикет с помощью dict(the_original, **the_updates). Наконец, я сохранил несколько строк, обратившись к конструктору функции через type(func), а не к типу импорта --- и объекты класса являются фабричными методами, поэтому используйте их!

Я также удалил global объявление. Это работает до тех пор, пока вы не перепривязываете переменную, т.е. удаление let фактически делает указатель (но не объект) доступным только для чтения. Если вы используете его таким образом, возможно, static - это лучшее имя, чем <=> для привязки, введенной таким образом.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top