簡體   English   中英

function 中的 static 變量的 Python 是什么?

[英]What is the Python equivalent of static variables inside a function?

此 C/C++ 代碼的慣用 Python 等價物是什么?

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

具體來說,如何在 function 級別而不是 class 級別實施 static 成員? 將 function 放入 class 會改變什么嗎?

有點顛倒,但這應該有效:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

如果您希望計數器初始化代碼位於頂部而不是底部,則可以創建一個裝飾器:

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

然后使用這樣的代碼:

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

它仍然需要您使用foo. 前綴,不幸的是。

(信用: @ony

您可以向函數添加屬性,並將其用作靜態變量。

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

或者,如果您不想在函數外設置變量,您可以使用hasattr()來避免AttributeError異常:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1

無論如何,靜態變量是相當少見的,你應該為這個變量找到一個更好的地方,最有可能是在一個類中。

還可以考慮:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

推理:

  • 很多pythonic(“請求寬恕而不是許可”)
  • 使用異常(只拋出一次)而不是if分支(想想StopIteration異常)

許多人已經建議測試“hasattr”,但有一個更簡單的答案:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

沒有try/except,沒有測試hasattr,只是getattr 使用默認值。

其他答案已經證明了您應該這樣做的方式。 這里有一種你不應該的方式:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

默認值僅在第一次計算函數時初始化,而不是每次執行時初始化,因此您可以使用列表或任何其他可變對象來存儲靜態值。

這是一個完全封裝的版本,不需要外部初始化調用:

def fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)

在 Python 中,函數是對象,我們可以通過特殊屬性__dict__簡單地向它們添加或猴子補丁成員變量。 內置vars()返回特殊屬性__dict__

編輯:注意,與替代try:except AttributeError答案不同,使用這種方法,變量將始終為初始化后的代碼邏輯做好准備。 我認為try:except AttributeError替代以下內容將不那么 DRY 和/或有尷尬的流程:

def Fibonacci(n):
   if n<2: return n
   Fibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cache
   return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2)) # lookup result in cache, if not available then calculate and store it

EDIT2:當從多個位置調用函數時,我只推薦上述方法。 如果該函數僅在一處調用,則最好使用nonlocal

def TheOnlyPlaceStaticFunctionIsCalled():
    memo={}
    def Fibonacci(n):
       nonlocal memo  # required in Python3. Python2 can see memo
       if n<2: return n
       return memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
    ...
    print (Fibonacci(200))
    ...

Python 沒有靜態變量,但您可以通過定義一個可調用的類對象然后將其用作函數來偽造它。 另請參閱此答案

class Foo(object):
  # Class variable, shared by all instances of this class
  counter = 0

  def __call__(self):
    Foo.counter += 1
    print Foo.counter

# Create an object instance of class "Foo," called "foo"
foo = Foo()

# Make calls to the "__call__" method, via the object's name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3

請注意, __call__使類(對象)的實例可通過其自己的名稱進行調用。 這就是上面調用foo()調用類的__call__方法的原因。 從文檔

通過在類中定義__call__()方法,可以使任意類的實例可調用。

使用生成器函數生成迭代器。

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

然后像這樣使用它

foo = foo_gen().next
for i in range(0,10):
    print foo()

如果你想要一個上限:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

如果迭代器終止(如上面的例子),你也可以直接循環遍歷它,比如

for i in foo_gen(20):
    print i

當然,在這些簡單的情況下,最好使用 xrange :)

這是關於yield 語句的文檔。

其他解決方案將計數器屬性附加到函數,通常使用復雜的邏輯來處理初始化。 這不適用於新代碼。

在 Python 3 中,正確的方法是使用nonlocal語句:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

有關nonlocal語句的規范,請參閱PEP 3104

如果計數器是模塊私有的,則應將其命名為_counter

使用函數的屬性作為靜態變量有一些潛在的缺點:

  • 每次要訪問變量時,都必須寫出函數的全名。
  • 外部代碼可以輕松訪問該變量並弄亂該值。

