[PYTHON] Reuse the behavior of the @property method by using a descriptor [16/100]

Introduction

The other day I learned about 100 Days Of Code, which was popular on Twitter for a while. The purpose of this article is to keep a record and output how much I, as a beginner, can grow through 100 days of study. I think there are many mistakes and difficult to read. I would appreciate it if you could point out!

Teaching materials to be learned this time

--Chapter 8 structure --Page 216 of this chapter

Today's progress

--Progress: Pages 95-101 --Chapter 4: Metaclasses and Attributes ――I will write down what I often forget or didn't know about what I learned today.

Use descriptors for reusable @property methods

Prerequisite knowledge

@staticmethod

Convert the method to a static method. Static methods do not take an implicit first argument. That is, it does not have self as its first argument. When you get a static method from a class or class instance, the actual returned object is a wrapped object and no further conversions are made.

__get__ method

object.__get__(self, instance, owner)

The __get__ method is a method that is called every time the attributes of the owning class or the attributes of an instance of that class are retrieved.

Let's look at an example.

class SampleGet(object):
    def __init__(self, x):
        self._x = x

    def __get__(self, instance, owner):
        print('Hello')
        print(instance)
        print(owner)
        return owner

class Sample(object):
    sample_get = SampleGet(100)

obj = Sample()
print('print', obj.sample_get)               #Because we got the attributes of an instance of the class__get__Call the method

Output result

Hello
<__main__.Sample object at 0x00000289EDAB11C8>          # instance
<class '__main__.Sample'>                               # owner
print <class '__main__.Sample'>                         # obj.sample_get 

__set__ method

object.__set__(self, instance, value)

The __get__ method is the method that is called when setting the attribute on the instance of the owner class to a new value (value).

class SampleGet

Let's look at an example.

class SampleSet(object):
    def __init__(self, x):
        self._x = x

    def __set__(self, instance, value):
        print('World!')
        print(instance)
        print(value)
        return self.value

class Sample(object):
    sample_set = SampleSet(100)

obj = Sample()                                      #Instantiation of Sample class
obj.sample_set = 200                                #New value for attributes on the instance(200)Set to
# World!                                            #Because the value of the attribute on the instance has changed__set__The method is called and World!And output
# <__main__.Sample object at 0x000002069D921188>    # instance
# 200                                               # value

Descriptor

Descriptors define how attribute access is interpreted in the language. Descriptor classes use the __get__, __set__, and __delete__ methods to override attribute access. An object is said to be a scripter if any of these methods are defined for the object.

weakref

It is one of the Python built-in modules, and you can take measures against memory leaks. This module removes an instance from the last remaining reference of the instance programmatically at run time if it is only held in the key set of that dictionary.

Main subject

@property is very convenient because you can add necessary processing sequentially, but the disadvantage is that it cannot be reused. The method you decorate cannot be reused for multiple attributes in the same class. To solve this, use a descriptor. As an example, suppose you want to create a class that manages the evaluation of each subject in a student's exam.

class Grade(object):
    #descriptor
    def __get__(self, instance, instance_type):
        pass
    
    def __set__(self, instance, value):
        pass

class Exam(object):
    #Generate a Grade instance for each subject
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()

exam = Exam()
exam.writing_grade = 40
print(exam.writing_grade)

exam.writing_grade = 40 is interpreted as follows: Exam.__dict_['writing_grade'].__set__(exam, 40) That is, __set__ () is called with an instance of __set__ (self, instance, value) as an argument and a value of 40 as an argument. print (exam.writing_grade) is interpreted as follows: print(Exam.__dict__['writing_grade'].__get__(exam, Exam)) In other words, __get__ () is called with exam insntace of __get__ (self, instance, value) and value as an argument of Exam.

Now, let's proceed with the implementation.

from weakref import WeakKeyDictionary

