클래스 속성을 만드는 방법?
파이썬에서는 @classmethod
데코레이터 로 클래스에 메소드를 추가 할 수 있습니다 . 클래스에 속성을 추가하는 비슷한 데코레이터가 있습니까? 내가 말하고있는 것을 더 잘 보여줄 수 있습니다.
class Example(object):
the_I = 10
def __init__( self ):
self.an_i = 20
@property
def i( self ):
return self.an_i
def inc_i( self ):
self.an_i += 1
# is this even possible?
@classproperty
def I( cls ):
return cls.the_I
@classmethod
def inc_I( cls ):
cls.the_I += 1
e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21
assert Example.I == 10
Example.inc_I()
assert Example.I == 11
위에서 사용한 구문이 가능합니까 아니면 더 필요한 것이 있습니까?
클래스 속성을 원하는 이유는 클래스 속성을 게으르게로드 할 수 있기 때문에 적당합니다.
이 작업을 수행하는 방법은 다음과 같습니다.
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Bar(object):
_bar = 1
@classproperty
def bar(cls):
return cls._bar
@bar.setter
def bar(cls, value):
cls._bar = value
# test instance instantiation
foo = Bar()
assert foo.bar == 1
baz = Bar()
assert baz.bar == 1
# test static variable
baz.bar = 5
assert foo.bar == 5
# test setting variable on the class
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
세터는 우리가 전화를 시간에 작동하지 않았다 Bar.bar
우리가 호출하기 때문에, TypeOfBar.bar.__set__
하지 않은, Bar.bar.__set__
.
메타 클래스 정의를 추가하면이 문제가 해결됩니다.
class ClassPropertyMetaClass(type):
def __setattr__(self, key, value):
if key in self.__dict__:
obj = self.__dict__.get(key)
if obj and type(obj) is ClassPropertyDescriptor:
return obj.__set__(self, value)
return super(ClassPropertyMetaClass, self).__setattr__(key, value)
# and update class define:
# class Bar(object):
# __metaclass__ = ClassPropertyMetaClass
# _bar = 1
# and update ClassPropertyDescriptor.__set__
# def __set__(self, obj, value):
# if not self.fset:
# raise AttributeError("can't set attribute")
# if inspect.isclass(obj):
# type_ = obj
# obj = None
# else:
# type_ = type(obj)
# return self.fset.__get__(obj, type_)(value)
이제 모두 괜찮을 것입니다.
classproperty
다음과 같이 정의 하면 요청한대로 예제가 작동합니다.
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
주의 할 점은 쓰기 가능한 속성에는이 속성을 사용할 수 없다는 것입니다. 하지만 e.I = 20
을 올릴 것이다 AttributeError
, Example.I = 20
속성 개체 자체를 덮어 쓰게됩니다.
메타 클래스 로이 작업을 수행 할 수 있다고 생각합니다. 메타 클래스는 클래스의 클래스와 같을 수 있기 때문에 (그렇다면) __call__()
메타 클래스에 메소드를 할당 하여 클래스 호출을 재정의 할 수 있다는 것을 알고 있습니다 MyClass()
. property
메타 클래스 에서 데코레이터를 사용하는 것이 비슷하게 작동 하는지 궁금합니다 . (이전에 시도한 적이 없지만 지금은 궁금합니다.)
[최신 정보:]
와우, 작동합니다 :
class MetaClass(type):
def getfoo(self):
return self._foo
foo = property(getfoo)
@property
def bar(self):
return self._bar
class MyClass(object):
__metaclass__ = MetaClass
_foo = 'abc'
_bar = 'def'
print MyClass.foo
print MyClass.bar
참고 : 이것은 Python 2.7에 있습니다. Python 3+는 다른 기술을 사용하여 메타 클래스를 선언합니다. 사용 : class MyClass(metaclass=MetaClass):
, 제거 __metaclass__
및 나머지는 동일합니다.
[python 3.4를 기반으로 작성된 답변; 메타 클래스 구문은 2가 다르지만이 기법은 여전히 효과가 있다고 생각합니다.]
메타 클래스를 사용 하여이 작업을 수행 할 수 있습니다 ... 주로 Dappawit은 거의 작동하지만 결함이 있다고 생각합니다.
class MetaFoo(type):
@property
def thingy(cls):
return cls._thingy
class Foo(object, metaclass=MetaFoo):
_thingy = 23
이것은 Foo의 클래스 속성을 얻지 만 문제가 있습니다 ...
print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
print("Foo().thingy is {}".format(foo.thingy))
else:
print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?
대체 무슨 일이 일어나고 있는거야? 인스턴스에서 클래스 속성에 도달 할 수없는 이유는 무엇입니까?
나는 그것이 답이라고 믿는 것을 찾기 전에 꽤 오랫동안 이것에 대해 머리를 치고 있었다. Python @properties는 디스크립터의 서브 세트이며 디스크립터 문서 (강조 광산)의 일부입니다.
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance,
a.x
has a lookup chain starting witha.__dict__['x']
, thentype(a).__dict__['x']
, and continuing through the base classes oftype(a)
excluding metaclasses.
So the method resolution order doesn't include our class properties (or anything else defined in the metaclass). It is possible to make a subclass of the built-in property decorator that behaves differently, but (citation needed) I've gotten the impression googling that the developers had a good reason (which I do not understand) for doing it that way.
That doesn't mean we're out of luck; we can access the properties on the class itself just fine...and we can get the class from type(self)
within the instance, which we can use to make @property dispatchers:
class Foo(object, metaclass=MetaFoo):
_thingy = 23
@property
def thingy(self):
return type(self).thingy
Now Foo().thingy
works as intended for both the class and the instances! It will also continue to do the right thing if a derived class replaces its underlying _thingy
(which is the use case that got me on this hunt originally).
This isn't 100% satisfying to me -- having to do setup in both the metaclass and object class feels like it violates the DRY principle. But the latter is just a one-line dispatcher; I'm mostly okay with it existing, and you could probably compact it down to a lambda or something if you really wanted.
As far as I can tell, there is no way to write a setter for a class property without creating a new metaclass.
I have found that the following method works. Define a metaclass with all of the class properties and setters you want. IE, I wanted a class with a title
property with a setter. Here's what I wrote:
class TitleMeta(type):
@property
def title(self):
return getattr(self, '_title', 'Default Title')
@title.setter
def title(self, title):
self._title = title
# Do whatever else you want when the title is set...
Now make the actual class you want as normal, except have it use the metaclass you created above.
# Python 2 style:
class ClassWithTitle(object):
__metaclass__ = TitleMeta
# The rest of your class definition...
# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
# Your class definition...
It's a bit weird to define this metaclass as we did above if we'll only ever use it on the single class. In that case, if you're using the Python 2 style, you can actually define the metaclass inside the class body. That way it's not defined in the module scope.
If you use Django, it has a built in @classproperty
decorator.
from django.utils.decorators import classproperty
If you only need lazy loading, then you could just have a class initialisation method.
EXAMPLE_SET = False
class Example(object):
@classmethod
def initclass(cls):
global EXAMPLE_SET
if EXAMPLE_SET: return
cls.the_I = 'ok'
EXAMPLE_SET = True
def __init__( self ):
Example.initclass()
self.an_i = 20
try:
print Example.the_I
except AttributeError:
print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I
But the metaclass approach seems cleaner, and with more predictable behavior.
Perhaps what you're looking for is the Singleton design pattern. There's a nice SO QA about implementing shared state in Python.
I happened to come up with a solution very similar to @Andrew, only DRY
class MetaFoo(type):
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.thingy})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
@property
def thingy(cls):
if not inspect.isclass(cls):
cls = type(cls)
return cls._thingy
@thingy.setter
def thingy(cls, value):
if not inspect.isclass(cls):
cls = type(cls)
cls._thingy = value
class Foo(metaclass=MetaFoo):
_thingy = 23
class Bar(Foo)
_thingy = 12
This has the best of all answers:
The "metaproperty" is added to the class, so that it will still be a property of the instance
- Don't need to redefine thingy in any of the classes
- The property works as a "class property" in for both instance and class
- You have the flexibility to customize how _thingy is inherited
In my case, I actually customized _thingy
to be different for every child, without defining it in each class (and without a default value) by:
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
def _create_type(meta, name, attrs):
type_name = f'{name}Type'
type_attrs = {}
for k, v in attrs.items():
if type(v) is _ClassPropertyDescriptor:
type_attrs[k] = v
return type(type_name, (meta,), type_attrs)
class ClassPropertyType(type):
def __new__(meta, name, bases, attrs):
Type = _create_type(meta, name, attrs)
cls = super().__new__(meta, name, bases, attrs)
cls.__class__ = Type
return cls
class _ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, owner):
if self in obj.__dict__.values():
return self.fget(obj)
return self.fget(owner)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
return self.fset(obj, value)
def setter(self, func):
self.fset = func
return self
def classproperty(func):
return _ClassPropertyDescriptor(func)
class Bar(metaclass=ClassPropertyType):
__bar = 1
@classproperty
def bar(cls):
return cls.__bar
@bar.setter
def bar(cls, value):
cls.__bar = value
참고URL : https://stackoverflow.com/questions/5189699/how-to-make-a-class-property
'programing tip' 카테고리의 다른 글
NodeJS UnhandledPromiseRejectionWarning (0) | 2020.07.29 |
---|---|
sys.path / PYTHONPATH에 디렉토리 추가 (0) | 2020.07.29 |
HTTP 엔터티 란 정확히 무엇입니까? (0) | 2020.07.29 |
예외 코드“EXC_I386_GPFLT”의 의미는 무엇입니까? (0) | 2020.07.29 |
C ++ 헤더에서 "네임 스페이스 사용" (0) | 2020.07.29 |