Skip to content

Latest commit

 

History

History
629 lines (509 loc) · 24.6 KB

211118.md

File metadata and controls

629 lines (509 loc) · 24.6 KB

Day 46

한 권으로 개발자가 원하던 파이썬 심화 A to Z

클래스 데코레이터

데코레이터 처리 기준을 함수에서 크래스로 바꿔서 처리하는 것이 클래스 데코레이터다.

class Decorator:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs): # 객체 생성 후 받은 인자로 함수를 실행해 결과를 반환한다.
        return self._func(*args, **kwargs)

@Decorator
def mul(x, y):
    return x + y

mul(1, 2) # 2

# mul에는 Decorator 클래스의 객체가 들어있어서 __name__속성이 없다.
# 객체의 이름공간을 확인하면 내부에 함수가 저장된 것을 알 수 있다.
mul.__name__ # 예외 발생
mul.__dict__ # {'_func': <function __main__.mul(x, y)>}

클래스 데코레이터로 함수를 처리할 때도 실행 함수의 메타 정보를 전달해야 한다.
함수를 호출할 때 2번 실행하는 것은 함수의 정보를 처리하고 이를 객체에 세팅해야하기 때문이다.
실제 실행은 wraps(함수 이름)(객체 이름)으로 처리한다.

import functools as ft

class Decorator_: # 데코레이터 클래스에 함수의 메타 정보 전달
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        ft.wraps(self._func)(self) # 실행 함수의 정보를 객체 속성으로 생성
        return self._func(*args, **kwargs)

@Decorator_
def add(x, y):
    return x + y

add(1, 2) # 3

# 메타 정보를 세팅한 후에는 __name__속성을 조회할 수 있다.
# 객체의 이름공간을 확인하면 여러 개의 메타 정보가 들어간 것을 확인할 수 있다.
add.__name__ # 'add'
add.__dict__ # 함수의 메타 정보가 속성에 들어가 있는 것을 확인 가능하다.

인스턴스 데코레이터

객체도 데코레이터로 처리할 수 있다.
이 경우 객체를 실행할 때 내부에 함수를 정의해서 데코레이터를 처리하게 만든다.

class InsDecorator:
    def __call__(self, func): # 객체가 실행될 때 함수의 매개변수를 받는다.
        self._func = func # 객체 속성에 함수 저장
        def inner(*args, **kwargs): # 내부 함수를 만들어 실행 함수의 인자를 받는다.
            return self._func(*args, **kwargs) # 실행해서 결과를 반환
        return inner # 내부 함수를 반환

ins = InsDecorator()

@ins
def mul(x, y):
    return x * y

mul(1, 2) # 2

# 함수의 이름을 확인하면 __call__ 내부에 정의된 함수라는 것과 아무 속성이 없다는 것을 알 수 있다.
mul.__name__ # 'inner', 내부 함수
mul.__dict__ # {}, 속성이 없다.

실행 함수의 메타 정보를 처리하는 방식은 함수 데코레이터로 처리할 때와 같은 방식을 사용한다.

import functools as ft
class InsDecorator_:
    def __call__(self, func):
        self._func = func
        @ft.wraps(self._func)
        def inner(*args, **kwargs): # 내부 함수를 실행 함수의 메타 정보로 변경
            return self._func(*args, **kwargs)
        return inner

@InsDecorator_()
def add(x, y):
    return x + y

add(1, 2) # 3

# 데코레이터 처리된 결과를 확인하면 실행 함수의 이름이 반환되었고 실제 함수도 객체 내부에 저장된 것을 확인할 수 있다.
add.__name__ # 'add'
add.__dict__ # {'__wrapped__': <function __main__.add(x, y)>}, 내부 함수 객체 이름공간에 함수가 별도로 저장

# __ wrapped__ 속성을 확인하면 같은 함수라는 것을 알 수 있따.
add.__wrapped__(1, 2) # 3

메소드 데코레이터

메소드 내에 데코레이터를 처리할 때는 내부에 함수를 정의하고 반환해서 다시 실행할 수 있는 환경을 구성한다.
메소드로 데코레이터를 ㅌ처리하려면 클래스를 정의할 때 초기화 함수에 함수를 저장하는 속성을 만들고 매개변수로 함수를 전달받게 지정한다.