第二個問題的慣用 python 可能會用前導下划線命名變量,以表明它不打算被訪問,同時在事后保持可訪問。

使用閉包

另一種選擇是使用詞法閉包的模式,python 3 中的nonlocal關鍵字支持這種模式。

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

可悲的是,我不知道如何將此解決方案封裝到裝飾器中。

使用內部狀態參數

另一種選擇可能是作為可變值容器的未記錄參數。

def counter(*, _i=[0]):
    _i[0] += 1
    return _i[0]

這是有效的,因為默認參數是在定義函數時計算的,而不是在調用時計算的。

更清潔的可能是使用容器類型而不是列表,例如

def counter(*, _i = Mutable(0)):
    _i.value += 1
    return _i.value

但我不知道一個內置類型,它清楚地傳達了目的。

_counter = 0
def foo():
   global _counter
   _counter += 1
   print 'counter is', _counter

Python 通常使用下划線來表示私有變量。 在 C 中在函數內部聲明靜態變量的唯一原因是將它隱藏在函數之外,這並不是真正慣用的 Python。

def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

就像上面 vincent 的代碼一樣,這將用作函數裝飾器,並且必須以函數名作為前綴訪問靜態變量。 這段代碼的優點(盡管無可否認,任何人都可能足夠聰明來弄清楚)是您可以擁有多個靜態變量並以更傳統的方式初始化它們。

更具可讀性,但更冗長(Python 之禪:顯式優於隱式):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

有關其工作原理的說明,請參見此處

在嘗試了幾種方法后,我最終使用了@warvariuc 答案的改進版本:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)

Python 方法中的靜態變量

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3

另一個(不推薦!)對可調用對象的扭曲,如https://stackoverflow.com/a/279598/916373 ,如果您不介意使用時髦的調用簽名,則可以這樣做

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2

溶液 n +=1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count

全局聲明提供了此功能。 在下面的示例中(python 3.5 或更高版本使用“f”),計數器變量是在函數之外定義的。 在函數中將其定義為全局意味着函數外部的“全局”版本應該可用於該函數。 因此,每次函數運行時,它都會修改函數外的值,將其保留在函數之外。

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"

慣用的方法是使用一個,它可以有屬性。 如果您需要實例不分開,請使用單例。

有多種方法可以將“靜態”變量偽造或混入 Python(目前未提及的一種方法是具有可變的默認參數),但這不是Pythonic 慣用的方法。 只需使用一個類。

或者可能是生成器,如果您的使用模式適合。

這個問題的提示下,我可以提出另一種可能更好用的替代方案,並且對於方法和函數看起來都一樣:

@static_var2('seed',0)
def funccounter(statics, add=1):
    statics.seed += add
    return statics.seed

print funccounter()       #1
print funccounter(add=2)  #3
print funccounter()       #4

class ACircle(object):
    @static_var2('seed',0)
    def counter(statics, self, add=1):
        statics.seed += add
        return statics.seed

c = ACircle()
print c.counter()      #1
print c.counter(add=2) #3
print c.counter()      #4
d = ACircle()
print d.counter()      #5
print d.counter(add=2) #7
print d.counter()      #8    

如果你喜歡這種用法,這里是實現:

class StaticMan(object):
    def __init__(self):
        self.__dict__['_d'] = {}

    def __getattr__(self, name):
        return self.__dict__['_d'][name]
    def __getitem__(self, name):
        return self.__dict__['_d'][name]
    def __setattr__(self, name, val):
        self.__dict__['_d'][name] = val
    def __setitem__(self, name, val):
        self.__dict__['_d'][name] = val

def static_var2(name, val):
    def decorator(original):
        if not hasattr(original, ':staticman'):    
            def wrapped(*args, **kwargs):
                return original(getattr(wrapped, ':staticman'), *args, **kwargs)
            setattr(wrapped, ':staticman', StaticMan())
            f = wrapped
        else:
            f = original #already wrapped

        getattr(f, ':staticman')[name] = val
        return f
    return decorator

