Nice programing

함수 내부의 정적 변수에 해당하는 Python은 무엇입니까?

nicepro 2020. 10. 3. 11:49
반응형

함수 내부의 정적 변수에 해당하는 Python은 무엇입니까?


이 C / C ++ 코드와 동등한 관용적 Python은 무엇입니까?

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

특히, 클래스 수준이 아닌 함수 수준에서 정적 멤버를 어떻게 구현합니까? 함수를 클래스에 배치하면 어떤 변화가 있습니까?


약간 반전되었지만 작동합니다.

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

카운터 초기화 코드를 하단 대신 상단에 배치하려면 데코레이터를 만들 수 있습니다.

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

그런 다음 다음과 같은 코드를 사용하십시오.

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

foo.불행히도 여전히 접두사 를 사용해야합니다 .


편집 ( ony 덕분에 ) : 이것은 더 좋아 보입니다.

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

함수에 속성을 추가하고 정적 변수로 사용할 수 있습니다.

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

추리:

  • 많은 비단뱀 ( ask for forgiveness not permission)
  • if분기 대신 예외 (한 번만 발생)를 사용합니다 ( StopIteration 예외를 생각하십시오 ).

다른 답변은이 작업을 수행하는 방법을 보여줍니다. 하지 말아야 할 방법은 다음과 같습니다.

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

기본값은 함수가 실행될 때마다가 아니라 함수가 처음 평가 될 때만 초기화되므로 목록이나 기타 변경 가능한 객체를 사용하여 정적 값을 저장할 수 있습니다.


많은 사람들이 이미 'hasattr'테스트를 제안했지만 더 간단한 대답이 있습니다.

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

시도 / 예외 없음, 테스트 hasattr 없음, 기본값으로 getattr.


파이썬에는 정적 변수가 없지만 호출 가능한 클래스 객체를 정의한 다음이를 함수로 사용하여 가짜로 만들 수 있습니다. 또한이 답변을 참조하십시오 .

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 fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)

파이썬에서 함수는 객체이며 특별한 속성을 통해 멤버 변수를 추가하거나 원숭이 패치 할 수 있습니다 __dict__. 내장 vars()은 특수 속성 반환합니다 __dict__.

편집 : 대체 try:except AttributeError답변 과 달리이 접근 방식을 사용하면 변수가 항상 초기화 후 코드 논리에 대해 준비됩니다. 나는 try:except AttributeError다음에 대한 대안이 덜 건조하거나 어색한 흐름을 가질 것이라고 생각합니다 .

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))
    ...

생성기 함수를 사용하여 반복자를 생성합니다.

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}')

설명 의 사양은 PEP 3104참조하십시오 nonlocal.


함수의 속성을 정적 변수로 사용하면 몇 가지 잠재적 인 단점이 있습니다.

  • 변수에 액세스 할 때마다 함수의 전체 이름을 작성해야합니다.
  • 외부 코드는 변수에 쉽게 액세스하고 값을 엉망으로 만들 수 있습니다.

두 번째 문제에 대한 관용적 파이썬은 아마도 변수의 이름을 밑줄로 지정하여 액세스 할 수 없음을 알리는 동시에 사실 이후에도 액세스 할 수 있도록 유지합니다.

대안은 nonlocalpython 3 키워드로 지원되는 어휘 클로저를 사용하는 패턴 입니다.

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

슬프게도 저는이 솔루션을 데코레이터로 캡슐화 할 방법을 모릅니다.


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의 코드와 마찬가지로 이것은 함수 데코레이터로 사용되며 정적 변수는 함수 이름을 접두사로 사용하여 액세스해야합니다. 이 코드의 장점은 (분명히 누구나 알아낼만큼 똑똑 할 수도 있지만) 여러 정적 변수를 가질 수 있고보다 일반적인 방식으로 초기화 할 수 있다는 것입니다.


조금 더 읽기 쉽지만 더 장황합니다 (Zen of Python : 명시 적이 암시 적보다 낫습니다) :

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

작동 방식에 대한 설명은 여기참조 하십시오 .


_counter = 0
def foo () :
   글로벌 카운터
   _counter + = 1
   print 'counter is', _counter