import functools as ft

class MethDecorator: # 클래스에 정의된 함수 즉 메소드로 데코레이터를 처리하는 클래스 정의
    def __init__(self, func=None):
        self._func = func

    def method(self, func): # 객체가 메소드를 호출할 때 실행 함수를 받아 데코레이터 처리
        self._func = func
        @ft.wraps(self._func) # 실행 함수의 메타 정보를 내부 함수의 메타 정보에 할당
        def inner(*args, **kwargs): # 내부 함수는 실행 함수의 인자를 전달받게 정의
            return self._func(*args, **kwargs) # 함수를 실행하고 결과 반환
        return inner # 내부 함수 반환

meth = MethDecorator()

@meth.method
def mul(x, y):
    return x * y

mul(1, 2) # 2

# 실행 함수의 메타 정보가 할당되어 실행 함수의 정보들이 이름공간과 속성에 전달된 것을 알 수 있다.
mul.__name__ # 'mul'
mul.__dict__ # {'__wrapped__': <function __main__.mul(x, y)>}

클래스에 데코레이팅 처리

데코레이터 처리를 함수로 정의할 때, 인자로 클래스를 받고 내부에 초기화 함수를 정의해서 클래스에 추가한 후에 클래스를 반환한다.
특정 기능을 클래스에 추가하여 정의할 때도 데코레이터를 사용해 처리할 수 있다.

def decorator(cls):
    def __init__(self, name): # 초기화 함수를 함수 내부에 정의
        self.name = name
    cls.__init__ = __init__ # 클래스에 초기화 함수 추가
    return cls # 클래스를 반환

@decorator # 초기화 함수가 없는 클래스에 초기화 함수를 추가하는 데코레이터 처리
class DecoratedClass:
    def method(self):
        return self.name

dec = DecoratedClass('Decorator')
dec # <__main__.DecoratedClass at ~~~>
dec.method() # 'Decorator', 객체 속성을 읽고 반환

# 아무것도 없는 클래스에 위의 데코레이터를 처리하면 같은 초기화 함수가 삽입된다.
@decorator
class Klass:
    pass

k = Klass('Class')
Klass.__dict__ # __init__이 들어있다.
k.__dict__ # {'name': 'Class'}

반복해서 계산 수행

재귀 호출 방식은 일반적인 재귀 호출 방식과 꼬ㄱ리 재귀 호출 방식을 구분해서 처리해야 한다.
파이썬은 재귀 호출에 대한 제약이 있어 재귀 호출할 때 특정 개수 이상 사용할 수 없게 한다.

def sigma(n):
    if n in [0]:
        return 0
    return n + sigma(n - 1)

sigma(10) # 55

def pie(n): # 연속된 숫자의 곱
    if n in [0, 1]:
        return 1
    return n * pie(n - 1)

pie(3) # 6

꼬리 재귀 호출은 반환문에서 함수를 호출할 때 함수만 호출한다.

def sigma_(n, accum=0): # 꼬리 호출을 이용해서 결과 계산을 하지 않게 변경한 함수
    if n in [0]:
        return accum
    return sigma_(n - 1, n + accum) # 계산된 상태를 보관하기 위해 매개변수 추가

sigma_(10) # 55

def pie_(n, accum=1):
    if n in [0, 1]:
        return accum
    return pie(n - 1, n * accum)

pie_(3) # 6

재귀 호출 함수를 클래스로 변환하고 객체에 저장할 값은 표현식으로 바꿔서 특정 계산식도 들어올 수 있도록 변경

class Sigma: # 함수를 클래스로 변경
    def __init__(self, expr):
        self._expr = expr # 일반항을 문자열로 보관

    def sigma(self): # 연속하는 값을 계산하는 함수 정의
        def inner(self, n, accum=0):
            if n in [0]:
                accum = accum + eval(self._expr)
                return accum # eval을 실행해서 값을 구한다.
            else:
                return inner(self, n - 1, (accum + eval(self._expr))) # 재귀 호출할 때 함수와 별도의 연산을 실행하지 않는다.
        return inner

s = Sigma('2*n+1') # 일반항을 가진 객체 생성
s.sigma()(s, 10) # 121, 연속하는 숫자 계산 메소드를 실행한 후, 내부 함수를 반환받아 계산
# 클래스에 n을 전달해서 일반항에 있는 n에 해당 수가 들어간다.

