[python] 파이썬에서 정적 클래스 변수가 가능합니까?

파이썬에서 정적 클래스 변수 또는 메소드를 가질 수 있습니까? 이를 위해 어떤 구문이 필요합니까?



답변

클래스 정의 내부에 선언되었지만 메소드 내부에는 선언되지 않은 변수는 클래스 또는 정적 변수입니다.

>>> class MyClass:
...     i = 3
...
>>> MyClass.i
3 

@ millerdev가 지적했듯이 클래스 수준 i변수를 만들지 만 인스턴스 수준 i변수와는 다르므로

>>> m = MyClass()
>>> m.i = 4
>>> MyClass.i, m.i
>>> (3, 4)

이는 C ++ 및 Java와 다르지만 인스턴스 참조를 사용하여 정적 멤버에 액세스 할 수없는 C #과는 다릅니다.

클래스와 클래스 객체의 주제에 대해 파이썬 튜토리얼이 무엇을 말하는지 보십시오 .

@ 스티브 존슨은 이미 관한 응답 한 정적 방법 도 아래 문서화, 파이썬 라이브러리 참조에서 “내장 기능” .

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

@beidy는 권장 classmethod 방법은 다음 첫 번째 인수로 클래스 유형을 수신 할 때, StaticMethod를 통해들,하지만, 난 여전히 StaticMethod를 통해이 방법의 장점에 약간 퍼지입니다. 당신이 너무 있다면, 아마도 중요하지 않을 것입니다.


답변

@Blair Conrad는 클래스 정의 내부에 선언 된 정적 변수이지만 메소드 내부에는없는 정적 변수는 클래스 또는 “정적”변수라고 말했다.

>>> class Test(object):
...     i = 3
...
>>> Test.i
3

여기 몇 가지가 있습니다. 위의 예에서 수행 :

>>> t = Test()
>>> t.i     # "static" variable accessed via instance
3
>>> t.i = 5 # but if we assign to the instance ...
>>> Test.i  # we have not changed the "static" variable
3
>>> t.i     # we have overwritten Test.i on t by creating a new attribute t.i
5
>>> Test.i = 6 # to change the "static" variable we do it by assigning to the class
>>> t.i
5
>>> Test.i
6
>>> u = Test()
>>> u.i
6           # changes to t do not affect new instances of Test

# Namespaces are one honking great idea -- let's do more of those!
>>> Test.__dict__
{'i': 6, ...}
>>> t.__dict__
{'i': 5}
>>> u.__dict__
{}

t.i속성 i이에 직접 설정된 경우 인스턴스 변수 가 “정적”클래스 변수와 어떻게 동기화되지 않았 는지 확인하십시오 t. 네임 스페이스와는 다른 네임 스페이스 i내에서 리 바인드 되었기 때문 입니다. “정적”변수의 값을 변경하려면 원래 정의 된 범위 (또는 오브젝트) 내에서 변경해야합니다. 파이썬에는 C ++과 Java가하는 의미에서 정적 변수가 없기 때문에 “정적”을 따옴표로 묶습니다.tTest

정적 변수 또는 메소드에 대해서는 구체적으로 언급하지 않지만 Python 자습서 에는 클래스 및 클래스 객체 에 대한 관련 정보가 있습니다 .

@Steve Johnson은 또한 Python Library Reference의 “Built-in Functions”에 문서화 된 정적 메소드에 대해서도 답변했습니다.

class Test(object):
    @staticmethod
    def f(arg1, arg2, ...):
        ...

@beid는 staticmethod와 유사한 classmethod도 언급했습니다. 클래스 메소드의 첫 번째 인수는 클래스 객체입니다. 예:

class Test(object):
    i = 3 # class (or static) variable
    @classmethod
    def g(cls, arg):
        # here we can use 'cls' instead of the class name (Test)
        if arg > cls.i:
            cls.i = arg # would be the same as Test.i = arg1

위 예의 그림 표현


답변

정적 및 클래스 메소드

다른 답변에서 언급했듯이 정적 및 클래스 메서드는 내장 데코레이터를 사용하여 쉽게 수행 할 수 있습니다.

class Test(object):

    # regular instance method:
    def MyMethod(self):
        pass

    # class method:
    @classmethod
    def MyClassMethod(klass):
        pass

    # static method:
    @staticmethod
    def MyStaticMethod():
        pass

