본문 바로가기
Python Object-oriented Programming

descriptors

by 자동매매 2023. 4. 4.

출처 :https://www.pythontutorial.net/python-oop/python-descriptors/

 

Python Descriptors

In this tutorial, you'll learn about Python descriptors, how they work, and how to apply them effectively.

www.pythontutorial.net

 

Introduction to the Python descriptors

 

개의 인스턴스 속성 ( first_name last_name ) 가진 Person 클래스 있다고 가정합니다.

 

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

 

그리고 first_name last_name 속성이 비어 있지 않은 문자열이 되기를 원합니다. 하지만 일반 속성은 이를 보장할 없습니다.

데이터 유효성을 적용하려면  다음과 같이 property getter setter 메서드를  사용하여 구현 있습니다.

 

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise ValueError('The first name must a string')

        if len(value) == 0:
            raise ValueError('The first name cannot be empty')

        self._first_name = value

    @property
    def last_name(self):
        return self._last_name

    @last_name.setter
    def last_name(self, value):
        if not isinstance(value, str):
            raise ValueError('The last name must a string')

        if len(value) == 0:
            raise ValueError('The last name cannot be empty')

        self._last_name = value

 

Person 클래스에서 getter 속성 값을 반환하고 setter 속성에 할당하기 전에 유효성을 검사합니다.

코드는 완벽하게 작동합니다. 그러나 이름과 성을 검증하는 유효성 검사 논리가 동일하여 중복이다.

 

또한 클래스에 비어 있지 않은 문자열이 필요한 property 많으면 논리를 다른 property 복제해야합니다. 하지만, 유효성 검사 논리는 다시 사용할 없습니다.

논리 중복을 방지하려면 데이터의 유효성을 검사하고 다른 property에서 메서드를 재사용하는 메서드가 있을 있습니다. 방법을 사용하면 재사용이 가능합니다. 그러나 Python descriptor(설명자) 사용하여 문제를 해결하는 좋은 방법을 가지고 있습니다.

 

먼저 가지 메서드 __set_name__, __get__ __set__ 구현하는 descriptor 클래스를 정의합니다.

 

class RequiredString:
    def __set_name__(self, owner, name):
        self.property_name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self

        return instance.__dict__[self.property_name] or None

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError(f'The {self.property_name} must be a string')

        if len(value) == 0:
            raise ValueError(f'The {self.property_name} cannot be empty')

        instance.__dict__[self.property_name] = value

 

Second, use the RequiredString class in the Person class:

둘째, Person 클래스에서 필수 문자열 클래스를 사용합니다.

 

class Person:
    first_name = RequiredString()
    last_name = RequiredString()

 

If you assign an empty string or a non-string value to the first_name or last_name attribute of the Person class, you’ll get an error.

For example, the following attempts to assign an empty string to the first_name attribute:

Person 클래스의 first_name 또는 last_name 속성에   문자열  또는  문자열이 아닌 값을 할당  하면 오류가 발생합니다.

예를 들어 다음은 first_name 속성에 문자열을 할당하려고 시도합니다.

 

try:
    person = Person()
    person.first_name = ''
except ValueError as e:
    print(e)

Error:

The first_name must be a string

 

Also, you can use the RequiredString class in any class with attributes that require a non-empty string value.

Besides the RequiredString, you can define other descriptors to enforce other data types like age, email, and phone. And this is just a simple application of the descriptors.

Let’s understand how descriptors work.

또한  비어 있지 않은 문자열 값이 필요한 속성이 있는 모든 클래스에서 RequiredString 클래스를 사용할 있습니다.

RequiredString 외에도 다른 descriptor 정의하여 나이, 전자 메일 전화와 같은 다른 데이터 형식을 적용할 있습니다. 그리고 이것은 descriptor 간단한 응용 프로그램 일뿐입니다.

descriptor 작동 방식을 이해합시다.

 

Descriptor protocol

 

In Python, the descriptor protocol consists of three methods:

  • __get__ gets an attribute value
  • __set__ sets an attribute value
  • __delete__ deletes an attribute

Optionally, a descriptor can have the __set_name__ method that sets an attribute on an instance of a class to a new value.

Python에서 descriptor 프로토콜은 가지 방법으로 구성됩니다.

·    __get__ 속성 값을 가져옵니다.

·    __set__ 속성 값을 설정합니다.

·    __delete__ 속성을 삭제합니다.