보호 속성

개발자들이 속성 이름으로 이 속성을 사용하지 않게 관행화하는 것이 보호 속성이다.

class Klass: # 밑줄이 하나 있는 보호 속성을 정의한 클래스
    _name = 'J' # 클래스 속성 이름에 밑줄을 하나 추가해서 보호 속성으로 만든다.
    def __init__(self, name):
        self._name = name # 객체의 속성 이름 앞에 밑줄을 하나 넣어 보호 속성으로 만든다.

    def getName(self): # 보호 속성을 조회하는 함수 정의
        return self._name

Klass.__dict__ # 보호 속성이라도 클래스에서 접근해서 조회할 수 있다.
Klass._name # 'J'

클래스로 객체를 만들어도 보호 속성이지만 직접 접근해서 조회할 수 있다.

k = Klass('K')
k.__dict__ # {'_name': 'K'}, 1개의 속성이 있다.
k.getName() # 'K'
k._name # 'K', 파이썬은 보호 속성을 명명규칙에 따라 표기만 해서, 직접 접근해서 조회하면 속성값을 알 수 있다.

클래스 속성과 객체 속성의 일므 앞에 밑줄을 2개 붙이면 이것을 맹글링(Mangling)이라고 한다.
클래스 내부에서는 밑줄이 2개 붙은 이름을 사용하지만, 외부에서 사용할 때는 밑줄이 붙은 클래스가 더 추가된다.

class Mangling:
    __name = 'J' # 밑줄이 2개 붙은 클래스 속성 정의
    
    def __init__(self, name):
        self.__name = name # 밑줄이 2개 붙은 객체의 속성을 하나 정의
    
    def getName(self): # 객체의 보호 속성을 조회하는 함수 정의
        return self.__name

    @classmethod
    def getAttr(cls): # 클래스 속성을 조회하는 클래스 메소드 정의
        return cls.__name # 클래스 메소드에 저장될 함수 정의

Mangling.__dict__ # 클래스 이름 앞에 밑줄이 하나 더 붙은 이름으로 바뀐 것을 확인할 수 있다.
# {'_Mangling__name': 'J' ~~~}

Mangling.__name # 클래스에서 클래스 속성을 접근하면 맹글링으로 처리되는데 이름이 매칭되는 것이 없어서 예외가 발생한다.

# 메소드로 조회할 수도 있고, 이름 작성 기준을 알고 조회하면 속성에 바로 접근할 수 있다.
Mangling.getAttr() # 'J'
Mangling._Mangling__name # 'J', 밑줄을 2개 붙이면 이름이 변경되는 것을 맹글링리라고 한다.

# 객체도 마찬가지로 이름이 변경된다.
m = Mangling('K')
m.__dict__ # {'_Mangling__name': 'K'}
m.getName() # 'K'
m.__name # 예외 발생
m._Mangling__name # 'K', 객체 속성도 맹글링 이름 체계로 작성해서 조회하면 값을 알 수 있다.

프로퍼티(property)

객체는 속성과 메소드로 처리하지만, 이름으로 접근하면 내부적으로 메소드가 실행되어 속성과 메소드를 같은 방식으로 사용할 수 있다.
파이썬 속성을 이름으로 접근해서 조회할 수 있는 property도 클래스다.
프로퍼티를 지정할 때 속성과 함수 이름이 같지 않게 속성의 이름을 보호 속성으로 만드는 것을 생각해야 한다.

class Data:
    def __init__(self, data): # 초기화 함수를 정의할 때, 속성 이름을 보호 속성으로 생성
        self._data = data

    @property # 데코레이터로 함수를 프로퍼티 객체로 변환
    def data(self): # 속성과 같은 이름으로 함수 정의
        return self._data

Data.__dict__ # {'data': <property at ~~~>}, data가 프로퍼티 객체라는 것을 알 수 있다.

d = Data(10)
d.data # 10, 함수 이름에 접근해 객체의 속성값 조회
d.__dict__ # {'_data': 10}

d.data = 3 # 프로퍼티에 값을 할당하면 예외가 발생하는데, 이는 프로퍼티를 정의할 때 조회만 정의했기 때문이다.

클래스 하나, 속성 하나로 프로퍼티 등록