평소와 같이 첫 번째 인수 MyMethod()는 클래스 인스턴스 객체에 바인딩됩니다. 대조적으로, 제 인수하도록 MyClassMethod()되어 클래스 객체에 바인딩 자체 (이 경우, 예를 들면 Test). 의 경우 MyStaticMethod(), 인수 아무도 구속하지 않고, 전혀 인수를 가지는 것은 선택 사항입니다된다.

“정적 변수”

그러나 “정적 변수”(잘 변하기 쉬운 가변 변수)를 구현 하는 것은 그리 간단하지 않습니다. millerdev 가 그의 답변에서 지적했듯이 , 문제는 파이썬의 클래스 속성이 실제로 “정적 변수”가 아니라는 것입니다. 치다:

class Test(object):
    i = 3  # This is a class attribute

x = Test()
x.i = 12   # Attempt to change the value of the class attribute using x instance
assert x.i == Test.i  # ERROR
assert Test.i == 3    # Test.i was not affected
assert x.i == 12      # x.i is a different object than Test.i

x.i = 12이 클래스 속성 값을 변경하는 대신 새 인스턴스 속성 i을 추가 했기 때문 입니다.xTesti

클래스 속성이 아닌 속성으로 속성을 변경하면 부분적으로 예상되는 정적 변수 동작, 즉 클래스 자체가 아닌 여러 인스턴스 간의 속성 동기화가 가능합니다 ( 아래의 “gotcha”참조).

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

    @i.setter
    def i(self,val):
        type(self)._i = val

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting and setting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    def set_i(self,val):
        type(self)._i = val

    i = property(get_i, set_i)

이제 할 수있는 일 :

x1 = Test()
x2 = Test()
x1.i = 50
assert x2.i == x1.i  # no error
assert x2.i == 50    # the property is synced

정적 변수는 이제 모든 클래스 인스턴스간에 동기화 상태를 유지 합니다 .

(참고 : 클래스 인스턴스가 자체 버전을 정의하기로 결정하지 않는 한 _i! 누군가가 그 일을하기로 결정하면 얻을 수있는 가치가 있습니까 ???)

기술적으로 말하면 i여전히 ‘정적 변수’가 아닙니다. 그것은이다 property기술자의 특별한 유형이다. 그러나이 property동작은 이제 모든 클래스 인스턴스에서 동기화 된 (변경 가능) 정적 변수와 동일합니다.

불변의 “정적 변수”

불변 정적 변수 동작의 경우 간단히 propertysetter 를 생략하십시오 .

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    i = property(get_i)

이제 인스턴스 i속성 을 설정하려고 시도 하면 AttributeError:

x = Test()
assert x.i == 3  # success
x.i = 12         # ERROR

인식해야 할 하나의 문제

위의 메소드 는 클래스의 인스턴스 에서만 작동 하며 클래스 자체를 사용할 때는 작동 하지 않습니다 . 예를 들어 :

x = Test()
assert x.i == Test.i  # ERROR

# x.i and Test.i are two different objects:
type(Test.i)  # class 'property'
type(x.i)     # class 'int'

assert Test.i == x.ii속성이 서로 다른 두 개의 객체 이므로 행 에 오류가 발생 합니다.Testx

많은 사람들이이 놀라운 것을 발견 할 것입니다. 그러나 그렇게해서는 안됩니다. 돌아가서 Test클래스 정의 (두 번째 버전)를 검사 하면이 행에 주목합니다.

    i = property(get_i) 

분명히, 부재 iTest필수가 될 property로부터 리턴 된 객체의 유형 객체 property함수.

위의 내용이 혼동 될 경우 다른 언어 (예 : Java 또는 c ++)의 관점에서 생각할 가능성이 높습니다. property파이썬 속성이 리턴되는 순서, 디스크립터 프로토콜 및 메소드 분석 순서 (MRO)에 대한 오브젝트를 연구해야합니다 .

아래의 위의 ‘gotcha’에 대한 해결책을 제시합니다. 그러나 나는 최소한 assert Test.i = x.i오류가 발생하는 이유를 완전히 이해할 때까지 다음과 같은 일을 시도하지 말 것을 강력하게 제안 합니다.

실제, 실제 정적 변수-Test.i == x.i

정보 제공 목적으로 만 (Python 3) 솔루션을 제시합니다. 나는 그것을 “좋은 해결책”으로 보증하지 않습니다. 파이썬에서 다른 언어의 정적 변수 동작을 에뮬레이트하는 것이 실제로 필요한지에 대한 의문이 있습니다. 그러나 실제로 유용한 지 여부에 관계없이 아래는 Python의 작동 방식을 이해하는 데 도움이됩니다.

