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#과 크게 다르지 않습니다.
클래스 및 클래스 개체에 대한 Python 자습서의 내용을 참조하십시오 .
@Steve Johnson은 Python Library Reference의 "Built-in Functions"에 문서화된 정적 메서드 에 대해 이미 답변 했습니다.
class C:
@staticmethod
def f(arg1, arg2, ...): ...
@beidy 는 메서드가 첫 번째 인수로 클래스 유형을 수신하므로 staticmethod보다 classmethod 를 권장합니다.
@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
가 "정적" 클래스 변수와 어떻게 동기화되지 않았 는지 확인하십시오 . 이는 네임스페이스와 구별되는 네임스페이스 내에서 다시 바인딩 되었기 때문 입니다. "정적" 변수의 값을 변경하려면 원래 정의된 범위(또는 개체) 내에서 변경해야 합니다. Python에는 C++ 및 Java와 같은 정적 변수가 없기 때문에 "정적"을 따옴표로 묶었습니다.i
t
i
t
Test
정적 변수나 메서드에 대해 구체적으로 말하지는 않지만 Python 자습서 에는 클래스 및 클래스 개체 에 대한 몇 가지 관련 정보가 있습니다 .
@Steve Johnson은 또한 Python 라이브러리 참조의 "내장 함수"에 문서화된 정적 메서드에 대해서도 답변했습니다.
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 my_method(self):
pass
# class method:
@classmethod
def my_class_method(cls):
pass
# static method:
@staticmethod
def my_static_method():
pass
평소와 같이 에 대한 첫 번째 인수 my_method()
는 클래스 인스턴스 개체에 바인딩됩니다. 대조적으로 에 대한 첫 번째 인수 my_class_method()
는 클래스 개체 자체에 바인딩됩니다 (예: 이 경우 Test
). 의 경우 my_static_method()
인수가 바인딩되지 않으며 인수가 전혀 없는 것은 선택 사항입니다.
"정적 변수"
그러나 "정적 변수"(음, 가변 정적 변수, 어쨌든 용어의 모순이 아니라면...)를 구현하는 것은 간단하지 않습니다. millerdev 가 그의 답변에서 지적했듯이 문제는 Python의 클래스 속성이 진정한 "정적 변수"가 아니라는 것입니다. 고려하다:
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
을 추가했기 때문 입니다.x
Test
i
부분적 으로 예상되는 정적 변수 동작, 즉 여러 인스턴스 간의 속성 동기화( 클래스 자체와 동기화 되지 않음 , 아래 "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
이제 동작은 모든 클래스 인스턴스에서 동기화된 (변경 가능한) 정적 변수와 동일합니다.
불변 "정적 변수"
변경할 수 없는 정적 변수 동작의 경우 property
setter를 생략하면 됩니다.
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.i
은 오류를 생성 합니다.i
Test
x
많은 사람들이 이것을 놀랍게 생각할 것입니다. 그러나 그렇게해서는 안됩니다. 돌아가서 Test
클래스 정의(두 번째 버전)를 검사하면 다음 행을 기록합니다.
i = property(get_i)
분명히 의 멤버 i
는 함수 에서 반환된 개체 유형인 개체 Test
여야 합니다 .property
property
위의 내용이 혼란스럽다면 여전히 다른 언어(예: Java 또는 C++)의 관점에서 생각하고 있을 가능성이 큽니다. property
Python 속성이 반환되는 순서, 설명자 프로토콜 및 MRO(메소드 확인 순서)에 대해 개체를 연구해야 합니다 .
나는 아래에 위의 'gotcha'에 대한 해결책을 제시합니다. assert Test.i = x.i
그러나 최소한 오류가 발생하는 이유를 완전히 이해할 때까지는 다음과 같은 작업을 시도하지 않는 것이 좋습니다 .
REAL, ACTUAL 정적 변수 -Test.i == x.i
아래의 (Python 3) 솔루션은 정보 제공용으로만 제공됩니다. 나는 그것을 "좋은 해결책"으로 지지하지 않는다. Python에서 다른 언어의 정적 변수 동작을 에뮬레이트하는 것이 실제로 필요한지 여부에 대해 의구심이 있습니다. 그러나 실제로 유용한지 여부에 관계없이 아래 내용은 Python이 작동하는 방식을 더 잘 이해하는 데 도움이 됩니다.
업데이트: 이 시도 는 정말 끔찍 합니다. 이와 같은 일을 고집하는 경우(힌트: 하지 마십시오. Python은 매우 우아한 언어이며 다른 언어처럼 행동하도록 구두 뿔을 만드는 것은 필요하지 않습니다) Ethan Furman의 답변 에 있는 코드를 대신 사용하십시오.
메타클래스를 사용하여 다른 언어의 정적 변수 동작 에뮬레이션
메타 클래스는 클래스의 클래스입니다. Python의 모든 클래스에 대한 기본 메타 클래스(즉, 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
'파이썬' 카테고리의 다른 글
[python] 이름(문자열)을 사용하여 모듈의 함수 호출 (0) | 2022.08.28 |
---|---|
[python] 줄 바꿈이나 공백 없이 인쇄하는 방법 (0) | 2022.08.28 |
[python] 파이썬에서 문자열을 어떻게 소문자로 합니까? (0) | 2022.08.28 |
[python] Python에서 문자열의 하위 문자열을 얻으려면 어떻게 해야 합니까? (0) | 2022.08.28 |
[python] 목록의 마지막 요소를 어떻게 얻습니까? (0) | 2022.08.28 |