class Grade(object):
    def __init__(self):
        # self._values = {}                  #With this, the instance reference never becomes zero, so memory cannot be recovered by garbage collection.
        self._values = WeakKeyDictionary()   #Delete the Exam instance if the only remaining reference at the end of the instance that replaces the dictionary is the key set for that dictionary.

    def __get__(self, instance, instance_type):
        if instance is None:
            return self
        return self._values.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        self._values[instance] = value

first_exam = Exam()
first_exam.writing_grade = 80
first_exam.science_grade = 99
print('Writing', first_exam.writing_grade)
print('Science', first_exam.science_grade)
second_exam = Exam()
second_exam.writing_grade = 75
print('Second', second_exam.writing_grade)

Output result

Writing 80
Science 99
Second 75

By using the descriptor, I was able to write the code concisely without using @property many times.

Reference site

https://docs.python.org/ja/3.7/reference/datamodel.html#object

Recommended Posts

Reuse the behavior of the @property method by using a descriptor [16/100]
Feature extraction by TF method using the result of morphological analysis
Generate a hash value using the HMAC method.
Approximation by the least squares method of a circle with two fixed points
Calculation of the shortest path using the Monte Carlo method
Cut a part of the string using a Python slice
Create a dictionary by searching the table using sqlalchemy
The copy method of pandas.DataFrame is deep copy by default
Python: Calculate the uniform flow depth of a rectangular cross section using the Brent method
A note on the default behavior of collate_fn in PyTorch
A simple Python implementation of the k-nearest neighbor method (k-NN)
A concrete method of predicting horse racing by machine learning and simulating the recovery rate
Reuse the results of clustering
A memo about the behavior of bowtie2 during multiple hits
I tried a little bit of the behavior of the zip function
Behavior of pandas rolling () method
The story of creating a database using the Google Analytics API
Try using [Tails], a purveyor of hackers (?), By USB booting.
A memorandum of using eigen3
[Python] How to use the for statement. A method of extracting by specifying a range or conditions.
A method of converting the style of an image while preserving the color
cv2.Canny (): Makes the adjustment of edge detection by the Canny method nice
Do a search by image from the camera roll using Pythonista3
Read the standard output of a subprocess line by line in Python
Create a pandas Dataflame by searching the DB table using sqlalchemy
A function that measures the processing time of a method in python
Evaluate the performance of a simple regression model using LeaveOneOut cross-validation
Try using Elasticsearch as the foundation of a question answering system
Finding the optimum value of a function using a genetic algorithm (Part 1)
[Anomaly detection] Try using the latest method of deep distance learning
Find the ratio of the area of Lake Biwa by the Monte Carlo method
[python] A note that started to understand the behavior of matplotlib.pyplot
[Kaggle] I made a collection of questions using the Titanic tutorial
Behavior of python3 by Sakura's server
About the behavior of yield_per of SqlAlchemy
The story of writing a program
A super introduction to Django by Python beginners! Part 2 I tried using the convenient functions of the template
Memorandum of introduction of EXODUS, a data model of the finite element method (FEM)
Installation method using the pip command of the Python package (library) Mac environment
I'm stunned by the behavior of filter () due to different versions of Python
Hit a method of a class instance with the Python Bottle Web API
Find the minimum value of a function by particle swarm optimization (PSO)
Implement a model with state and behavior (3) --Example of implementation by decorator
I tried to verify the result of A / B test by chi-square test
What Java users thought of using the Go language for a day
Check the behavior of destructor in Python
Count / verify the number of method calls.
Saddle point search using the gradient method
A memorandum of using Python's input function
About the behavior of enable_backprop of Chainer v2
Measure the relevance strength of a crosstab
Create a graph using the Sympy module
Impressions of using Flask for a month
A quick overview of the Linux kernel
Try cluster analysis using the K-means method
[python] [meta] Is the type of python a type?
[Pandas_flavor] Add a method of Pandas DataFrame
Pandas of the beginner, by the beginner, for the beginner [Python]
Quadratic programming by the interior point method
Summary of SQLAlchemy connection method by DB
A memo explaining the axis specification of axis