После 20 лет опыта C ++ я изо всех сил пытаюсь изучить что-то из Python.

Теперь я хотел бы иметь метод (функцию внутри класса), который имеет собственную «статическую» переменную, а не статическую переменную class .

Вероятно, пример псевдокода лучше иллюстрирует то, что я хочу.

class dummy:
    @staticmethod
    def foo():
        foo.counter += 1
        print "You have called me {} times.".format(foo.counter)
    foo.counter = 0

ПРИМЕЧАНИЕ 1. . Я использовал @staticmethod просто для простоты, но это не имеет значения.

ПРИМЕЧАНИЕ 2: . Это происходит с AttributeError: 'staticmethod' object has no attribute 'counter', но, как я уже сказал выше, это псевдокод, поясняющий мою цель.

Я уже узнал, что это работает вне класса:

def foo():
    foo.counter += 1
    print "You have called me {} times.".format(foo.counter)
foo.counter = 0

Но та же самая хитрость, кажется, не работает для функций-членов.

В последнюю минуту информация, я ограничен использованием Python 2.7 (не мой выбор).

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

Некоторые релевантные ссылки

Заранее спасибо.

2
j4x 28 Июн 2019 в 21:46

5 ответов

Лучший ответ

Один из способов добиться этого - убрать переменную в замыкание, чтобы она была статичной для ваших целей. К сожалению, Python 2 не поддерживает ключевое слово nonlocal, поэтому нам нужно обернуть значение нашей переменной в объект (если только вы не хотите ссылка , а не mutate переменная (то есть присвоить переменной) в методе:

In [7]: class _Nonlocal:
   ...:     def __init__(self, value):
   ...:         self.counter = value
   ...:
   ...: def foo_maker():
   ...:     nonlocal = _Nonlocal(0)
   ...:     def foo(self):
   ...:         nonlocal.counter += 1
   ...:         print "You have called me {} times.".format(nonlocal.counter)
   ...:     return foo
   ...:

In [8]: class Dummy(object): #you should always inherit from object explicitely in python 2
   ...:     foo = foo_maker()
   ...:

In [9]: dummy = Dummy()

In [10]: dummy.foo()
You have called me 1 times.

In [11]: dummy.foo()
You have called me 2 times.

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

In [35]: import types
    ...:
    ...: class Foo(object):
    ...:     def __init__(this):
    ...:         this.counter = 0
    ...:     def __call__(this, self):
    ...:         this.counter += 1
    ...:         print "You have called me {} times.".format(this.counter)
    ...:         print "here is some instance state, self.bar: {}".format(self.bar)
    ...:     def __get__(this, obj, objtype=None):
    ...:         "Simulate func_descr_get() in Objects/funcobject.c"
    ...:         if obj is None:
    ...:             return this
    ...:         return types.MethodType(this, obj)
    ...:

In [36]: class Dummy(object): #you should always inherit from object explicitely in python 2
    ...:     foo = Foo()
    ...:     def __init__(self):
    ...:         self.bar = 42
    ...:

In [37]: dummy = Dummy()

In [38]: dummy.foo()
You have called me 1 times.
here is some instance state, self.bar: 42

In [39]: dummy.bar = 99

In [40]: dummy.foo()
You have called me 2 times.
here is some instance state, self.bar: 99

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

Обратите внимание, я использовал this в качестве имени первого аргумента, чтобы избежать путаницы с self, который фактически будет получен из объекта, с которым Foo получает привязку как метод.

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

1
juanpa.arrivillaga 28 Июн 2019 в 20:34

Я уже опубликовал это в старом посте, но никто не заметил это

Поскольку у меня другая идиоматическая цель со статическими переменными, я хотел бы раскрыть следующее: В функции я хочу инициализировать переменную только один раз с вычисленным значением, которое может быть немного дорогостоящим. Как я люблю красиво писать и быть старым программистом в стиле C. Я попытался определить макросоподобное письмо:

def  Foo () :
   StaticVar( Foo, ‘Var’, CalculateStatic())
   StaticVar( Foo, ‘Step’, CalculateStep())
   Foo.Var += Foo.Step
   print(‘Value of Var : ‘, Foo.Var)

Затем я написал «StaticVar» следующим образом:

  def StaticVar(Cls, Var, StaticVal) :
     if not hasattr(Cls, Var) :
        setattr(Cls, Var, StaticVal)

Я даже могу написать более приятный код на Python:

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        if not hasattr(Cls, Var) :
            setattr(Cls, Var, StaticVal)

  def  Foo () :
      StaticVars( Foo, Var = CalculateStatic(),Step= CalculateStep()))
      Foo.Var += Foo. Step
      print(‘Value of Var : ‘, Foo.Var)

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

def CalculateStatic() :
    print("Costly Initialization")
    return 0

def CalculateStep() :
    return 2

def Test() :
    Foo()
    Foo()
    Foo()

>>> Test()
Costly Initialization
Value of Var : 2
Costly Initialization
Value of Var : 4
Costly Initialization
Value of Var : 6

Чтобы достичь своей цели, я бы лучше написал что-то вроде этого:

def  Foo () :
    if not hasattr(Foo, ‘Var’) :
        setattr ( Foo, ‘Var’, CalculateStatic())
        setattr ( Foo, ‘Step’, CalculateStep())

    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

>>> Test()
Costly Initialization
Value of Var : 2
Value of Var : 4
Value of Var : 6

И это может быть «красиво написано», как это (я использовал символ подчеркивания со ссылкой на «private == static»):

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        setattr(Cls, Var, StaticVal)

def  Foo () :
    _ = Foo
    try :
        __ = _.Var
    except AttributeError : # The above code could only generate AttributeError Exception
                    # the following code is executed only once
        StaticDefVars(_, Var= CalculateStatic(), Step = CalculateStep())

    _.Var += _. Step
    print(‘Value of Var : ‘, Foo.Var)

Следует обратить внимание на то, чтобы не вводить «код вычисления» в предложение «try», что может вызвать дополнительное исключение «AttributeError».

Конечно, если бы у Python была «макропроцессорная обработка», это было бы еще лучше »

0
Farazdak 6 Июл 2019 в 19:38

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

class Dummy:

    @staticmethod
    def foo(_counter=[0]):   # here using a list, but you could use a dictionary, or a deque
        _counter[0] += 1
        print "You have called me {} times.".format(_counter[0])

Обоснование состоит в том, что эта переменная инициализируется только один раз; его последнее значение остается в образовании замыкания.

1
Reblochon Masque 29 Июн 2019 в 00:50

Как уже упоминалось @Prune, реального способа сделать это не существует.

Однако, если вы хотите, чтобы статическая переменная внутри метода была доступна только для объекта, которому она принадлежит (насколько я помню, в C ++), вы должны определить ее в конструкторе или как переменную класса с не статический метод:

from __future__ import print_function

class dummy:
    def __init__(self, counter=0):
        self._foo_counter = 0

    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

Или же:

class dummy:
    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

    _foo_counter = 0

Таким образом, работает:

x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

Результаты в:

You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.

Обратите внимание, что две версии не ведут себя точно так же. Когда вы определите _foo_counter непосредственно в классе, у вас будет доступ к переменной _foo_counter как для объекта (self._foo_counter), так и для самого класса (dummy._foo_counter). dummy._foo_counter будет статическим для каждого использования класса и будет сохраняться для нескольких экземпляров класса, а также для нескольких объектов. Это также единственная переменная, к которой вы можете получить доступ, если используете @staticmethod декоратор в dummy.foo():

class dummy:
    @staticmethod
    def foo():
        dummy._foo_counter += 1
        print("You have called me {} times.".format(dummy._foo_counter))

    _foo_counter = 0

Здесь self или _foo_counter не будут доступны, и вы можете использовать единственную переменную для всего класса dummy._foo_counter (которая, как уже упоминалось, может также использоваться с методами, не украшенными @staticmethod).

Так что опять бегом

x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

Результаты в:

You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 5 times.
You have called me 6 times.
You have called me 7 times.
You have called me 8 times.
1
norok2 28 Июн 2019 в 19:53

Нет, нет Вы уже нашли версию Python: переменную класса, к которой вы, верховный повелитель разработки класса dummy, будете иметь доступ только внутри функции foo.

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

2
Prune 28 Июн 2019 в 18:53