업데이트 :이 시도 는 정말 끔찍합니다 . 이 같은 일을 주장하는 경우 (힌트 : 제발하지 마십시오; 파이썬은 매우 우아한 언어이며 다른 언어처럼 행동하도록 구두를 휘두르는 것은 필요하지 않습니다) 대신 Ethan Furman의 답변에 코드를 사용하십시오 .

메타 클래스를 사용하여 다른 언어의 정적 변수 동작 에뮬레이션

메타 클래스는 클래스의 클래스입니다. 파이썬의 모든 클래스에 대한 기본 메타 클래스 (즉, Python 2.3 이후의 “새로운 스타일”클래스)는 type입니다. 예를 들면 다음과 같습니다.

type(int)  # class 'type'
type(str)  # class 'type'
class Test(): pass
type(Test) # class 'type'

그러나 다음과 같이 자신의 메타 클래스를 정의 할 수 있습니다.

class MyMeta(type): pass

다음과 같이 자신의 클래스에 적용하십시오 (Python 3 전용).

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

아래는 다른 언어의 “정적 변수”동작을 에뮬레이션하려는 메타 클래스입니다. 기본적으로 기본 getter, setter 및 deleter를 요청되는 속성이 “정적 변수”인지 확인하는 버전으로 대체하여 작동합니다.

“정적 변수”의 카탈로그가 StaticVarMeta.statics속성에 저장됩니다 . 모든 속성 요청은 초기에 대체 해결 순서를 사용하여 해결하려고 시도합니다. 나는 이것을 “정적 해상도 순서”또는 “SRO”라고 불렀다. 이것은 주어진 클래스 (또는 그 상위 클래스)에 대한 “정적 변수”세트에서 요청 된 속성을 찾아서 수행됩니다. 속성이 “SRO”에 나타나지 않으면 클래스는 기본 속성 가져 오기 / 설정 / 삭제 동작 (예 : “MRO”)으로 대체됩니다.

from functools import wraps