선택적으로 descriptor에는  클래스 인스턴스의 속성을 값으로 설정하는 __set_name__ 메서드가 있을 있습니다.

 

What is a descriptor

 

A descriptor is an object of a class that implements one of the methods specified in the descriptor protocol.

Descriptors have two types: data descriptor and non-data descriptor.

  1. A data descriptor is an object of a class that implements the __set__ and/or __delete__ method.
  2. A non-data descriptor is an object that implements the __get__ method only.

The descriptor type specifies the property lookup resolution that we’ll cover in the next tutorial.

descriptordescriptor 프로토콜에 지정된 메서드 하나를 구현하는 클래스의 obje ct입니다.

descriptor에는 데이터 descriptor 비데이터 descriptor 가지 유형이 있습니다.

1.   데이터 descriptor __set__ /또는 __delete__ 메서드를 구현하는 클래스의 개체입니다.

2.   n-data descriptor 없음은 __get__ 메서드만 구현하는 개체입니다  .

descriptor 형식은 다음 자습서에서 다룰 속성 조회 해상도를 지정합니다.

 

How descriptors work

 

The following modifies the RequiredString class to include the print statements that print out the arguments.

폴로 윙은  인수를 출력하는 인쇄 문을  포함하도록 RequiredString 클래스를수정합니다.

 

class RequiredString:
    def __set_name__(self, owner, name):
        print(f'__set_name__ was called with owner={owner} and name={name}')
        self.property_name = name

    def __get__(self, instance, owner):
        print(f'__get__ was called with instance={instance} and owner={owner}')
        if instance is None:
            return self

        return instance.__dict__[self.property_name] or None

    def __set__(self, instance, value):
        print(f'__set__ was called with instance={instance} and value={value}')

        if not isinstance(value, str):
            raise ValueError(f'The {self.property_name} must a string')

        if len(value) == 0:
            raise ValueError(f'The {self.property_name} cannot be empty')

        instance.__dict__[self.property_name] = value


class Person:
    first_name = RequiredString()
    last_name = RequiredString()

 

The __set_name__ method

 

When you compile the code, you’ll see that Python creates the descriptor objects for first_name and last_name and automatically call the __set_name__ method of these objects. Here’s the output:

코드를 컴파일할 Python first_name last_name 대한 descriptor 개체를 만들고 이러한 개체의 __set_name__ 메서드를  자동으로 호출  하는 것을 있습니다  . 출력은 다음과 같습니다.

 

__set_name__ was called with owner=<class '__main__.Person'> and name=first_name
__set_name__ was called with owner=<class '__main__.Person'> and name=last_name

 

In this example, the owner argument of __set_name__ is set to the Person class in the __main__ module, and the name argument is set to the first_name and last_name attribute accordingly.

It means that Python automatically calls the __set_name__ when the owning class Person is created. The following statements are equivalent:

예제에서 __set_name__ 소유자 인수는 __main__ 모듈의 Person 클래스   설정  되고 그에 따라 name 인수는 first_name last_name 속성으로 설정됩니다.

이는 파이썬이  소유 클래스 Person 생성  자동으로 __set_name__ 호출한다는 것을 의미합니다  . 다음 문은 동일합니다.

 

first_name = RequiredString()

and

first_name.__set_name__(Person, 'first_name')

 

Inside, the __set_name__ method, we assign the name argument to the property_name instance attribute of the descriptor object so that we can access it later in the __get__ and __set__ method:

__set_name__ 메서드  내에서 descriptor 개체의 property_name 인스턴스 속성에 name 인수   할당하여 나중에 __get__ __set__ 메서드에서 액세스할  있도록 합니다.

 

self.property_name = name

 

The first_name and last_name are the class variables of the Person class. If you look at the Person.__dict__ class attribute, you’ll see two descriptor objects first_name and last_name:

first_name last_name Person 클래스의 클래스 변수입니다   . Person.__dict__ class 속성을 보면 개의 descriptor 개체가 first_name되고 last_name 표시됩니다.

 

from pprint import pprint

pprint(Person.__dict__)

Output:

mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>,
            '__doc__': None,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref__' of 'Person' objects>,
            'first_name': <__main__.RequiredString object at 0x0000019D6AB947F0>,
            'last_name': <__main__.RequiredString object at 0x0000019D6ACFBE80>})

 

The __set__ method

 

Here’s the __set__ method of the RequiredString class:

