747

この C/C++ コードに相当する慣用的な Python は何ですか?

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

具体的には、クラス レベルではなく、関数レベルで静的メンバーをどのように実装しますか? そして、関数をクラスに配置すると何かが変わりますか?

4

27 に答える 27

796

少し逆ですが、これはうまくいくはずです:

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 )

于 2008-11-10T23:46:00.920 に答える
260

関数に属性を追加して、静的変数として使用できます。

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

とにかく、静的変数はかなりまれであり、おそらくクラス内で、この変数のより良い場所を見つける必要があります。

于 2008-11-10T23:53:00.797 に答える
243

次のことも考えられます。

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

理由:

  • 多くのpythonic(「許可ではなく許しを求める」)
  • if分岐の代わりに例外 (一度だけスローされる) を使用します ( StopIteration例外を考えてください) 。
于 2013-04-25T12:16:31.450 に答える
67

多くの人がすでに「hasattr」のテストを提案していますが、もっと簡単な答えがあります:

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

try/except なし、hasattr のテストなし、デフォルトで getattr のみ。

于 2015-01-05T16:24:14.347 に答える
59

他の回答は、これを行う方法を示しています。してはいけない方法は次のとおりです。

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

デフォルト値は、関数が実行されるたびにではなく、関数が最初に評価されるときにのみ初期化されるため、リストまたはその他の変更可能なオブジェクトを使用して静的な値を格納できます。

于 2008-11-10T23:47:53.770 に答える
32

以下は、外部の初期化呼び出しを必要としない、完全にカプセル化されたバージョンです。

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:関数が複数の場所から呼び出される場合にのみ、上記のアプローチをお勧めします。代わりに、関数が 1 か所でのみ呼び出される場合は、次を使用することをお勧めします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))
    ...
于 2012-09-04T19:51:35.113 に答える
31

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__()クラスでメソッドを定義することにより、任意のクラスのインスタンスを呼び出し可能にすることができます。

于 2008-11-10T23:53:12.480 に答える
16

ジェネレーター関数を使用して反復子を生成します。

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ステートメントに関するドキュメントです。

于 2008-11-10T23:37:00.440 に答える
11

関数の属性を静的変数として使用すると、潜在的な欠点がいくつかあります。

  • 変数にアクセスするたびに、関数の完全な名前を書き出す必要があります。
  • 外部コードは変数に簡単にアクセスでき、値をいじることができます。

2番目の問題の慣用的なpythonは、おそらく変数の名前にアンダースコアを付けて、アクセスすることを意図していないことを示し、事後にアクセスできるようにするでしょう。

クロージャの使用

代替手段はnonlocal、Python 3 のキーワードでサポートされているレキシカル クロージャーを使用するパターンです。

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

しかし、目的を明確に伝える組み込み型については知りません。

于 2015-08-03T09:50:37.460 に答える
9
_カウンター = 0
デフ foo():
   グローバル_カウンター
   _カウンター += 1
   print 'counter is', _counter

Python は慣習的にアンダースコアを使用してプライベート変数を示します。C で static 変数を関数内で宣言する唯一の理由は、関数の外でそれを隠すためです。これは実際には慣用的な Python ではありません。

于 2008-11-10T23:50:14.670 に答える
7
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 のコードと同様に、これは関数デコレーターとして使用され、静的変数には関数名をプレフィックスとして使用してアクセスする必要があります。このコードの利点は (確かに誰でも理解できるほど賢いかもしれませんが)、複数の静的変数を使用して、より従来の方法でそれらを初期化できることです。

于 2015-02-09T02:27:39.613 に答える
4

ファンキーな呼び出し署名を使用しても構わない場合は、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
于 2015-12-01T14:47:24.163 に答える
4

慣用的な方法は、属性を持つことができるclassを使用することです。インスタンスを分離しない必要がある場合は、シングルトンを使用してください。

「静的」変数を Python に偽装またはマンジする方法はいくつかあります (これまでに言及されていない方法の 1 つは、変更可能なデフォルト引数を持つことです) が、これはPython の慣用的な方法ではありません。クラスを使用するだけです。

または、使用パターンが適合する場合は、おそらくジェネレーターです。

于 2011-05-16T07:36:30.363 に答える
3

この質問に促されて、もう少し使いやすく、メソッドと関数の両方で同じように見える別の代替案を提示できますか:

@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
于 2013-10-01T21:09:43.700 に答える
3

静的なローカル変数を持つ関数を作成する代わりに、いつでも「関数オブジェクト」と呼ばれるものを作成し、それに標準 (非静的) メンバー変数を与えることができます。

C++ で書かれた例を挙げてくださったので、まず C++ における「関数オブジェクト」とは何かを説明します。「関数オブジェクト」とは、オーバーロードされた を持つ単純なクラスoperator()です。クラスのインスタンスは関数のように動作します。たとえば、 がオブジェクト (オーバーロードされた) であり、技術的に「関数」でなくint x = square(5);ても記述できます。関数オブジェクトには、クラス オブジェクトに与えることができる任意の機能を与えることができます。squareoperator()

# 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;

operator()Python では、メソッドが代わりに名前が付けられていることを除いて、オーバーロードすることもできます__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
于 2017-10-25T22:22:25.003 に答える
2

この回答は、@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私のメソッドは、属性スタイルのアクセスをサポートするディクショナリであるという名前のクラスを使用します (これについては、2000 年頃の元の記事を参照してください)。

経由でインストールできますpip install bunch

次のように手書きすることもできます。

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
于 2018-03-18T11:06:41.603 に答える
1

私は個人的にデコレータよりも以下を好みます。それぞれ独自に。

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)
于 2012-06-08T01:15:58.377 に答える
-2

確かにこれは古い質問ですが、更新を提供できると思います。

パフォーマンスの議論は時代遅れのようです。同じテスト スイートは、siInt_try と isInt_re2 に対して同様の結果をもたらすようです。もちろん結果はさまざまですが、これは Xeon W3550 を搭載したカーネル 4.3.01 で Python 3.4.4 を使用した私のコンピューターでの 1 つのセッションです。何度か実行しましたが、結果は似ているようです。グローバル正規表現を関数 static に移動しましたが、パフォーマンスの違いはごくわずかです。

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

パフォーマンスの問題が解決されたので、try/catch が最も将来性とコーナーケースに強いコードを生成するように思われるので、関数でラップするだけかもしれません

于 2016-01-26T17:40:11.607 に答える