class DataInt: # 같은 이름으로 속성을 조회할 클래스 정의
    @property # 조회하는 함수를 등록할 때는 property 클래스로 데코레이터 처리
    def data(self):
        return self._data

    @data.setter # 이름으로 접근해서 속성을 갱신하는 함수를 정의할 때는 setter 메소드로 함수를 등록
    def data(self, value):
        if isinstance(value, int): # 갱신하는 값이 정수가 아니면 예외 처리
            self._data = value
        else:
            raise AttributeError('Not Int')

    @data.deleter # 이름으로 접근해서 속성을 삭제하는 함수를 정의할 때는 deleter 메소드로 함수를 등록
    def data(self):
        raise AttributeError("Can't delete")

t = DataInt() # 초기화 함수가 정의되지 않아서 속성이 없는 객체가 생성된다.
t.__dict__ # {}

t.data = 12 # 갱신할 수 있는 함수를 프로퍼티에 등록하고 값을 갱신할 때 객체에 속성을 추가
t.__dict__ # {'_data': 12}
t.data # 12

# 객체에 문자열 할당 했을 때
try:
    t.data = '123'
except Exception as e:
    print(e) # Not Int

# 객체를 삭제할 때
try: # 프로퍼티에 삭제를 정의했지만, 예외만 처리해서 삭제할 경우 내부에 정의된 예외 발생
    del t.data
except Exception as e:
    print(e) # Can't delete

프로퍼티 클래스

class my_property: # 프로퍼티 클래스 구조와 같은 클래스 정의
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget # 초기화 함수에 3개의 함수를 받아 3개의 속성에 저장하는 구조 생성
        self.fset = fset
        self.fdel = fdel

    def setter(self, fset): # 변경하는 함수를 등록하는 메소드 정의
        self.fset = fset
        return self

    def deleter(self, fdel): # 삭제하는 함수를 등록하는 메소드 정의
        self.fdel = fdel
        return self

    def getter(self, fget): # 조회하는 함수를 등록하는 메소드 정의
        self.fget = fget
        return self

    # 디스크립터 클래스 __get__, __set__, __delete__ 함수 정의한다.
    # 이름으로 접근하면 자동 실행되어 결과를 반환한다.
    def __get__(self, instance, owner): # 이름으로 조회하면 함수를 실행해 속성값 조회
        return self.fget(instance)

    def __set__(self, instance, value): # 이름으로 값을 갱신하면 속성을 변경하는 함수를 실행하여 결과를 변경
        if not self.fset:
            raise AttributeError
        return self.fset(instance, value)

    def __delete__(self, instance): # 이름으로 삭제하면 삭제하는 함수를 실행해 속성값 삭제
        if not self.fdel:
            raise AttributeError
        return self.fdel(instance)

데코레이터 처리

class OnlyInt:
    @my_property # 프로퍼티 클래스로 데코레이터를 처리해서 프로퍼티 객체 생성
    def data(self): # 첫 번째로 조회 함수 등록
        return self._data

    @data.setter # 프로퍼티 객체의 setter 메소드로 등록
    def data(self, value):
        if isinstance(value, int):
            self._data = value
        else:
            raise AttributeError('Only Int')

    @data.deleter # 프로퍼티 객체의 deleter 메소드로 등록
    def data(self):
        raise AttributeError('No Delete')

c = OnlyInt()
c.__dict__ # {}
c.data = 999 # 속성 갱신
c.data # 999

try:
    c.data = 'str'
except Exception as e:
    print(e) # Only Int

계산을 처리하는 프로퍼티

class Double:
    def __init__(self, value):
        self.value = value

    @property # 객체의 속성을 사용해 계산하는 함수를 프로퍼티로 처리
    def double(self):
        return self.value * 2

    @double.setter # 변경하면 예외가 발생해야 하므로 변경 처리하는 함수 등록
    def double(self, value):
        raise AttributeError("Can't Change")

d = Double(3)
d.double # 6, 이름으로 접근하면 계산 결과를 반환
try:
    d.double = 999
exception Exception as e:
    print(e) # Can't Change

디스크립터

클래스를 캡슐화하려면 속성에 직접 접근해 사용하지 않게 구성해야 한다.
파이썬은 디스크립터라는 클래스를 만들고, 속성에 스페셜 메소드로 접근해서 조회, 갱신, 삭제한다.