파이썬은 일반적으로 밑줄을 사용하여 개인 변수를 나타냅니다. C에서 함수 내부에 정적 변수를 선언하는 유일한 이유는 실제로 관용적 인 Python이 아닌 함수 외부에서이를 숨기는 것입니다.


관용적 방법은 사용하는 클래스 속성을 가질 수있다. 인스턴스를 분리하지 않으려면 싱글 톤을 사용하십시오.

"정적"변수를 파이썬으로 속이거나 뭉칠 수있는 여러 가지 방법이 있습니다 (지금까지 언급되지 않은 것은 가변 기본 인수를 갖는 것입니다).하지만 이것은 파이썬의 관용적 방법이 아닙니다. 수업을 사용하십시오.

또는 사용 패턴이 맞다면 생성기 일 수도 있습니다.


하라는 메시지가 이 질문에 내가 사용에 조금 더 좋은 수 있습니다 및 방법과 기능 모두에 대해 같은 모양 또 다른 대안을 제시 할 수 있습니다 :

@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);경우에도 작성할 수 있습니다 . 클래스 객체에 제공 할 수있는 모든 기능을 함수 객체에 제공 할 수 있습니다.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;

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

여러 가지 접근 방식을 시도한 후 개선 된 버전의 @warvariuc 답변을 사용하게됩니다.

import types

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

나는 개인적으로 데코레이터보다 다음을 선호합니다. 각자에게.

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)

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

물론 이것은 오래된 질문이지만 업데이트를 제공 할 수 있다고 생각합니다.

성능 논쟁은 쓸모없는 것 같습니다. 동일한 테스트 스위트가 siInt_try 및 isInt_re2에 대해 유사한 결과를 제공하는 것으로 보입니다. 물론 결과는 다양하지만 이것은 Xeon W3550을 사용하는 커널 4.3.01에서 Python 3.4.4를 사용하는 내 컴퓨터의 한 세션입니다. 여러 번 실행했는데 결과가 비슷해 보입니다. 전역 정규식을 정적 함수로 옮겼지만 성능 차이는 무시할 수 있습니다.

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

성능 문제가 사라지면 try / catch가 가장 미래 및 코너 케이스 증명 코드를 생성하는 것처럼 보이므로 함수로 래핑 할 수 있습니다.


이 답변은 setdefault가 정적 지역 변수 를 만드는 방법에 대한 OP 질문을 진정으로 충족시키지 못함을 보여줍니다 .

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

fn만큼 오래 작동합니다. 각 변수 이름에 접두사가 붙습니다. 이렇게 제거하는 경우 :

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

오류는 없지만 카운터는 항상 0이며 vars (fn)이 지역 변수에 액세스하지 않고 글로벌, 아마도 데코레이터 또는 속성 숨김에 액세스하고 있음을 알려줍니다.

이것이 효과가 있었다면 내가 선호하는 솔루션이었을 것입니다. 그러나 그렇지 않기 때문에 이러한 정적 변수를 만들기 위해 완전히 캡슐화 된 클래스 정의를 사용하는 경향이 있습니다.

IMHO는 가장 간단합니다. 물론 기능적 코딩 스타일과 OOP 코딩 스타일에 더 익숙한 지 여부에 따라 다릅니다.


영혼 n + = 1

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

전역 선언은이 기능을 제공합니다. 아래 예 ( "f"를 사용하는 Python 3.5 이상)에서 카운터 변수는 함수 외부에서 정의됩니다. 함수에서 전역으로 정의하면 함수 외부의 "전역"버전을 함수에서 사용할 수 있어야 함을 의미합니다. 따라서 함수가 실행될 때마다 함수 외부의 값을 수정하여 함수 외부로 유지합니다.

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"

이 답변은 @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

Daniel의 답변을 바탕으로 작성 (추가) :

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 클래스가 단일 클래스로 제한적으로 정의되면 이상적입니다. 이것은 그것을 더 비단뱀처럼 만들 것입니다.


그러나 이것은 다른 관용적 목표를 가지고 있기 때문에 조용하고 오래된 게시물입니다.

함수에서 약간의 비용이들 수있는 계산 된 값으로 변수를 한 번만 초기화하고 싶습니다.

내가 멋진 글쓰기를 좋아하고 예전 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)

파이썬에 '마크로 전처리'가 있었다면 더 좋을 것입니다 ..

참고 URL : https://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function

반응형