클래스 속성을 만드는 방법?
파이썬에서는 @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.xhas 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 |