본문 바로가기
Python Object-oriented Programming

Metaclass Example

by 자동매매 2023. 4. 1.

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

댓글