다음은 필수 문자열 클래스의 __set__ 메서드입니다.

 

def __set__(self, instance, value):
    print(f'__set__ was called with instance={instance} and value={value}')

    if not isinstance(value, str):
        raise ValueError(f'The {self.property_name} must be a string')

    if len(value) == 0:
        raise ValueError(f'The {self.property_name} cannot be empty')

    instance.__dict__[self.property_name] = value

 

When you assign the new value to a descriptor, Python calls __set__ method to set the attribute on an instance of the owner class to the new value. For example:

descriptor 값을 할당하면 Python __set__ 메서드를 호출하여 소유자 클래스 인스턴스의 속성을 값으로 설정합니다. 예를 들어:

 

person = Person()
person.first_name = 'John'

Output:

__set__ was called with instance=<__main__.Person object at 0x000001F85F7167F0> and value=John

 

In this example, the instance argument is person object and the value is the string 'John'. Inside the __set__ method, we raise a ValueError if the new value is not a string or if it is an empty string.

Otherwise, we assign the value to the instance attribute first_name of the person object:

예제에서 인스턴스 인수   person 객체이고 값은 문자열  'John'입니다. __set__ 메서드 내에서   값이 문자열이 아니거나 문자열인 경우 ValueError  발생시킵니다.

그렇지 않으면 person 객체 인스턴스 속성 first_name 값을 할당  합니다.

 

instance.__dict__[self.property_name] = value

 

Note that Python uses instance.__dict__ dictionary to store instance attributes of the instance object.

Once you set the first_name and last_name of an instance of the Person object, you’ll see the instance attributes with the same names in the instance’s __dict__. For example:

Python instance.__dict__ 사전을 사용하여 인스턴스 객체   인스턴스 속성을 저장합니다  .

Person 객체의 인스턴스 first_name last_name 설정하면 내부  __dict__ 동일한 이름의 인스턴스 속성이 표시됩니다. 예를 들어:

 

person = Person()
print(person.__dict__)  # {}

person.first_name = 'John'
person.last_name = 'Doe'

print(person.__dict__) # {'first_name': 'John', 'last_name': 'Doe'}

Output:

{}
{'first_name': 'John', 'last_name': 'Doe'}

 

The __get__ method

 

The following shows the __get__ method of the RequiredString class:

다음은 필수 문자열 클래스의 __get__ 메서드를  보여 줍니다  .

def __get__(self, instance, owner):
    print(f'__get__ was called with instance={instance} and owner={owner}')
    if instance is None:
        return self

    return instance.__dict__[self.property_name] or None/pre>

 

Python calls the __get__ method of the Person‘s object when you access the first_name attribute. For example:

파이썬은 first_name 속성에 액세스할 Person 객체의 __get__ 메서드를  호출합니다  . 예를 들어:

 

person = Person()

person.first_name = 'John'
print(person.first_name)

Output:

__set__ was called with instance=<__main__.Person object at 0x000001F85F7167F0> and value=John
__get__ was called with instance=<__main__.Person object at 0x000001F85F7167F0> and owner=<class '__main__.Person'>

 

The __get__ method returns the descriptor if the instance is None. For example, if you access the first_name or last_name from the Person class, you’ll see the descriptor object:

__get__ 메서드는 인스턴스가 None  경우 descriptor 반환합니다. F 또는 예를 들어 Person 클래스  에서 first_name 또는 last_name 액세스하면  descriptor 개체가 표시됩니다.

 

print(Person.first_name)

Output:

<__main__.RequiredString object at 0x000001AF1DA147F0>

 

If the instance is not None, the __get__() method returns the value of the attribute with the name property_name of the instance object.

 

인스턴스가 None  아닌 경우 __get__() 메서드는 인스턴스 객체 이름 property_name  함께 속성 값을 반환합니다.

Summary

 

  • Descriptors are objects of class that implements one of the method in the descriptor protocol including __set__, __get__, __del__

·   descriptor __set__, __get__ __del__ 포함하는 descriptor 프로토콜의 메서드 하나를 구현하는 클래스의 개체입니다.

 

'Python Object-oriented Programming' 카테고리의 다른 글

enum 기타 기능  (0) 2023.04.07
Data vs. Non-data Descriptors  (0) 2023.04.04
Multiple inheritance  (0) 2023.04.04
Interface Segregation Principle  (0) 2023.04.04
Liskov Substitution Principle  (0) 2023.04.04

댓글