class Descriptor:
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner): # 이름으로 접근하면 값을 조회하는 스페셜 메소드 정의
        print('get')
        return self.value

class A: # 디스크립터 속성을 사용하는 클라이언트 클래스 정의
    value = Descriptor(1) # 2개의 클래스 속성에 디스크립터 객체를 생성한다.
    value1 = Descriptor(2) # 디스크립터 객체는 반드시 클래스 속성으로 정의한다.

# 객체를 만들고 객체에서 value 속성을 조회하면 __get__ 메소드가 실행된다.
a = A()
a.value # get이 출력되고 나서 1 출력
a.value1 # get이 출력되고 나서 2 출력

__get__과 __set__ 정의

class Descriptor_:
    def __get__(self, instance, owner): # 디스크립터 객체, 클라이언트 객체, 클라이언트 클래스 매개변수를 정의
        print('get')
        if not hasattr(instance, '_value'): # 속성이 없으면 __set__으로 초깃값을 가진  속성 추가
            self.__set__(instance,0)
        return instance._value # 클라이언트 객체의 속성 조회

    def __set__(self, instance, value): # 디스크립터 객체, 클라이언트 객체, 클라이언트 속성 갱신 매개변수 정의
        print('set')
        instance._value = value # 클라이언트 속성 갱신

class B:
    value = Descriptor_()
    value1 = Descriptor_()

B.__dict__ # 이름공간에 'value'와 'value1'이 클래스 속성으로 생성되어 있다.

b = B()
b.value # get, set, 0 순으로 출력된다. __get__을 호출하고, __set__을 호출해서 속성을 추가하기 때문이다.
b.value1 # get, 0 순으로 출력된다. 추가적인 속성이 만들어지지 않는다.

b.value1 = 999 # set 출력
b.__dict__ # {'_value': 999}, 클라이언트 객체를 확인하면 하나의 속성만 만들어져 있다.
b.value = 111 # 다른 속성으로 값을 갱신
b.__dict__ # {'_value': 111}, 클라이언트 객체 내에 하나의 속성만 만들어져서 값이 갱신된다.

디스크립터 클래스를 정의할 때 객체에 들어갈 속성 이름을 저장할 수 있는데, 이때는 __set_name__을 정의한다.

class Descriptor_c:
    def __set_name__(self, owner, name): # 클라이언트의 속성 이름을 읽어와서 디스크립터 객체에 클라이언트 객체의 속성 이름을 관리
        self._name = '_' + name

    def __get__(self, instance, owner):
        print('get')
        if not (self._name in instance.__dict__):
            self.__set__(instance, 0) # 클라이언트 객체의 이름공간에 속성 이름이 없으면 초기화

        return instance.__dict__[self._name] # 클라이언트 이름공간을 이요해서 속성 조회

    def __set__(self, instance, value):
        print('set')
        instance.__dict__[self._name] = value # 클라이언트 객체의 이름공간을 확인해서 값 갱신

class C:
    value = Descriptor_c()
    value1 = Descriptor_c()

c = C()
c.value # get, set, 0 순으로 출력
c.value1 # get, set, 0 순으로 출력
c.__dict__ # {'_value': 0, '_value1': 0}, 2개의 속성이 있다.

c.value = 111 # set
c.value1 = 999 # set
c.__dict__ # {'_value': 111, '_value1': 999}, 2개의 속성 값이 갱신되었다.

데이터/비데이터 디스크립터

데이터 디스크립터란 데이터를 갱신할 수 있는 디스크립터이다.
비데이터 디스크립터란 단순히 조회만 하는 디스크립터이다.

디스크립터의 활용

def add(x, y):
    return x + y

add = add.__get__(add) # 함수에서 __ get__에 인자로 함수 객체를 넣고 조회하면 메소드를 반환한다.
add # <bound method add of <function add at ~~~~~>>

# 메소드로 정의 되면 __func__ 내부에 함수가 저장된다.
add.__func__(1, 2) # 3

비데이터 디스크립터