您始終可以創建所謂的“函數對象”並為其提供標准(非靜態)成員變量,而不是創建具有靜態局部變量的函數。

既然你給出了一個用 C++ 編寫的例子,我將首先解釋什么是 C++ 中的“函數對象”。 “函數對象”只是具有重載operator()任何類。 類的實例將表現得像函數。 例如,你可以寫int x = square(5); 即使square是一個對象(帶有重載的operator() )並且在技術上不是“函數”。 您可以為函數對象提供您可以為類對象提供的任何功能。

# C++ function object
class Foo_class {
    private:
        int counter;     
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d\n", counter);
        }     
   };
   Foo_class foo;

在 Python 中,我們也可以重載operator()只是該方法被命名為__call__

這是一個類定義:

class Foo_class:
    def __init__(self): # __init__ is similair to a C++ class constructor
        self.counter = 0
        # self.counter is like a static member
        # variable of a function named "foo"
    def __call__(self): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
foo = Foo_class() # call the constructor

這是正在使用的類的示例:

from foo import foo

for i in range(0, 5):
    foo() # function call

打印到控制台的輸出是:

counter is 1
counter is 2
counter is 3
counter is 4
counter is 5

如果你想讓你的函數接受輸入參數,你也可以將它們添加到__call__中:

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -

class Foo_class:
    def __init__(self):
        self.counter = 0
    def __call__(self, x, y, z): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
        print("x, y, z, are %d, %d, %d" % (x, y, z));
foo = Foo_class() # call the constructor

# FILE: main.py - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

from foo import foo

for i in range(0, 5):
    foo(7, 8, 9) # function call

# Console Output - - - - - - - - - - - - - - - - - - - - - - - - - - 

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9

這個答案建立在@claudiu 的答案之上。

我發現每當我打算訪問靜態變量時,我總是不得不在函數名之前加上函數名,因此我的代碼變得不那么清晰了。

也就是說,在我的函數代碼中,我更願意這樣寫:

print(statics.foo)

代替

print(my_function_name.foo)

所以,我的解決方案是:

  1. 向函數添加一個statics屬性
  2. 在函數作用域中,添加一個局部變量statics作為my_function.statics的別名
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

評論

我的方法使用了一個名為Bunch的類,它是一個支持屬性樣式訪問的字典,一種 JavaScript(參見關於它的原始文章,大約在 2000 年)

它可以通過pip install bunch

它也可以像這樣手寫:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

該答案表明setdefault不能真正滿足OP如何創建靜態局部變量的問題。

def fn():
    fn.counter = vars(fn).setdefault('counter',-1)

它的作用時間只要fn。 在每個變量名之前添加前綴。 如果像這樣刪除它們:

def fn():
   counter = vars(fn).setdefault('counter',-1)
   counter += 1
   print (counter)

沒有錯誤,但counter始終為0,這告訴我vars(fn)不是訪問局部變量,而是全局的,可能是裝飾器或屬性存儲。

如果這項工作奏效,那將是我的首選解決方案。 但是,由於沒有,我傾向於使用完全封裝的類定義來創建此類靜態var。

恕我直言,這是最簡單的。 當然,這取決於您是否更熟悉功能和OOP編碼樣式。

我個人更喜歡以下裝飾器。 各有各的。

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)

使用裝飾器和閉包

以下裝飾器可用於創建靜態函數變量。 它用自身的返回值替換聲明的函數。 這意味着被裝飾的函數必須返回一個函數。

def static_inner_self(func):
    return func()

然后在一個函數上使用裝飾器,該函數返回另一個帶有捕獲變量的函數:

@static_inner_self
def foo():
    counter = 0
    def foo():
        nonlocal counter
        counter += 1
        print(f"counter is {counter}")
    return foo

nonlocal是必需的,否則 Python 認為counter變量是局部變量而不是捕獲的變量。 由於變量賦值counter += 1 ,Python 的行為與此類似。 函數中的任何賦值都會使 Python 認為該變量是局部變量。