class StaticVarsMeta(type):
    '''A metaclass for creating classes that emulate the "static variable" behavior
    of other languages. I do not advise actually using this for anything!!!

    Behavior is intended to be similar to classes that use __slots__. However, "normal"
    attributes and __statics___ can coexist (unlike with __slots__). 

    Example usage: 

        class MyBaseClass(metaclass = StaticVarsMeta):
            __statics__ = {'a','b','c'}
            i = 0  # regular attribute
            a = 1  # static var defined (optional)

        class MyParentClass(MyBaseClass):
            __statics__ = {'d','e','f'}
            j = 2              # regular attribute
            d, e, f = 3, 4, 5  # Static vars
            a, b, c = 6, 7, 8  # Static vars (inherited from MyBaseClass, defined/re-defined here)

        class MyChildClass(MyParentClass):
            __statics__ = {'a','b','c'}
            j = 2  # regular attribute (redefines j from MyParentClass)
            d, e, f = 9, 10, 11   # Static vars (inherited from MyParentClass, redefined here)
            a, b, c = 12, 13, 14  # Static vars (overriding previous definition in MyParentClass here)'''
    statics = {}
    def __new__(mcls, name, bases, namespace):
        # Get the class object
        cls = super().__new__(mcls, name, bases, namespace)
        # Establish the "statics resolution order"
        cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c,mcls))

        # Replace class getter, setter, and deleter for instance attributes
        cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
        cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
        cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
        # Store the list of static variables for the class object
        # This list is permanent and cannot be changed, similar to __slots__
        try:
            mcls.statics[cls] = getattr(cls,'__statics__')
        except AttributeError:
            mcls.statics[cls] = namespace['__statics__'] = set() # No static vars provided
        # Check and make sure the statics var names are strings
        if any(not isinstance(static,str) for static in mcls.statics[cls]):
            typ = dict(zip((not isinstance(static,str) for static in mcls.statics[cls]), map(type,mcls.statics[cls])))[True].__name__
            raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
        # Move any previously existing, not overridden statics to the static var parent class(es)
        if len(cls.__sro__) > 1:
            for attr,value in namespace.items():
                if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
                    for c in cls.__sro__[1:]:
                        if attr in StaticVarsMeta.statics[c]:
                            setattr(c,attr,value)
                            delattr(cls,attr)
        return cls
    def __inst_getattribute__(self, orig_getattribute):
        '''Replaces the class __getattribute__'''
        @wraps(orig_getattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                return StaticVarsMeta.__getstatic__(type(self),attr)
            else:
                return orig_getattribute(self, attr)
        return wrapper
    def __inst_setattr__(self, orig_setattribute):
        '''Replaces the class __setattr__'''
        @wraps(orig_setattribute)
        def wrapper(self, attr, value):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__setstatic__(type(self),attr, value)
            else:
                orig_setattribute(self, attr, value)
        return wrapper
    def __inst_delattr__(self, orig_delattribute):
        '''Replaces the class __delattr__'''
        @wraps(orig_delattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__delstatic__(type(self),attr)
            else:
                orig_delattribute(self, attr)
        return wrapper
    def __getstatic__(cls,attr):
        '''Static variable getter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    return getattr(c,attr)
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __setstatic__(cls,attr,value):
        '''Static variable setter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                setattr(c,attr,value)
                break
    def __delstatic__(cls,attr):
        '''Static variable deleter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    delattr(c,attr)
                    break
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __delattr__(cls,attr):
        '''Prevent __sro__ attribute from deletion'''
        if attr == '__sro__':
            raise AttributeError('readonly attribute')
        super().__delattr__(attr)
    def is_static(cls,attr):
        '''Returns True if an attribute is a static variable of any class in the __sro__'''
        if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
            return True
        return False


답변

클래스 변수를 즉시 클래스에 추가 할 수도 있습니다

>>> class X:
...     pass
... 
>>> X.bar = 0
>>> x = X()
>>> x.bar
0
>>> x.foo
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: X instance has no attribute 'foo'
>>> X.foo = 1
>>> x.foo
1

클래스 인스턴스는 클래스 변수를 변경할 수 있습니다

class X:
  l = []
  def __init__(self):
    self.l.append(1)

print X().l
print X().l

>python test.py
[1]
[1, 1]


답변

개인적으로 정적 메소드가 필요할 때마다 클래스 메소드를 사용합니다. 주로 클래스를 인수로 사용하기 때문입니다.

class myObj(object):
   def myMethod(cls)
     ...
   myMethod = classmethod(myMethod) 

또는 데코레이터를 사용하십시오

class myObj(object):
   @classmethod
   def myMethod(cls)

정적 속성의 경우. 파이썬 정의를 검색 할 때가 있습니다. 변수는 항상 변경 될 수 있습니다. 변경 가능하고 변경 불가능한 두 가지 유형이 있습니다. 또한 클래스 속성 및 인스턴스 속성이 있습니다. Java 및 C ++의 의미에서 정적 속성과 같은 것은 없습니다.

클래스와 관련이없는 경우 정적 방법을 파이썬의 의미로 사용하십시오! 내가 당신이라면 classmethod를 사용하거나 클래스와 독립적으로 메소드를 정의합니다.


답변

정적 속성 및 인스턴스 속성에 대해 유의해야 할 사항은 아래 예와 같습니다.

class my_cls:
  my_prop = 0

#static property
print my_cls.my_prop  #--> 0

#assign value to static property
my_cls.my_prop = 1
print my_cls.my_prop  #--> 1

#access static property thru' instance
my_inst = my_cls()
print my_inst.my_prop #--> 1

#instance property is different from static property 
#after being assigned a value
my_inst.my_prop = 2
print my_cls.my_prop  #--> 1
print my_inst.my_prop #--> 2

즉, 인스턴스 속성에 값을 할당하기 전에 인스턴스를 통해 속성에 액세스하려고하면 정적 값이 사용됩니다. python 클래스에서 선언 된 각 속성에는 항상 메모리에 정적 슬롯이 있습니다.


답변

파이썬의 정적 메소드를 클래스 메소드라고 합니다. 다음 코드를 살펴보십시오

class MyClass:

    def myInstanceMethod(self):
        print 'output from an instance method'

    @classmethod
    def myStaticMethod(cls):
        print 'output from a static method'

>>> MyClass.myInstanceMethod()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method myInstanceMethod() must be called [...]

>>> MyClass.myStaticMethod()
output from a static method

myInstanceMethod 메소드를 호출 하면 오류가 발생합니다. 이 클래스의 인스턴스에서 메소드를 호출해야하기 때문입니다. myStaticMethod 메소드 는 데코레이터 @classmethod를 사용하여 클래스 메소드 로 설정됩니다 .

발차 기와 킥킥 거리기 위해 클래스의 인스턴스를 전달하여 클래스에서 myInstanceMethod 를 호출 할 수 있습니다 .

>>> MyClass.myInstanceMethod(MyClass())
output from an instance method