class Descriptor_nd:
    def __init__(self, func):
        self._func = func

    def __set_name__(self, owner, name): # 클라이언트 속성의 이름을 받아서 내부에 저장
        self._name = '_' + name

    def __get__(self, instance, owner): # 이름으로 접근하면 내부 함수 반환
        print('get')
        def inner(*args, **kwargs): # 내부함수는 저장된 함수의 인자를 받는다.
            return self._func(*args, *kwargs)
        return inner

def mul(x, y):
    return x * y

class Non_data:
    mul = Descriptor_nd(mul)

nd = Non_data()
nd.mul # 속성에 접근하면 내부 함수를 반환한다.
#  get과 <function __main__.Descriptor_nd.__get__.<locals>.inner(*args, **kwargs)> 출력
nd.mul(1, 2) # get, 2 순으로 출력

디스크립터 클래스를 정의한 후에 데코레이터 사용

from functools import parial

class descriptor:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner): # 부분함수로 전달해서 다시 호출하면 실행한 결과를 보여준다.
        return partial(self.func, instance)

class A:
    @descriptor
    def sum(self, a, b, c):
        return a + b + c

A.__dict__ # sum을 클라이언트 클래스의 이름공간에 있는 것을 확인할 수 있고, sum은 디스크립터 속성임도 알 수 있다.

a = A()
a.sum(1, 2, 3) # 6, 객체가 sum에 접근하면 내부에 저장된 sum함수를 부분함수로 반환하고 이를 호출하면 실행 결과를 반환한다.

데이터 디스크립터를 작성하려면 __set__과 __delete__를 추가한다.

class MutableAttribute: # 속성을 갱신하는 디스크립터 클래스 정의
    def __init__(self, value=None):
        self.value = value

    def __set_name__(self, owner, name): # 클라이언트 속성의 이름을 받아서 객체의 속성에 이름 저장
        self._name = '_' + name

    def __get__(self, instance, owner): # 클라이언트 객체 속성이 없으면 디스크립터 객체의 속성 값 반환
        if self._name in instance.__dict__:
            return instance.__dict__[self._name]
        else:
            return self.value

    def __set__(self, instance, value): # 클라이언트 객체의 속성 갱신
        instance.__dict__[self._name] = value
    
    def __delete__(self, instance): # 디스크립터 객체의 값 삭제
        del self.value

디스크립터 클래스에서 변경할 수 없는 객체를 만들려면 __set__과 __delete__로 정의하지만 변경이나 삭제할 때 예외가 발생한다.

class ImmutableAttribute: # 변경할 수 없는 디스크립터 클래스 정의
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner): # 이름으로 접근하면 저장된 함수 실행
        return self.func(instance)

    def __set__(self, instance, value): # 변경이 들어오면 예외 처리
        raise AttributeError('Can not Change')
    
    def __delete__(self, instance): # 삭제가 들어오면 예외 처리
        raise AttributeError('Can not Delete')

class Circle:
    pi = 3.1415
    radius = MutableAttribute(10) # 반지름은 변경할 수 있는 디스크립터
    diameter = ImmutableAttribute(lambda self : self.radius * 2) # 지름은 변경할 수 없는 람다 함수 등록

    @ImmutableAttribute # 원의 둘레를 계산하는 함수를 변경할 수 없게 데코레이터 처리
    def circumference(self):
        return self.radius * self.pi * 2

    @ImmutableAttribute # 원의 면적도 변경할 수 없게 처리
    def area(self):
        return self.radius ** 2 * self.pi

c = Circle()
c.radius, c.diameter # (10, 20), 이름으로 접근
c.circumference, c.area # 원 둘레와 면적도 이름으로 조회가 가능하다.

c.radius = 100 # 반지름을 갱신하고 면적을 다시 확인하면 면적 값이 변경된 것을 확인할 수 있다.

디스크립터 속성 자료형 제약하기

디스크립터 클래스를 작성하고 속성에 특정한 제약을 처리할 수 있다.

class Field:
    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int): # 정수가 아니면 갱신할 수 없도록 제약
            raise ValueError(f'Not Int, Please Enter Int Value {self.name}')
        instance.__dict__[self.name] = value

    def __set_name__(self, owner, name):
        self.name = name

class A:
    var = Field()

a = A()
a.var = 100 # 정수로 변경하면 값이 변경된다.
a.__dict__ # {'var': 100}

a.var = 100.1 # 실수로 변경하면 내부의 예외가 발생한다.