如果你不是在內部函數中賦值給變量,那么你可以忽略nonlocal語句,例如,在這個函數中我用來縮進字符串的行,其中 Python 可以推斷出變量是nonlocal

@static_inner_self
def indent_lines():
    import re
    re_start_line = re.compile(r'^', flags=re.MULTILINE)
    def indent_lines(text, indent=2):
        return re_start_line.sub(" "*indent, text)
    return indent_lines

PS 有一個已刪除的答案提出了相同的建議。 不知道作者為什么刪了。 https://stackoverflow.com/a/23366737/195417

基於丹尼爾的回答(補充):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

我想添加這部分的原因是,靜態變量不僅用於增加某個值,還用於檢查靜態變量是否等於某個值,作為現實生活中的例子。

靜態變量仍然受到保護,僅在函數 use_foo() 的范圍內使用

在此示例中,對 foo() 函數的調用與(相對於相應的 C++ 等效項)完全相同:

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

如果類 Foo 被限制性地定義為單例類,那將是理想的。 這將使它更加pythonic。

但是,這是一個安靜的舊帖子,因為我有一個不同的慣用目標,所以我提出以下內容:

在一個函數中,我只想通過一個計算值初始化一次變量,這可能會有點昂貴。

因為我喜歡出色的寫作,並且是一名老的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  Foo () :
    if not hasattr(Foo, ‘Var’) :
        setattr ( Foo, ‘Var’, CalculateStatic())
        setattr ( Foo, ‘Step’, CalculateStep())

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

如果Python具有“ Marcro預處理”功能,那就更好了。

我寫了一個簡單的函數來使用靜態變量:

def Static():
    ### get the func object by which Static() is called.
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    # print(func_name)
    caller = caller.f_back
    func = caller.f_locals.get(
        func_name, caller.f_globals.get(
            func_name
        )
    )
    
    class StaticVars:
        def has(self, varName):
            return hasattr(self, varName)
        def declare(self, varName, value):
            if not self.has(varName):
                setattr(self, varName, value)

    if hasattr(func, "staticVars"):
        return func.staticVars
    else:
        # add an attribute to func
        func.staticVars = StaticVars()
        return func.staticVars

如何使用:

def myfunc(arg):
    if Static().has('test1'):
        Static().test += 1
    else:
        Static().test = 1
    print(Static().test)

    # declare() only takes effect in the first time for each static variable.
    Static().declare('test2', 1)
    print(Static().test2)
    Static().test2 += 1

Miguel Angelo 的自我重定義解決方案甚至可以在沒有任何裝飾器的情況下實現:

def fun(increment=1):
    global fun
    counter = 0
    def fun(increment=1):
        nonlocal counter
        counter += increment
        print(counter)
    fun(increment)

fun()    #=> 1
fun()    #=> 2
fun(10)  #=> 12

第二行必須進行調整才能獲得有限的 scope:

def outerfun():
    def innerfun(increment=1):
        nonlocal innerfun
        counter = 0
        def innerfun(increment=1):
            nonlocal counter
            counter += increment
            print(counter)
        innerfun(increment)

    innerfun()    #=> 1
    innerfun()    #=> 2
    innerfun(10)  #=> 12

outerfun()

裝飾器的優點是您不必額外注意構造的 scope。

當然這是一個老問題,但我想我可能會提供一些更新。

性能參數似乎已經過時了。 相同的測試套件似乎為 siInt_try 和 isInt_re2 提供了相似的結果。 當然,結果各不相同,但這是我的計算機上的一個會話,在內核 4.3.01 和 Xeon W3550 上使用 python 3.4.4。 我已經運行了幾次,結果似乎很相似。 我將全局正則表達式移動到靜態函數中,但性能差異可以忽略不計。

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

隨着性能問題的解決,似乎 try/catch 會產生最適合未來和角落案例的代碼,所以也許只需將它包裝在函數中

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM