Introduction to the Python metaclass example
다음은 name, age라는 속성이 있는 Person 클래스를 정의합니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
self._age = value
def __eq__(self, other):
return self.name == other.name and self.age == other.age
def __hash__(self):
return hash(f'{self.name, self.age}')
def __str__(self):
return f'Person(name={self.name},age={self.age})'
def __repr__(self):
return f'Person(name={self.name},age={self.age})'
일반적으로 새 클래스를 정의할 때는 다음을 수행해야 합니다.
- 개체의 속성 목록을 정의합니다.
- 개체의 속성을 초기화하는 __init__ 메서드를 정의합니다.
- __str__ 및 __repr__ 메서드를 구현하여 사람이 읽을 수 있고 기계가 읽을 수 있는 형식으로 개체를 나타냅니다.
- 모든 속성의 값으로 객체를 비교하는 __eq__ method를 사용합니다.
- __hash__ 메서드를 구현하여 클래스의 개체를 사전의 키 또는 집합의 요소로 사용합니다.
Person 클래스를 다음과 같이 정의하고 위의 모든 함수를 자동으로 가지고 있다고 상상해보십시오.
class Person:
props = ['first_name', 'last_name', 'age']
이를 위해 메타 클래스를 사용할 수 있습니다.
Define a metaclass
먼저 type 클래스에서 상속되는 Data 메타클래스를 정의합니다.
class Data(type):
pass
둘째, __new__ 메서드를 재정의하여 새 클래스 개체를 반환합니다.
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
return class_obj
__new__ 메서드는 Data 메타클래스의 정적 메서드입니다. 그리고 파이썬이 특별하게 취급하기 때문에 @staticmethod 데코레이터를 사용할 필요가 없습니다.
또한 __new__ 메서드는 Person 클래스의 인스턴스가 아닌 Person 클래스와 같은 새 클래스를 만듭니다.
Create property objects
먼저 속성 이름을 받아들이고 속성 개체를 만들기 위한 세 가지 메서드(set, get 및 delete)를 포함하는 Prop 클래스를 정의합니다. Data 메타 클래스는 이 Prop 클래스를 사용하여 클래스에 속성 개체를 추가합니다.
class Prop:
def __init__(self, attr):
self._attr = attr
def get(self, obj):
return getattr(obj, self._attr)
def set(self, obj, value):
return setattr(obj, self._attr, value)
def delete(self, obj):
return delattr(obj, self._attr)
둘째, props 목록에서 각 속성에 대한 속성 객체를 만드는 새로운 정적 메서드 define_property()를 만듭니다.
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
Data.define_property(class_obj)
return class_obj
@staticmethod
def define_property(class_obj):
for prop in class_obj.props:
attr = f'_{prop}'
prop_obj = property(
fget=Prop(attr).get,
fset=Prop(attr).set,
fdel=Prop(attr).delete
)
setattr(class_obj, prop, prop_obj)
return class_obj
다음은 Data 메타 클래스를 사용하는 Person 클래스를 정의합니다.
class Person(metaclass=Data):
props = ['name', 'age']
Person 클래스에는 name, age라는 두 가지 속성이 있습니다.
pprint(Person.__dict__)
Output:
mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'age': <property object at 0x000002213CA92090>,
'name': <property object at 0x000002213C772A90>,
'props': ['name', 'age']})
Define __init__ method
다음은 init 정적 메서드를 정의하고 클래스 객체의 __init__ 속성에 할당합니다.
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
# create property
Data.define_property(class_obj)
# define __init__
setattr(class_obj, '__init__', Data.init(class_obj))
return class_obj
@staticmethod
def init(class_obj):
def _init(self, *obj_args, **obj_kwargs):
if obj_kwargs:
for prop in class_obj.props:
if prop in obj_kwargs.keys():
setattr(self, prop, obj_kwargs[prop])
if obj_args:
for kv in zip(class_obj.props, obj_args):
setattr(self, kv[0], kv[1])
return _init
# more methods
다음은 Person 클래스의 새 인스턴스를 만들고 해당 속성을 초기화합니다.
p = Person('John Doe', age=25)
print(p.__dict__)
Output:
{'_age': 25, '_name': 'John Doe'}
p.__dict__은 props list에 미리 정의된 이름을 기반으로 하는 두 개의 속성 _name 및 _age 을 포함한다.
Define __repr__ method
다음은 함수를 반환하고, 클래스 객체의 __repr__ 속성에 사용하는 repr 정적 메서드를 정의합니다.
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
# create property
Data.define_property(class_obj)
# define __init__
setattr(class_obj, '__init__', Data.init(class_obj))
# define __repr__
setattr(class_obj, '__repr__', Data.repr(class_obj))
return class_obj
@staticmethod
def repr(class_obj):
def _repr(self):
prop_values = (getattr(self, prop) for prop in class_obj.props)
prop_key_values = (f'{key}={value}' for key, value in zip(class_obj.props, prop_values))
prop_key_values_str = ', '.join(prop_key_values)
return f'{class_obj.__name__}({prop_key_values_str})'
return _repr
다음은 Person 클래스의 새 인스턴스를 만들고 표시합니다.
p = Person('John Doe', age=25)
print(p)
Output:
Person(name=John Doe, age=25)
Define __eq__ and __hash__ methods
다음은 eq 및 hash 메서드를 정의하고 메타 클래스의 클래스 객체의 __eq__ 및 __hash__에 할당합니다.
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
# create property
Data.define_property(class_obj)
# define __init__
setattr(class_obj, '__init__', Data.init(class_obj))
# define __repr__
setattr(class_obj, '__repr__', Data.repr(class_obj))
# define __eq__ & __hash__
setattr(class_obj, '__eq__', Data.eq(class_obj))
setattr(class_obj, '__hash__', Data.hash(class_obj))
return class_obj
@staticmethod
def eq(class_obj):
def _eq(self, other):
if not isinstance(other, class_obj):
return False
self_values = [getattr(self, prop) for prop in class_obj.props]
other_values = [getattr(other, prop) for prop in other.props]
return self_values == other_values
return _eq
@staticmethod
def hash(class_obj):
def _hash(self):
values = (getattr(self, prop) for prop in class_obj.props)
return hash(tuple(values))
return _hash
다음은 Person의 두 인스턴스를 만들고 비교합니다. 모든 속성의 값이 같으면 동일합니다. 그렇지 않으면 동일하지 않습니다.
p1 = Person('John Doe', age=25)
p2 = Person('Jane Doe', age=25)
print(p1 == p2) # False
p2.name = 'John Doe'
print(p1 == p2) # True
전체 Code
from pprint import pprint
class Prop:
def __init__(self, attr):
self._attr = attr
def get(self, obj):
return getattr(obj, self._attr)
def set(self, obj, value):
return setattr(obj, self._attr, value)
def delete(self, obj):
return delattr(obj, self._attr)
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
# create property
Data.define_property(class_obj)
# define __init__
setattr(class_obj, '__init__', Data.init(class_obj))
# define __repr__
setattr(class_obj, '__repr__', Data.repr(class_obj))
# define __eq__ & __hash__
setattr(class_obj, '__eq__', Data.eq(class_obj))
setattr(class_obj, '__hash__', Data.hash(class_obj))
return class_obj
@staticmethod
def eq(class_obj):
def _eq(self, other):
if not isinstance(other, class_obj):
return False
self_values = [getattr(self, prop) for prop in class_obj.props]
other_values = [getattr(other, prop) for prop in other.props]
return self_values == other_values
return _eq
@staticmethod
def hash(class_obj):
def _hash(self):
values = (getattr(self, prop) for prop in class_obj.props)
return hash(tuple(values))
return _hash
@staticmethod
def repr(class_obj):
def _repr(self):
prop_values = (getattr(self, prop) for prop in class_obj.props)
prop_key_values = (f'{key}={value}' for key, value in zip(class_obj.props, prop_values))
prop_key_values_str = ', '.join(prop_key_values)
return f'{class_obj.__name__}({prop_key_values_str})'
return _repr
@staticmethod
def init(class_obj):
def _init(self, *obj_args, **obj_kwargs):
if obj_kwargs:
for prop in class_obj.props:
if prop in obj_kwargs.keys():
setattr(self, prop, obj_kwargs[prop])
if obj_args:
for kv in zip(class_obj.props, obj_args):
setattr(self, kv[0], kv[1])
return _init
@staticmethod
def define_property(class_obj):
for prop in class_obj.props:
attr = f'_{prop}'
prop_obj = property(
fget=Prop(attr).get,
fset=Prop(attr).set,
fdel=Prop(attr).delete
)
setattr(class_obj, prop, prop_obj)
return class_obj
class Person(metaclass=Data):
props = ['name', 'age']
if __name__ == '__main__':
pprint(Person.__dict__)
p1 = Person('John Doe', age=25)
p2 = Person('Jane Doe', age=25)
print(p1 == p2) # False
p2.name = 'John Doe'
print(p1 == p2) # True
Decorator
다음은 Data 메타 클래스를 사용하는 Employee라는 클래스를 정의합니다 .
class Employee(metaclass=Data):
props = ['name', 'job_title']
if __name__ == '__main__':
e = Employee(name='John Doe', job_title='Python Developer')
print(e)
Output:
Employee(name=John Doe, job_title=Python Developer)
예상대로 작동합니다. 그러나 메타 클래스를 지정하는 것은 매우 장황합니다. 이를 개선하기 위해 함수 데코레이터를 사용할 수 있습니다.
먼저, Data 메타 클래스의 인스턴스인 새 클래스를 반환하는 함수 데코레이터를 정의하십시오.
def data(cls):
return Data(cls.__name__, cls.__bases__, dict(cls.__dict__))
둘째, Data를 메타 클래스로 사용하는 모든 클래스에 대해 @data 데코레이터 를 사용하십시오.
@data
class Employee:
props = ['name', 'job_title']
전체 Code
class Prop:
def __init__(self, attr):
self._attr = attr
def get(self, obj):
return getattr(obj, self._attr)
def set(self, obj, value):
return setattr(obj, self._attr, value)
def delete(self, obj):
return delattr(obj, self._attr)
class Data(type):
def __new__(mcs, name, bases, class_dict):
class_obj = super().__new__(mcs, name, bases, class_dict)
# create property
Data.define_property(class_obj)
# define __init__
setattr(class_obj, '__init__', Data.init(class_obj))
# define __repr__
setattr(class_obj, '__repr__', Data.repr(class_obj))
# define __eq__ & __hash__
setattr(class_obj, '__eq__', Data.eq(class_obj))
setattr(class_obj, '__hash__', Data.hash(class_obj))
return class_obj
@staticmethod
def eq(class_obj):
def _eq(self, other):
if not isinstance(other, class_obj):
return False
self_values = [getattr(self, prop) for prop in class_obj.props]
other_values = [getattr(other, prop) for prop in other.props]
return self_values == other_values
return _eq
@staticmethod
def hash(class_obj):
def _hash(self):
values = (getattr(self, prop) for prop in class_obj.props)
return hash(tuple(values))
return _hash
@staticmethod
def repr(class_obj):
def _repr(self):
prop_values = (getattr(self, prop) for prop in class_obj.props)
prop_key_values = (f'{key}={value}' for key, value in zip(class_obj.props, prop_values))
prop_key_values_str = ', '.join(prop_key_values)
return f'{class_obj.__name__}({prop_key_values_str})'
return _repr
@staticmethod
def init(class_obj):
def _init(self, *obj_args, **obj_kwargs):
if obj_kwargs:
for prop in class_obj.props:
if prop in obj_kwargs.keys():
setattr(self, prop, obj_kwargs[prop])
if obj_args:
for kv in zip(class_obj.props, obj_args):
setattr(self, kv[0], kv[1])
return _init
@staticmethod
def define_property(class_obj):
for prop in class_obj.props:
attr = f'_{prop}'
prop_obj = property(
fget=Prop(attr).get,
fset=Prop(attr).set,
fdel=Prop(attr).delete
)
setattr(class_obj, prop, prop_obj)
return class_obj
class Person(metaclass=Data):
props = ['name', 'age']
def data(cls):
return Data(cls.__name__, cls.__bases__, dict(cls.__dict__))
@data
class Employee:
props = ['name', 'job_title']
데이터 메타클래스와 같은 일부 기능을 가진 PEP 557에 지정된 @dataclass 데코레이터를 제공했습니다 . 또한 데이터 클래스는 classes로 작업 할 때 시간을 절약하는데 도움이되는 더 많은 기능을 제공합니다.
'Python Object-oriented Programming' 카테고리의 다른 글
Object-oriented Programming (0) | 2023.04.02 |
---|---|
Property Decorator (0) | 2023.04.01 |
Metaclass (0) | 2023.04.01 |
type Class (0) | 2023.04.01 |
Abstract Class (0) | 2023.03.31 |
댓글