A complete understanding of Python's object-oriented programming

Object-orientation

1. Object-oriented origin

2003 Turing Award Winner [Alan Kay](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A9%E3%83%B3%E3%83%BB% E3% 82% B1% E3% 82% A4) often says [Object Oriented Programming](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82% B8% E3% 82% A7% E3% 82% AF% E3% 83% 88% E6% 8C% 87% E5% 90% 91% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0) is called the father. The person himself has declared the invention right many times in Public Place. However, Kay despises modern object-oriented languages such as "C ++" and "Java." These languages are inherited from the language "Simula 67" and I created "[Smalltalk](https://ja. Wikipedia.org/wiki/Smalltalk) ”, Kay thinks it has nothing to do with it.

The name object-oriented certainly comes from Alan Kay. However, the modern object-orientation used in C ++ and Java is quite different from the original. Kay himself does not recognize these languages as successors. So what kind of language is Simula 67, the parent of C ++ and Java, according to Kay? Now let's take a look at a simple sample code.

Class Rectangle (Width, Height); Real Width, Height;
                           ! Class with two parameters;
 Begin
    Real Area, Perimeter;  ! Attributes;
 
    Procedure Update;      ! Methods (Can be Virtual);
    Begin
      Area := Width * Height;
      Perimeter := 2*(Width + Height)
    End of Update;
 
    Boolean Procedure IsSquare;
      IsSquare := Width=Height;
 
    Update;                ! Life of rectangle started at creation;
    OutText("Rectangle created: "); OutFix(Width,2,6);
    OutFix(Height,2,6); OutImage
 End of Rectangle;

It's a class with two variables. I don't know the grammar, but the comments will give you a general idea of what it looks like. Simula 67, as the name implies, is a programming language introduced in 1967 and released in 1973. Smalltalk, on the other hand, is the language released in the 1980s, with the first version (Smalltalk-72) released in 1975.

With class, it's not object-oriented programming, but Simula 67's class is "instance", "inheritance", "method" and "[late binding](https://ja.wikipedia. org/wiki/%E3%83%80%E3%82%A4%E3%83%8A%E3%83%9F%E3%83%83%E3%82%AF%E3%83%90%E3%82 % A4% E3% 83% B3% E3% 83% 87% E3% 82% A3% E3% 83% B3% E3% 82% B0) ". Simula 67 is definitely an object-oriented language.

However, the Simula 67's object-oriented design is also not original. 1965, [Antony Hoare](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%88%E3%83%8B%E3%83% BC% E3% 83% BB% E3% 83% 9B% E3% 83% BC% E3% 82% A2) (1980 Turing Award winner) a paper /resources/text/algol/ACM_Algol_bulletin/1061032/p39-hoare.pdf) has been announced. The concept of record class was submitted to the paper. Hoare wrote the sample in the language ALGOL.

record class person;
    begin   integer date of birth;
            Boolean male;
            reference   father, mother, youngest offspring, elder sbling (person)
    end;

It is a complex data type and is similar to the C language structure. Right.

And in 1966, at a summer school, Hoare [Kristen Nygaard](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%82%B9% E3% 83% 86% E3% 83% B3% E3% 83% BB% E3% 83% 8B% E3% 82% AC% E3% 83% BC% E3% 83% 89) and [Ole-Johan Dahl]( https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%AB%E3%83%A8%E3%83%8F%E3%83%B3%E3%83%BB%E3 I met% 83% 80% E3% 83% BC% E3% 83% AB). It was these two who later made Simula 67. Hoare shared the idea of record class with two people. According to Dar, Hoare had already come up with the concept of "inheritance" and taught them. And in 2001, Kristen Nygaard and Ole-Johan Dahl won the Turing Award for their contribution to object orientation. It was two years earlier than Alan Kay.

Introduced Simula 67. You also understand that Simula 67 is the world's first object-oriented language. So is Smalltalk made by Kay a fake? In conclusion, it's not. Lisp In contrast to the language "Everything is a list", Smalltalk created the concept of "Everything is an object" for the first time. .. In addition, Smalltalk interprets all expressions, including operators, as "messages" to objects. Smalltalk is the programming language that has helped object-oriented programming. In the 1980s, Smalltalk produced object-oriented programming languages. Among them is C ++, which is still alive and well. In addition, Functional Programming The Lisp camp, the ancestor of the language, also helped with the "Common Lisp Object System".

Finally, in 1996, Java (https://ja.wikipedia.org/wiki/Java), the pinnacle of the modern object-oriented programming paradigm, was announced. This is a major milestone in object-oriented history. Java itself hasn't invented anything in object orientation, but it has absorbed the excellent concepts so far, and JVM Excellent multi-platform performance of E6% 83% B3% E3% 83% 9E% E3% 82% B7% E3% 83% B3) and GC 82% AC% E3% 83% 99% E3% 83% BC% E3% 82% B8% E3% 82% B3% E3% 83% AC% E3% 82% AF% E3% 82% B7% E3% 83% With A7% E3% 83% B3), it is still one of the top 3 programming languages in the world.

2. Object-oriented features

Introduced the origin of object orientation. But what is object-oriented in the first place? Before getting into the main subject, I would like to explain using a simple example.

Object-oriented is often process-oriented ([Procedural Programming](https://ja.wikipedia.org/wiki/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B%] Also known as E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0)). The code below represents the process-oriented and object-oriented formats.

a = 0
# a+I want to realize 3 functions

#Process oriented
sum(a, 3)

#Object-orientation
a.sum(3)

You might think that it's just written differently. In fact, object-oriented programming can clarify the logic of your code. And the bigger the program, the more powerful it is. Next, let's take a closer look at the differences in the above code.

** 1. Syntax ** You can interpret the syntax of function calls in terms of word order.

** 2. Definition method **

** 3. Calling method ** In practice, when you want to do the same for multiple objects:

When there are many such processes

Next, let's take a closer look at the benefits of object orientation through Python code examples.

2-1. Unification and management of interfaces

We define three classs: bird, dog and fish.

class Bird:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The bird named {} is flying".format(self.name))


class Dog:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The dog named {} is running".format(self.name))


class Fish:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The fish named {} is swimming".format(self.name))

Create an instance.

bob = Bird("Bob")
john = Bird("John")
david = Dog("David")
fabian = Fish("Fabian")

Then call the move method of all instances.

bob.move()
john.move()
david.move()
fabian.move()

Execution result:

The bird named Bob is flying
The bird named John is flying
The dog named David is running
The fish named Fabian is swimming

When you create an instance, you need to pass parameters. This parameter is the data that distinguishes an object from other objects. For example, the name of the object bob is Bob and the name of john is John, so even though the instance was created from the same class, it will be a different object and the same. Even if you execute the method, the result will be different.

Also, the move method of a different class outputs a different result. For example, move of Bird outputs The bird named ... and Dog outputsThe dog named .... The move method means" move ", and each animal class can be moved, so by implementing it as the same move, the interface is unified and it is easier to remember.

When implemented in a process-oriented manner, it might look like this:

def move_bird(name):
    print("The bird named {} is flying".format(name))


def move_dog(name):
    print("The dog named {} is runing".format(name))


def move_fish(name):
    print("The fish named {} is swimming".format(name))


bob = "Bob"
john = "John"
david = "David"
fabian = "Fabian"

move_bird(bob)
move_bird(john)
move_dog(david)
move_fish(fabian)

When you get an object called bob, you have to first clarify whether it is a" bird "or a" dog ", or you can't decide whether to use move_bird or move_dog. In an actual program, it is common to implement not only move but dozens of types of processing functions. As the number of functions increases, it becomes extremely difficult to clarify the correspondence with variables. Also, these functions may be calling other functions internally, so when you reuse this function in another program, you need to find out all the functions used internally and migrate them.

Object-oriented uses variables to create an instance from class, and you can see which methods are available by looking at class. And by abstracting it as class, functions in the same context are consolidated and easier to manage.

2-2. Encapsulation

Object-oriented bundles functions and data together, which is very convenient when you want to process the same variable (data) with many functions.

class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def describe(self):
        print("name: {}; age: {}; height: {}".format(self.name, self.age, self.height))

    def introduce(self):
        print("My name is {}, and height is {}, and age is {}. ".format(self.name, self.height, self.age))


bob = Person("Bob", 24, 170)
mary = Person("Mary", 10, 160)
bob.describe()
bob.introduce()
mary.describe()
mary.introduce()

Execution result:

name: Bob; age: 24; height: 170
My name is Bob, and height is 170, and age is 24.
name: Mary; age: 10; height: 160
My name is Mary, and height is 160, and age is 10.

There are two ways to implement the above process in a process-oriented manner. One is to pass it as an argument as it is.

def description(name, age, height):
    print("Description: name is {}, age is {}, height is {}".format(name, age, height))


def introduction(name, age, height):
    print("My name is {}, and height is {}, and age is {}. ".format(name, height, age))


description("Bob", 24, 170)
description("Mary", 20, 160)
introduction("Bob", 24, 170)
introduction("Mary", 20, 160)

The above method requires you to pass the same arguments each time, which can be very annoying with many arguments. The other is that you don't have to pass arguments every time.

bob = dict(name='Bob', age=24, height=170)
mary = dict(name='Mary', age=20, height=160)


def introduction(**kwargs):
    print("My name is {name}, and height is {age}, and age is {height}. ".format(**kwargs))


def description(**kwargs):
    print("Description: name is {name}, age is {age}, height is {height}".format(**kwargs))


introduction(**bob)
description(**bob)
introduction(**mary)
description(**mary)

In this method, the argument is stored in the dictionary, and the dictionary is unpacked and passed as the argument. However, if the three keys name, age, heigh do not exist in the dictionary, an error will occur.

In this way, compared to process-oriented, object-oriented encapsulates processing and data together, so the logic of the code tends to be cleaner.

2-3. Dynamic operation of objects

The realization of a dynamic sequence of actions of an object is unsuitable for process orientation.

class Individual:
    def __init__(self, energy=10):
        self.energy = energy

    def eat_fruit(self):
        self.energy += 1
        return self

    def eat_meat(self):
        self.energy += 2
        return self

    def run(self):
        self.energy -= 3
        return self


anyone = Individual()
print("energy: {}".format(anyone.energy))
anyone.eat_meat()
print("energy after eat_meat: {}".format(anyone.energy))
anyone.eat_fruit()
print("energy after eat_fruit: {}".format(anyone.energy))
anyone.run()
print("energy after run: {}".format(anyone.energy))
anyone.eat_meat().run()
print("energy after eat_meat and run: {}".format(anyone.energy))

Execution result:

energy: 10
energy after eat_meat: 12
energy after eat_fruit: 13
energy after run: 10
energy after eat_meat and run: 9

The class of the above" individual "has an internal state parameter called" energy "and three methods of" eating fruit "," eating meat ", and" running ". Next, we define two more subdivided classs," boy "and" girl ".

class Boy(Individual):
    def daily_activity(self):
        self.eat_meat().eat_meat().run().eat_meat().eat_fruit().run().eat_meat()
        print("boy's daily energy: {}".format(self.energy))


class Girl(Individual):
    def daily_activity(self):
        self.eat_meat().eat_fruit()
        print("girl's daily energy: {}".format(self.energy))


bob = Boy()
bob.daily_activity()
mary = Girl()
mary.daily_activity()

Execution result:

boy's daily energy: 13
girl's daily energy: 13

If the above process is implemented in a process-oriented manner, it is necessary to define a dedicated variable called ʻenergy and a function to process each ʻenergy for each object, which is inevitably redundant.

Also, the structure of subject, verb, object`` is relatively easy to understand. In the above example, you can understand that the sequence of actions, first ʻeat_meat () and thenrun (), continues forever. If it is realized in a process-oriented manner, it will be a long sentence like boy_energy = eat_meat (boy_energy); boy_energy = run (boy_energy); ... It will be difficult to understand.

3. Object-oriented concepts

I briefly introduced the features of object-oriented programming. From here, we will go into a little more advanced content. There are various concepts in object orientation, and I will explain these.

3-1. Class

[Class](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%B9_(%E3%82%B3%E3%83%B3%E3% 83% 94% E3% 83% A5% E3% 83% BC% E3% 82% BF)) is a design drawing of an object with the same attributes (variables, data) and processes (methods, functions). Classes define common attributes and actions for objects that they generate. In process-oriented languages, variables are categorized by type, whereas in object-oriented languages, variables are categorized by class. And the type of object-oriented language itself is also a class.

By the way, Python 2 has an old class and a new class, each of which looks like this:

class oldStyleClass: # inherits from 'type'
    pass

class newStyleClass(object): # explicitly inherits from 'object'
    pass

With Python 3, all classes default to new classes, so you no longer have to explicitly inherit ʻobject`.

3-2. Instance

Instances (https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82 % B9) is simply Object % AF% E3% 83% 88_ (% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% Sometimes called B0)), the class's [constructor](https://en.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%82%B9%E3%83 % 88% E3% 83% A9% E3% 82% AF% E3% 82% BF) and Initializer to specify the attribute Refers to an instance to which a value has been assigned.

3-3. Instantiation

Instantiation (https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3% 82% B9% E5% 8C% 96) refers to the act of creating an instance from a class that is a blueprint.

3-4. Instance variables

[Instance Variables](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3% 82% B9% E5% A4% 89% E6% 95% B0) refers to the variables assigned to each instance.

3-5. Class variables

Class Variables A variable shared by a class and its instances.

3-6. Method

[Method](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7%) AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6))) refers to a function that belongs to a class or instance.

3-7. Static method

[Static Method](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%) E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6) #% E3% 82% A4% E3% 83% B3% E3% 82% B9% E3% 82% BF% E3% 83% B3% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E3% 81% A8% E3% 82% AF% E3% 83% A9% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89) refers to a method that can be called without instantiation.

3-8. Class method

[Class Method](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7) % AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6) #% E3% 82% A4% E3% 83% B3% E3% 82% B9% E3% 82% BF% E3 % 83% B3% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E3% 81% A8% E3% 82% AF% E3% 83 % A9% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89) refers to a method that operates on a class as an object.

3-9. Members

Member is the namespace of the class or instance. //ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93) is an element to be stored. Namespaces usually include member variables (class or instance variables) and member functions (various methods).

3-10. Override

[Override](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82 % A4% E3% 83% 89) is the Child Class % A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (subclass / derived class) is [parent Class](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BC%E3%82%AF%E3%83% Inherited from A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (super class / base class) Refers to the act of overwriting a method.

3-11. Encapsulation

Encapsulation is It refers to the act of grouping data and processing into an object to create a boundary.

3-12. Inheritance

[Inheritance](https://ja.wikipedia.org/wiki/%E7%B6%99%E6%89%BF_(%E3%83%97%E3%83%AD%E3%82%B0%E3%] 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0))) refers to designing a child class that inherits the structure of an existing class. Has a relationship of is-a or has-a It is an architecture that can be used.

3-13. Polymorphism

[Polymorphism](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82 % A3% E3% 82% BA% E3% 83% A0) (polymorphism) refers primarily to the variety of child classes achieved by overrides. The following are examples.

class Animal:
    def run(self):
        print('Animal is running...')


class Dog(Animal):
    def run(self):
        print('Dog is running...')


class Cat(Animal):
    def run(self):
        print('Cat is running...')


def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())
run_twice(Dog())
run_twice(Cat())

Execution result:

Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...

In other words, the process of inputting a certain class can operate normally without any modification to the child class ["Liskov Substitution Principle"](https://ja.wikipedia.org/wiki/ % E3% 83% AA% E3% 82% B9% E3% 82% B3% E3% 83% 95% E3% 81% AE% E7% BD% AE% E6% 8F% 9B% E5% 8E% 9F% E5 By% 89% 87).

3-14. Operator overload

Operator overloading (https://en.wikipedia.org/wiki/Operator_overloading) refers to the act of user-defining the functionality of an operator. In Python, all classes are child classes of the ʻobject` class, and each operator overload is realized by a special method, so it is a kind of polymorphism in nature. The special methods for operator overloading are:

class MyNum:
    def __init__(self,x):
        self.__x = x

    def __lt__(self, other):
        print("__lt__")
        return self.__x < other

    def __le__(self, other):
        print("__le__")
        return self.__x <= other

    def __eq__(self, other):
        print("__eq__")
        return self.__x == other

    def __ne__(self, other):
        print("__ne__")
        return self.__x != other

    def __gt__(self, other):
        print("__gt__")
        return self.__x > other

    def __ge__(self, other):
        print("__ge__")
        return self.__x >= other

x = MyNum(100)
x < 10
x <= 10
x == 10
x != 10
x > 10
x >= 10

Execution result:

__lt__
__le__
__eq__
__ne__
__gt__
__ge__

The above is the addition of print processing to the arithmetic processing. Python has a numerical library called Numpy. And the reason we can calculate the Hadamard product of a matrix in the form ʻa * b` is that Python supports operator overloading.

3-15. Abstraction

[Abstract](https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E5%8C%96_(%E8%A8%88%E7%AE%97%E6] % A9% 9F% E7% A7% 91% E5% AD% A6))) refers to encapsulation, which forms a concept by grouping only strongly related data and processes into objects. For example, you can abstract an animal by designing it as a class called ʻAnimal`, making the state of the animal a variable, and making the behavior of the animal a method.

3-16. Duck typing and monkey patches

These two concepts are derived from the Ruby community and represent the nature of dynamic languages.

[Monkey Patch](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%82%AD%E3%83%BC%E3%83%91%E3% 83% 83% E3% 83% 81) is a way to extend or modify your code at runtime. In Python's object-oriented programming, it is used as a term when dynamically changing a class.

[Duck Typing](https://ja.wikipedia.org/wiki/%E3%83%80%E3%83%83%E3%82%AF%E3%83%BB%E3%82%BF%E3 % 82% A4% E3% 83% 94% E3% 83% B3% E3% 82% B0) is the nature of a dynamically typed object-oriented programming language, for example, to execute a function such as run_twice (animal). I will. A statically typed language evaluates the type of its argument and is only allowed to run on the ʻAnimalclass or its derivatives. However, dynamically typed languages can run normally if they have a method calledrun ()` without type evaluation. "If it walks like a duck and sounds like a duck, it must be a duck."

3-17. SOLID SOLID is an acronym for memorizing the five principles of software design in the field of object-oriented programming. The five principles are the single responsibility principle and the open and closed principle. wiki /% E9% 96% 8B% E6% 94% BE /% E9% 96% 89% E9% 8E% 96% E5% 8E% 9F% E5% 89% 87), [Riskov replacement principle](https:: //ja.wikipedia.org/wiki/%E3%83%AA%E3%82%B9%E3%82%B3%E3%83%95%E3%81%AE%E7%BD%AE%E6%8F % 9B% E5% 8E% 9F% E5% 89% 87), [Principle of Interface Separation](https://ja.wikipedia.org/w/index.php?title=%E3%82%A4%E3% 83% B3% E3% 82% BF% E3% 83% BC% E3% 83% 95% E3% 82% A7% E3% 82% A4% E3% 82% B9% E5% 88% 86% E9% 9B% A2% E3% 81% AE% E5% 8E% 9F% E5% 89% 87 & action = edit & redlink = 1) and Principle of Dependency Reversal 9D% E5% AD% 98% E6% 80% A7% E9% 80% 86% E8% BB% A2% E3% 81% AE% E5% 8E% 9F% E5% 89% 87).

3-17-1. Single-responsibility principle

The single-responsibility principle is the principle that a class should have only one responsibility. "One responsibility" is a bit vague, so in practice it's not a single responsibility when there are two or more motives for changing a class. As an example, assuming there is a class Rectangle that represents a rectangle, it is used in two modules, the GUI drawing function and the geometric calculation of the rectangle. The Rectangle class here violates the single-responsibility principle.

3-17-2. Open-closed principle

The open-closed principle is that new requirements should be extended as much as possible, rather than modifying the code. In practice, abstractions are often used to implement this principle. Python's Decorator is open-closed principle With conforming features, you can implement new features without modifying existing methods, functions, or classes.

3-17-3. Liskov Substitution Principle

Liskov Substitution Principle is that where a parent class is used, it should be possible to replace it with a child class. In practice, this principle is achieved using inheritance and polymorphism. As an example, as a child class of the class Rectangle that represents a rectangle, there is aSquare class that causes an error if the height and width do not match. And if a function or method takes the Rectangle class as input and gives a difference between the height and width internally, it cannot be replaced by the Square class, which violates the Liskov Substitution Principle.

3-17-4. Principle of interface separation

The principle of interface isolation is that clients should not have dependencies on methods they do not use. It's hard to understand in words, but see the example below. Screen Shot 2020-10-17 at 23.31.01.png (Source: Agile Principles, Patterns, and Practices in C #)

This figure shows the relationship between several classes. The Door class has methods associated with doors such aslock (), ʻun_lock () and ʻis_open (). This time, we will create a Timed Door that will automatically close if the door is open for a certain period of time. Here, the time measurement function is given to a class called TimerClient, and Door directly inherits TimerClient and acquires that function. Then, TimedDoor, which inheritsDoor, can also acquire the time measurement function. However, Door is an ordinary door and does not require a time measurement function, so it violates the principle of interface separation.

The solution is to create an adapter method or variable that connects to the TimerClient inside the TimedDoor as shown below and inherit Mixin. There are two types of methods. Screen Shot 2020-10-17 at 23.46.32.png Screen Shot 2020-10-17 at 23.46.55.png (Source: Agile Principles, Patterns, and Practices in C #)

3-17-5. Dependency Inversion Principle

The Dependency Inversion Principle involves two rules.

This principle is for decoupling between modules. The following are examples. Screen Shot 2020-10-17 at 23.57.12.png (Source: Agile Principles, Patterns, and Practices in C #)

The upper module PolicyLayer here depends on the lower moduleMechanismLayer, and the lower module MechanismLayer depends on the detail module ʻUtilityLayer`. This is a pattern that violates the Dependency Inversion Principle.

As a solution, you can design as follows. Screen Shot 2020-10-18 at 0.00.17.png (Source: Agile Principles, Patterns, and Practices in C #)

The PolicyLayer now depends on the abstract interface PolicyServiceInterface instead of the submodule. For PolicyServiceInterface, MechanismLayer implements each interface.

With the intervention of PolicyServiceInterface, PolicyLayer and MechanismLayer are compatible without being dependent on each other. The same is true for MechanismServiceInterface. The abstract interface is unlikely to change, and its intervention decouples each module.

3-18. GRASP GRASP is a design policy for object-oriented systems called "General Responsibility Assignment Software Pattern". GRASP exemplifies nine patterns: information expert, generator, controller, loosely coupled, highly cohesive, polymorphic, pure man-made, indirect, and protection from variation.

3-18-1. Information Expert

The Information Expert Pattern states that if you have a class that has all the information you need to fulfill a responsibility, you should leave it to that class. For example, suppose the EC site system has two classes, a shopping cart ShopCar and a product SKU. Then, we will implement a function that "does not allow duplicate products in the shopping cart". Since the SKUID is in the SKU class as information to determine if the same product is the same, this feature is not the ShopCar class, but theSKU which has all the necessary information, according to the information expert pattern. Should be implemented in theclass. Screen Shot 2020-10-18 at 21.30.32.png

3-18-2. Generator

Assuming that there are class A and class B in the generator pattern, B should be made to create A when one or more of the following conditions are met. In this case, B is the creator of A.

For example, if your EC site system has a class called Order ʻOrder that manages product SKU, you should create SKU inside ʻOrder. Screen Shot 2020-10-18 at 21.32.06.png

3-18-3. Controller

The controller pattern says that system events should be controlled by an object called a controller. This controller should be a class, system, or subsystem that does not interact with the UI or interfaces.

For example, "C" in the architecture "MVC" (https://ja.wikipedia.org/wiki/Model_View_Controller) used for Ruby on Rails. Is an abbreviation for controller.

3-18-4. Loose coupling

Cohesiveness is a measure of the strength of the dependencies between each component of the system. The loose coupling pattern says that the system should be designed to weaken the dependencies between each component. In order to weaken the dependency, minimize ʻimport` etc., tighten the access authority of the class member, make the class immutable % A4% E3% 83% 9F% E3% 83% A5% E3% 83% BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) There is a method to make it an object.

Taking the EC site system as an example, for example, when adding a function to calculate the total price of a product, create a new class, etc., ʻimport`` SKU, and create a method to aggregate the amount. Rather, adding it to ʻOrder, which already has a dependency on SKU, will prevent you from creating unnecessary dependencies. Screen Shot 2020-10-18 at 21.33.12.png

3-18-5. High cohesiveness

Cohesiveness is a measure of the strength of the relationship between responsibilities (functions) of an object (module). The highly cohesive pattern says that the responsibility should be properly focused on the object.

Taking the EC site system as an example, create the DAO class ʻOrderDAO of the order data, and create the methodSaveOrder () for saving the data. To implement. If you want to realize the function to save in Excel and the function to save in DB, implement different classes and inherit ʻOrderDAO, rather than implementing them collectively in ʻOrderDAO, and then [Virtual Method](https:: It is better to override SaveOrder ()` of //en.wikipedia.org/wiki/Virtual_function) (which is implemented as an abstract method in Python and will be referred to as an abstract method hereafter), but it will be more cohesive. Screen Shot 2020-10-18 at 21.33.42.png

3-18-6. Polymorphism

Polymorphism is a concept introduced in [3-13. Polymorphism](# 3-13-Polymorphism), which is patterned here as a rule of system design. The polymorphism pattern implements the variable part of the class as an abstract method, etc., and Polymorphism % E3% 83% A2% E3% 83% BC% E3% 83% 95% E3% 82% A3% E3% 82% BA% E3% 83% A0), and its concrete realization is a child class Should be implemented in.

For example, create an abstract class called Shape and implement an abstract method for drawing calledDraw (). Inheriting Shape, creating rectangleRectangle and circleRound respectively, overriding Draw () internally, and realizing each drawing function is a design based on polymorphic pattern Become. This way, the next time you want to add a diamond Diamond, you can create it in the same way without changing the system structure. Screen Shot 2020-10-18 at 21.44.21.png

3-18-7. Pure man-made

When designing a system, high cohesiveness and loose coupling are inconsistent. High cohesiveness subdivides the classes and concentrates their responsibilities on each other, but if the classes do not cooperate with each other, they will not work properly and will inevitably become more cohesive.

Pure artifacts create artificial classes, or abstract classes, that balance cohesiveness and cohesiveness. For example, in the case of the drawing function of figures, this time we will add a function that supports both Windows and Linux. Since the system calls and structure of each OS are different, the drawing function Draw () must also be implemented in a different way. Here, by adding the abstract base class ʻAbstractShape`, the system can be realized without reducing the cohesiveness and the cohesiveness. Screen Shot 2020-10-18 at 22.07.19.png

3-18-8. Indirect

The indirect pattern is a design method that promotes the reduction of connectivity between classes by providing an intermediary object between the two classes. In the MVC architecture, it was indirect pattern to not let Model interact directly with View and put Contoroller in between. It's a design. The interface abstract class in the middle introduced in [3-17-4. Interface Separation Principle](# 3-17-4-Interface Separation Principle) is designed with the same idea.

3-18-9. Protection from fluctuations

The pattern of protection from fluctuations is similar to [3-17-2. Open-Closed Principle](# 3-17-2-Open-Closed Principle). Encapsulate unstable parts with a unified interface to protect against fluctuations. And when changes occur, we add rather than change the interface. The purpose is to be able to extend the functionality without changing the old code. Python's Decorator given as an example in [3-17-2. Principle of opening and closing] In addition to% 83% BC% E3% 83% B3), [ORM](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8% E3% 82% A7% E3% 82% AF% E3% 83% 88% E9% 96% A2% E4% BF% 82% E3% 83% 9E% E3% 83% 83% E3% 83% 94% E3% 83% B3% E3% 82% B0) is a typical variation protection pattern, changing the DB does not affect the client side

3-19. Design pattern

[Design pattern](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3% 82% BF% E3% 83% BC% E3% 83% B3_ (% E3% 82% BD% E3% 83% 95% E3% 83% 88% E3% 82% A6% E3% 82% A7% E3% 82 % A2)) is design know-how in object-oriented programming. Unlike the Design Principles like [SOLID](# 3-17-solid) and [GRASP](# 3-18-grasp) mentioned above, design patterns are the experience devised by past developers. It's like a rule.

4. Python's object-oriented basics

I explained the concept of object orientation. Now let's create four classes to see the basic structure of Python's object-oriented programming.

4-1. Class variables and methods

Python classes have variables and methods. And there are various types of each.

In the code below, the definitions of various variables and methods are explained in comments.

from types import MethodType


class Animal:
    #This is where you define your class variables
    the_name = "animal"  #Class variables

    def __init__(self, name, age):  #Initializer
        self.name = name  #Instance variables
        self.age = age

    #This is where the method is defined
    def sleep(self):  #Instance method
        print("{} is sleeping".format(self.name))

    def eat(self, food):  #Instance method with arguments
        print("{} is eating {}".format(self.name, food))

    @classmethod
    def speak(cls, adjective):  #Class method
        print("I am a {} {}".format(adjective, cls.the_name))

    @staticmethod
    def happening(person, do):  #Static method
        print("{} is {}ing".format(person, do))


def drink_water(self):
    print("{} is drinking water".format(self.name))

Verification:

adam = Animal(name="Adam", age=2)  #Instantiation
print('adam.the_name: {}'.format(adam.the_name))  #Call a class variable from an instance
#Execution result: adam.the_name: animal
print('Animal.the_name: {}'.format(Animal.the_name))  #Call a class variable from a class
#Execution result: adam.name: Adam
print('adam.name: {}'.format(adam.name))  #Call an instance variable
#Execution result: Animal.the_name: animal
adam.sleep()  #Call an instance method
#Execution result: Adam is sleeping
adam.eat("meat")  #Call an instance method with arguments
#Execution result: Adam is eating meat
adam.speak("happy")  #Call a class method from an instance
#Execution result: I am a happy animal
Animal.speak("sad")  #Call a class method from a class
#Execution result: I am a sad animal
adam.happening("Tim", "play")  #Call a static method from your instance
#Execution result: Tim is playing
Animal.happening("Mary", "watch")  #Call a static method from a class
#Execution result: Mary is watching
Animal.the_name = "Animal"  #Modify class variables
print('adam.the_name: {}'.format(adam.the_name))
#Execution result: adam.the_name: Animal
adam.the_name = "animal"  #Fix from instance
print('Animal.the_name: {}'.format(Animal.the_name))
#Execution result: Animal.the_name: Animal
adam.age = 3  #Modify instance variables

#Method banding (monkey patch)
adam.drink_water = MethodType(drink_water, adam)  #Banding to an instance
adam.drink_water()
#Execution result: Adam is drinking water
print(adam.drink_water)
#Execution result:<bound method drink_water of <__main__.Animal object at 0x7ffd68064310>>
try:
    Animal.drink_water
except AttributeError as e:
    print(e)
#Execution result: type object'Animal' has no attribute 'drink_water'
Animal.drink_water = MethodType(drink_water, Animal)  #Banding in class
adam.drink_water()
#Execution result: Adam is drinking water
Animal.drink_water = drink_water  #Banding methods with direct assignment
adam.drink_water()
#Execution result: Adam is drinking water

4-2. Properties

Create a Dog class that inherits from ʻAnimal and take a look at property` and its associated decorators. These decorators convert methods into properties (variables) and have the following two advantages:

In addition to the decorator, there is also a way to achieve the above processing with the property function.

from functools import cached_property


class Dog(Animal):  #Class inheritance
    def eating(self):
        print("{} is eating".format(self.name))

    @property
    def running(self):
        if self.age >= 3 and self.age < 130:
            print("{} is running".format(self.name))
        elif self.age > 0 and self.age < 3:
            print("{} can't run".format(self.name))
        else:
            print("please input true age")

    @property  #Get a private variable
    def country(self):
        return self._country

    @country.setter  #Method name.setter
    def country(self, value):  #Assign a value to a private variable
        self._country = value

    @country.deleter  #Method name.deleter
    def country(self):  #Delete the value to a private variable
        del self._country
        print("The attr country is deleted")

    #Achieve the same function as the above decorator with the property function
    def get_city(self):
        return self._city

    def set_city(self, value):
        self._city = value

    def del_city(self, value):
        del self._city

    city = property(get_city, set_city, del_city, "I'm the 'city' property.")

    @cached_property  #Cached property
    def official_name(self):
        return 'Mr.{} - the Dog'.format(self.name)

Verification:

david = Dog("David", 2)
david.eating()
#Execution result: David is eating
david.running  # ()Call without
#Execution result: David can't run
dean = Dog("Dean", 4)
dean.running
#Execution result: Dean is running

#Decorator method
david.country = "America"
print(david.country)
#Execution result: America
del david.country
#Execution result: The attr country is deleted

#Method by property function
david.city = "NewYork"
print(david.city)
#Execution result: New York

#Cached property
print(david.official_name)
#Execution result: Mr.David - the Dog

4-3. Inheritance / override of private variables and methods

Let's define the Cat class and its child class BlackCat and look at inheritance and overriding of private variables and methods.

class Cat(Animal):
    def __init__(self, weight):  #Of the parent class__init__Override
        self.__weight = weight
        self._weight = weight + 1
        self.weight = self._weight + 1

    def get_weight(self):
        print("My _weight is {}kg".format(self._weight))

    def get_real_weight(self):
        print("Actually my __weight is {}kg".format(self.__weight))


class BlackCat(Cat):
    def get_weight(self):  #Override parent class methods
        print("My weight is {}kg".format(self.weight))

    def get_real_weight(self):
        print("Actually my _weight is {}kg".format(self._weight))

    def get_actual_weight(self):
        print("My __weight is exactly {}kg".format(self.__weight))

Verification:

cole = Cat(5)
print("Cole's weight: {}kg".format(cole.weight))
#Execution result: Cole's weight: 7kg

# _x is a private variable that is not recommended for external use, and its use itself is not restricted.
print("Cole's _weight: {}kg".format(cole._weight))
#Execution result: Cole's _weight: 6kg

# __x is a private variable that prohibits external use, and its use is restricted._<class>__Can be forcibly called in the form of x
print("Cole's __weight: {}kg".format(cole._Cat__weight))
#Execution result: Cole's __weight: 5kg
cole.get_real_weight()  #From inside with method__x is available
#Execution result: Actually my__weight is 5kg

cain = BlackCat(5)
cain.get_weight()
#Execution result: My weight is 7kg

# _Since x is not restricted, it can be called even from a child class
cain.get_real_weight()
#Execution result: Actually my_weight is 6kg

#Of the private variable of the parent class__x cannot be used in a straightforward way from inside a child class
try:
    cain.get_actual_weight()
except AttributeError as e:
    print(e)
#Execution result:'Blackcat' object has no attribute '_Blackcat__weight'

4-4. Inheritance of class members

Let's define Tiger and WhiteTiger and see how to use super. super is a function for calling variables and methods of the parent class in the child class.

class Tiger(Animal):   
    def speak(self):
        return "I'm a tiger not Lulu's song"

    def eat(self):
        return "{} is eating".format(self.name)


class WhiteTiger(Tiger):
    def __init__(self, name, age, height):
        super().__init__(name, age)
        self.height = height

    def speak(self):
        return super().speak().replace("tiger", 'white tiger')

    def eat(self):
        return super().eat()

Verification:

tony = WhiteTiger("Tony", 10, 100)
print(tony.eat())
#Execution result: Tony is eating
print(tony.speak())
#Execution result: I'm a white tiger not Lulu's song

** 1. Redefine the variables of the parent class. ** **

def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

** 2. Explicitly call the parent class __init__. If you rename the parent class, you'll have to fix everything that's called. ** **

def __init__(self, name, age, height):
    Tiger.__init__(self, name, age)
    self.height = height

5. Python's object-oriented evolution

We've seen the basic forms of Python's object-oriented programming. In practice, the content of [4. Python Object-Oriented Basics](# 4-python Object-Oriented Basics) is almost sufficient. However, if you want to realize advanced functions, create your own modules, or create a beautiful system that follows the design pattern, you need to know a little more advanced content.

5-1. Special method

As I mentioned a bit in [3-14. Operator Overload](# 3-14-Operator Overload), Python classes have two underscores before and after, such as __init__. There are many methods and variables called "special methods", "magic methods" or " __dunder__ (dbader: double underscore) ". .. These methods and variables are common to some or all objects and can provide various functions.

import collections
import copy
import math
import operator
import pickle
import sys
import asyncio


class Dunder:
    def __abs__(self):
        # abs(Dunder());Called when calculating the absolute value
        return self.x

    def __add__(self, other):
        # Dunder() + 123;Called when adding
        return self.x + other

    async def __aenter__(self):
        # `__aenter__`When`__aexit__`Must be implemented together
        # async with Dunder() as coro;Return value is limited to awaitable object
        await asyncio.sleep(1)

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        # `__aenter__`When`__aexit__`Must be implemented together
        # async with Dunder() as coro;Return value is limited to awaitable object
        await asyncio.sleep(1)

    def __aiter__(self):
        # `__aiter__`Is`__anext__`Must be implemented with
        # async for _ in Dunder();Return value is limited to asynchronous iterable object
        return self

    def __and__(self, other):
        # Dunder() & 123;Called when performing a logical product operation
        return self.x & other

    async def __anext__(self):
        # `__aiter__`Is`__anext__`Must be implemented with
        # async for _ in Dunder();Should cause StopAsyncIteration when the element is gone
        #Return value is limited to awaitable object
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

    def __await__(self):
        # await Dunder();Return value is limited to iterator
        return self.z  # `__next__`When`__iter__`Class that implements

    def __call__(self, *args, **kwargs):
        # Dunder()(); callable(Dunder()) == True;Can be called like a function
        return self.x

    def __init__(self, **kwargs):
        # Dunder(y=2);Initializer
        self.x = 1
        self.y = kwargs.get('y')
        self.z = [1, 2, 3]

    def __bool__(self):
        # bool(Dunder()) == True;Called when doing Boolean operations
        return True

    def __bytes__(self):
        # bytes(Dunder());Byte sequence
        return bytes('123', encoding='UTF-8')

    def __ceil__(self):
        # math.ceil(Dunder());Called when rounding up
        return math.ceil(self.x)

    def __class_getitem__(cls, item):
        # Dunder[int] == "Dunder[int]";This method automatically becomes a class method
        return f"{cls.__name__}[{item.__name__}]"

    def __complex__(self):
        # complex(Dunder());Complex number
        return complex(self.x)

    def __contains__(self, item):
        # item not in Dunder(); item in Dunder()
        return True if item in self.z else False

    def __copy__(self):
        # copy.copy(Dunder());Called when making a shallow copy
        return copy.copy(self.z)

    def __deepcopy__(self, memodict={}):
        # copy.deepcopy(Dunder());Called when making a deep copy
        return copy.deepcopy(self.z)

    def __del__(self):
        # dunder = Dunder(); del dunder;
        #Called when deleting an object. Also supports garbage collection
        del self

    def __delattr__(self, item):
        # del self.params;Called when deleting an instance variable
        del self.item

    def __delete__(self, instance):
        # class Owner: dunder = Dunder()
        # del Owner().medusa;Descriptor method
        #Called when deleting as an attribute of the owner class
        del self.x

    def __delitem__(self, key):
        # del Dunder()['some_key']
        self.__dict__.pop(key)

    def __dir__(self):
        # dir(Dunder());Returns an iterable object that contains all the attributes of the object
        return super().__dir__()

    def __divmod__(self, other):
        # divmod(Dunder(), 123);Get division quotient and remainder at the same time
        return divmod(self.x, other)

    def __enter__(self):
        # with Dunder() as dunder: pass
        return self

    def __eq__(self, other):
        # Dunder() == 123;Called when performing an equivalence operation
        return self.x == other

    def __exit__(self, exc_type, exc_val, exc_tb):
        # with Dunder() as dunder: pass;The arguments are TypeError, ValueError and Traceback, respectively.
        return True

    def __float__(self):
        # float(Dunder());Make it a floating point
        return float(self.x)

    def __floor__(self):
        # math.floor(Dunder());Truncate the decimal point
        return math.floor(self.x)

    def __floordiv__(self, other):
        # Dunder() // 123;Called when truncating and dividing
        return self.x // other

    def __format__(self, format_spec):
        # '{:x}'format(Dunder()); format(Dunder(), 'x')
        if format_spec == 'x':
            return '{}'.format(self.x)
        return '{}'.format(self.y)

    def __fspath__(self):
        # os.fspath(Dunder()) == '/var/www/html/mysite';Returns the file system path
        return '/var/www/html/mysite'

    def __ge__(self, other):
        # Dunder() >= 123
        return self.x >= other

    def __get__(self, instance, owner):
        # class Test: dunder = Dunder();Descriptor method
        # `Test().dunder`Or`Test.dunder`Called when you do
        return self.x

    def __getattr__(self, item):
        # Dunder().a;Called when accessing undefined members
        return f'object has no attribute "{item}"'

    def __getattribute__(self, item):
        # Dunder().a;Called when accessing all members, undefined or defined
        # `return self.x`Please note that this will result in an infinite loop.
        return super().__getattribute__(item)

    def __getitem__(self, item):
        # Dunder()[item]
        return self.__dict__.get(item)

    def __getnewargs__(self):
        # pickle.loads(pickle.dumps(Dunder()));When unPickle`__new__`Can define the arguments passed to the method
        # Python 3.Used when using pickle protocol 2 or 3 before 6
        # Python 3.When using pickle protocol 2 or 3 after 6`__getnewargs_ex__`Is used
        #Not called directly`__reduce__`Make up the method
        return (2 * self.x, )

    def __getstate__(self):
        # pickle.dumps(Dunder());You can get the state of an object during Pickle processing
        #Not called directly`__reduce__`Make up the method
        return self.__dict__.copy()

    def __gt__(self, other):
        # Dunder() > 123
        return self.x > 123

    def __hash__(self):
        # hash(Dunder());Called when calculating the hash value
        return hash(self.x)

    def __iadd__(self, other):
        # dunder = Dunder(); dunder += 123; in-Called when adding place
        self.x += other
        return self

    def __iand__(self, other):
        # dunder = Dunder(); dunder &= 123; in-Called when performing a logical product operation of place
        self.x &= other
        return self

    def __ifloordiv__(self, other):
        # dunder = Dunder(); dunder //= 123; in-Called when performing truncation division of place
        self.x //= other
        return self

    def __ilshift__(self, other):
        # dunder = Dunder(); dunder <<= 123; in-Called when calculating the bit left shift of place
        self.x <<= other
        return self

    def __imatmul__(self, other):
        # dunder = Dunder(); dunder @= 123; in-Called when doing binary operations on place
        #In numpy, it is implemented as a dot product
        self.x @= other  #Function not implemented in standard library
        return self

    def __imod__(self, other):
        # dunder = Dunder(); dunder %= 123; in-Called when performing modulo operations on place
        self.x %= other
        return self

    def __imul__(self, other):
        # dunder = Dunder(); dunder *= 123; in-Called when multiplying place
        self.x *= 123
        return self

    def __index__(self):
        # slice(Dunder(), Dunder() * 2); bin(Dunder()); hex(Dunder()); oct(Dunder())
        # operator.index(Dunder());The return value is limited to integers`operator.index`Called from a function
        #Also requires an integer`slice`、`bin()`、`hex()`、`oct()`Calls this method
        return self.x

    def __init_subclass__(cls, **kwargs):
        # class Test(Dunder, **kwargs): ...;Called when inherited
        super().__init_subclass__()
        cls.x = kwargs.get('x', 1)

    def __instancecheck__(self, instance):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     def __instancecheck__(self, other):
        #         return True
        #
        # class Test(metaclass=MetaClass): ...
        # isinstance(int, Test) == True
        #This method is not called unless it is defined by class type (metaclass)
        #Also,`type(other) == self`Will be true directly and will not be called
        pass

    def __int__(self):
        # int(Dunder());Called when converting to an integer
        return int(self.x)

    def __invert__(self):
        # ~Dunder();Called when calculating bit inversion
        return ~self.x

    def __ior__(self, other):
        # dunder = Dunder(); dunder |= 123; in-Called when performing OR operation of place
        self.x |= other
        return self

    def __ipow__(self, other):
        # dunder = Dunder(); dunder ** 2; in-Called when calculating the power of place
        self.x ** other
        return self

    def __irshift__(self, other):
        # dunder = Dunder(); dunder >>= 2; in-Called when calculating the bit right shift of place
        self.x >>= other
        return self

    def __isub__(self, other):
        # dunder = Dunder(); dunder -= 2; in-Called when subtracting place
        return self

    def __iter__(self):
        # dunder = iter(Dunder()); next(dunder);Method for creating iterable object
        # `__next__`Must be implemented with
        self._i = 0
        return self.z[self._i]  # self.z is defined as a list

    def __itruediv__(self, other):
        # dunder = Dunder(); dunder /= 123; in-Called when dividing place
        self.x /= other
        return self

    def __ixor__(self, other):
        # dunder = Dunder(); dunder ^= 123; in-Called when performing an exclusive OR operation on place
        self.x ^= other
        return self

    def __le__(self, other):
        # dunder = Dunder(); dunder <= 123
        return self.x <= other

    def __len__(self):
        # len(Dunder())
        return len(self.z)

    def __lshift__(self, other):
        # Dunder() << 123;Called when calculating a bit left shift
        return self.x << other

    def __lt__(self, other):
        # Dunder() < 123
        return self.x < other

    def __matmul__(self, other):
        # Dunder() @ 123;Called when doing binary operations
        return self.x @ other  #Function not implemented in standard library

    def __missing__(self, key):
        # class Dict(dict):
        #     def __missing__(self, key):
        #         return f'__missing__({key})'
        # dunder = Dict({'key': 1})
        # print(dunder['unk_key'])
        #Method called when the key does not exist in the dictionary
        pass

    def __mod__(self, other):
        # Dunder() % 123;Called when performing modulo operation
        return self.x % other

    def __mro_entries__(self, bases):
        #Called when a non-class object is specified in the parent list of the class definition
        #Method to correct inheritance relationship in implementation of type annotation
        # https://www.python.org/dev/peps/pep-0560/#mro-entries
        pass

    def __mul__(self, other):
        # Dunder() * 123;Called when multiplying
        return self.x * ohter

    def __ne__(self, other):
        # Dunder() != 123;Called when performing unequal operations
        return self.x != other

    def __neg__(self):
        # -Dunder();Called when calculating the reciprocal
        return -self.x

    def __new__(cls, *args, **kwargs):
        # Dunder();constructor
        # __init__Create a self (instance itself) used in or other instance methods
        return super().__new__(cls)

    def __next__(self):
        # dunder = iter(Dunder()); next(dunder);Method for creating iterable object
        # `__iter__`Must be implemented with
        self._i += 1
        return self.z[self._i]

    def __or__(self, other):
        # Dunder() | 132;Called when performing a logical sum operation
        return self.x | other

    def __pos__(self):
        # +Dunder();Called when converting to a positive number
        return +self.x

    def __post_init__(self):
        #A method for the data class`__init__`Only if is defined`__init__`Called after
        pass

    def __pow__(self, power, modulo=None):
        # Dunder() ** 123;Called when calculating exponentiation
        if modulo:
            return self.x ** power % modulo
        else:
            return self.x ** power

    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     @classmethod
        #     def __prepare__(cls, name, bases, **kwargs):
        #         return dict()
        #
        # class Test(metaclass=MetaClass): ...
        # namespace = MetaClass.__prepare__(name, bases, **kwargs)
        #Called before evaluating the class body, it returns a dictionary object (namespace) that stores class members
        #Normal`types.prepare_class`Use with
        #This method is not called unless it is defined as a class method in the metaclass
        return collections.OrderedDict()

    def __radd__(self, other):
        # 123 + Dunder();Called when the operand performs a reflected addition
        return other + self.x

    def __rand__(self, other):
        # 123 & Dunder();Called when the operand performs a reflected AND operation
        return other & self.x

    def __rdiv__(self, other):
        # 123 / Dunder();Called when the operand performs a reflected division
        return other / self.x

    def __rdivmod__(self, other):
        # divmod(123, Dunder());Get the quotient and remainder of the division reflected by the operand at the same time
        return divmod(other, self.x)

    def __reduce__(self):
        # pickle.dumps(Dunder())
        # `__getstate__`、`__setstate__`、`__getnewargs__`Can be used to control the behavior of Pickle
        #As much as possible`__reduce__`To define the above method without directly defining
        #Backward compatible`__reduce_ex__`Is used preferentially when is defined
        return super().__reduce__() # return super().__reduce_ex__(protocol)

    def __repr__(self):
        # repr(Dunder());Returns a string containing a printable representation of the object
        return super().__repr__()

    def __reversed__(self):
        # reversed(Dunder());Returns an inverted iterator object
        new_instance = copy.deepcopy(self)
        new_instance.z = new_instance.z[::-1]
        return new_instance

    def __rfloordiv__(self, other):
        # 123 // Dunder();Called when the operand performs a reflected truncation division
        return other // self.x

    def __rlshift__(self, other):
        # 123 << Dunder();Called when the operand calculates the reflected bit left shift
        return '__rlshift__'

    def __rmatmul__(self, other):
        # 123 @ Dunder();Called when the operand performs a reflected binary operation
        return other @ self.x  #Function not implemented in standard library

    def __rmod__(self, other):
        # 123 % Dunder();Called when the operand performs a reflected modulo operation
        return other % self.x

    def __rmul__(self, other):
        # 123 * Dunder();Called when the operand performs a reflected multiplication
        return other * self.x

    def __ror__(self, other):
        # 123 | Dunder();Called when the operand performs a reflected disjunction
        return other | self.x

    def __round__(self, n=None):
       # round(Dunder());Rounding
        return round(self.x)

    def __rpow__(self, other):
        # 123 ** Dunder();Called when the operand calculates the reflected exponentiation
        return other ** self.x

    def __rrshift__(self, other):
        # 123 >> Dunder();Called when the operand calculates the reflected bit right shift
        return other >> self.x

    def __rshift__(self, other):
        # Dunder() >> 123;Called when calculating a bit right shift
        return self.x >> other

    def __rsub__(self, other):
        # 123 - Dunder();Called when the operand performs a reflected subtraction
        return other - self.x

    def __rtruediv__(self, other):
        # 123 / Dunder();Called when the operand performs a reflected division
        return other / self.x

    def __rxor__(self, other):
        # 123 ^ Dunder();Called when the operand calculates the exclusive OR reflected
        return other ^ self.x

    def __set__(self, instance, value):
        # class Test: dunder = Dunder();Descriptor method
        # `Test().dunder=123`Or`Test.dunder=123`Called when you do
        instance.x = value

    def __set_name__(self, owner, name):
        #Descriptor variable name assignment
        # class Test: pass;Called automatically when the owner class is created,
        # dunder = Dunder();Must be called explicitly when banding later
        # Test.dunder = dunder
        # dunder.__set_name__(Test, 'dunder')
        #A descriptor called dunder is used in the namespace of the Test class.'dunder'Assign to
        owner.__dict__[name] = self

    def __setattr__(self, key, value):
        # dunder = Dunder(); dunder.x = 123;Called when setting attributes
        self.__dict__[key] = value

    def __setitem__(self, key, value):
        # dunder = Dunder(); dunder['x'] = 123; ;Called when setting attributes with subscripts
        self.__dict__[key] = value

    def __setstate__(self, state):
        # pickle.loads(pickle.dumps(Dunder()))
        #When unPickle`__getstate__`You can use the state of the object obtained in
        #Not called directly`__reduce__`Make up the method
        self.__dict__.update(state)

    def __sizeof__(self):
        # sys.getsizeof(Dunder());Returns the size of the object
        return super().__sizeof__()

    def __str__(self):
        # str(Dunder())
        # print(Dunder())
        #Define a string representation of an object
        return f'{self.x}'

    def __sub__(self, other):
        # Dunder() - 123;Called when doing subtraction
        return self.x - other

    def __subclasscheck__(self, subclass):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     def __subclasscheck__(self, subclass):
        #         return True
        #
        # class Test(metaclass=MetaClass): ...
        # issubclass(int, Test) == True
        #This method is not called unless it is defined by class type (metaclass)
        return NotImplemented

    @classmethod
    def __subclasshook__(cls, subclass):
        # class Test: x = 1; #Define class variables
        # issubclass(Test, Dunder) == True
        #This method must be defined as a class method of the virtual base class
        if cls is Dunder:
            return hasattr(subclass, 'x')

    def __truediv__(self, other):
        # Dunder() // 123;Called when performing truncation division
        return self.x // other

    def __trunc__(self):
        # math.trunc(Dunder());Called when rounding
        return int(self.x)

    def __xor__(self, other):
        # Dunder() ^ 123;Called when performing an exclusive OR operation
        return self.x ^ other

The above are common special methods. You don't have to remember everything, and I think it's just right to have something like this. There are also some more special attributes and methods.

attribute meaning
__dict__ Of the object(Writable)A dictionary or other mapping object used to store attributes. Built-in function vars()You can refer to the dictionary with.
__class__ The class to which the class instance belongs.
__bases__ A tuple of the base class (parent class) of a class object.
__name__ The name of the class, function, method, descriptor, or generator instance.
__qualname__ Qualified names for classes, functions, methods, descriptors, and generator instances.
__mro__ This attribute is a tuple of classes that is considered when exploring the base class (parent class) when resolving a method.
    mro() This method may be overridden by the metaclass to customize the order of method resolution for that instance. This method is called when the class is instantiated and the result is__mro__It is stored in.
__subclasses__ Each class holds a weak reference to its own direct subclass. This method returns a list of those references that are alive.
__doc__ Function documentation string, None if there is no documentation. Not inherited by subclasses.
__module__ The name of the module in which the function is defined. If there is no module name, it will be None.
__defaults__ A tuple containing the default value for the argument with the default value, None if there is no argument with the default value
__code__ A code object that represents the body of a compiled function.
__globals__ A dictionary containing global variables of functions(Reference to)is---This dictionary determines the global namespace of the module in which the function is defined.
__closure__ None or individual free variables of the function(Variables other than arguments)It is a tuple consisting of a group of cells that bind values to. The cell object is the attribute cell_Has contents. In addition to setting the cell value, this can also be used to get the cell value.
__annotations__ A dictionary containing type annotation information. The key of the dictionary is the parameter name, if there is a return annotation'return'Is the key.
__kwdefaults__ A dictionary containing default values for keyword-only parameters.
__slots__ You can assign this class variable a string, iterable, or sequence of strings that represents the variable name used by the instance.__slots__Reserves the required storage space for the variables declared for each instance__dict__When__weakref__Is not automatically generated.
__weakref__ Attribute primarily for garbage collection and stores weak references.
__func__ An attribute of a class method that returns a function object that is the entity of the method.
__self__ An attribute of a class method that returns the object to which it belongs.
__isabstractmethod__ In the abstract base class, it is an attribute to judge whether it is an abstract method.
__members__ An attribute dedicated to enum classes, a dictionary used to store each element.

(See: Special Attributes, [Standard Hierarchy](https://docs.python.org/ ja / 3 / reference / datamodel.html # the-standard-type-hierarchy), [\ _ \ _ slotss \ _ \ _](https://docs.python.org/ja/3/reference/datamodel.html? highlight = slots # slots)))

Most of the attributes shown in the table are owned by Function Objects. Since everything in Python is an object, functions are also objects. Other than those shown in the table above, some are used in certain modules.

If you want to refer to class members, you can use ʻinspect.getmembers ()in addition todir (). You can also narrow down just the methods with ʻinspect.getmembers (obj, inspect.ismethod). There are other functions in the inspect module that start with ʻis`, and you can use them to get specific members. See Documentation for more information.

5-2 Types and objects

Python's type and ʻobjecthave a relationship like" chicken or the egg ". In other words, it is not possible to clearly explain which one comes first. Andtype and ʻobject are symbiotic and always appear at the same time.

First, Python is an "everything object" programming language. And, as introduced in [3. Concepts related to object orientation](# 3-Concepts related to object orientation), there are mainly the following two types of relationships in the object-oriented framework.

The relationship between these two types is shown in the figure below.                                                   image.png

Next, let's look at type and ʻobject`.

print(object)
#Execution result:<class 'object'>
print(type)
#Execution result:<class 'type'>

In the Python world, ʻobjectis the vertex of the inheritance relationship and is the parent class of all data classes. On the other hand,type` is the vertex of the class instance relationship and is the type class of all objects. The relationship between the two can be expressed as "object is an instance of type".

print(object.__class__)
#Execution result:<class 'type'>
print(object.__bases__)  #Since it is the pinnacle of inheritance, there is no more
#Execution result:()
print(type.__class__)  #type itself is also an instance of type
#Execution result:<class 'type'>
print(type.__bases__)
#Execution result:(<class 'object'>,)

Now let's look at built-in data classes such as list, dict, and tuple.

print(list.__bases__)
#Execution result:(<class 'object'>,)
print(list.__class__)
#Execution result:<class 'type'>
print(dict.__bases__)
#Execution result:(<class 'object'>,)
print(dict.__class__)
#Execution result:<class 'type'>
print(tuple.__bases__)
#Execution result:(<class 'object'>,)
print(tuple.__class__)
#Execution result:<class 'type'>

Similarly, the parent class is ʻobject, which is an instance of type. Let's instantiate and verify list`.

mylist = [1, 2, 3]
print(mylist.__class__)
#Execution result:<class 'list'>
print(mylist.__bases__)
#Execution result:
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# <ipython-input-21-0b850541e51b> in <module>
# ----> 1 print(mylist.__bases__)
#
# AttributeError: 'list' object has no attribute '__bases__'

It seems that the instantiated list has no parent class. Next, let's define our own class and take a look at its instances.

class C:  #In Python3 classes inherit object by default
    pass
print(C.__bases__)
#Execution result:(<class 'object'>,)
c = C()
print(c.__class__)
#Execution result:<class '__main__.C'>
print(c.__bases__)
#Execution result:
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# <ipython-input-30-bf9b854689d5> in <module>
# ----> 1 print(c.__bases__)
#
# AttributeError: 'C' object has no attribute '__bases__'

The parent class does not exist in the instance of the C class here either.

The figure below shows the various relationships up to this point. Here, the solid line represents the inheritance relationship and the arrow points to the parent class. The dotted line represents the class-instance relationship and the arrow points to the instance type class. Screen Shot 2020-11-02 at 22.24.34.png

From the above verification, we arrived at the following results.

So what would a class inherit from type be?

class M(type):
    pass
print(M.__class__)
#Execution result:<class 'type'>
print(M.__bases__)
#Execution result:(<class 'type'>,)

Both the type class and the parent class of the M class here are type. In the rule in the above figure, it should be placed in the first column. But where should the instance of M be placed?

class TM(metaclass=M):
    pass
print(TM.__class__)
#Execution result:<class '__main__.M'>
print(TM.__bases__)
#Execution result:(<class 'object'>,)

Actually, this M is [Metaclass](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF%E3%83%A9%E3%82" It is a class of the class called% B9). A TM created from the metaclass M will belong to the "class" in the second column of the figure above. I'll talk more about how to use metaclasses later.

You might wonder why Python needs both type and ʻobject. For example, without type, the figure above would have two columns, the first column would be the" type class "and the second column would be the" instance ". A static object-oriented programming language is roughly a two-column structure. Python has a three-column structure because it dynamically creates classes at runtime. The second column, ʻobject, is just an instance of type, so you can change methods and attributes at runtime. A three-row structure is required to achieve this property.

5-3. Metaclass

5-3-1. Class is an object

Python classes are borrowed from smalltalk. In most object-oriented programming languages, a class is code that describes "how to create an object."

class ObjectCreater:
    pass
my_object = ObjectCreater()
print(my_object)
#Execution result:<__main__.ObjectCreater object at 0x7fbc76f9a970>

But again, Python classes are both classes and objects. When executing a class reserved word, Python creates an object in memory. In the above code, an object called ʻObjectCreater was created. This "class" object can create an "instance" object. This is the role of the "class". And since it is an object, the following operations can be performed on ʻObjectCreater.

class ObjectCreator:
    pass


def echo(obj):
    print(obj)


echo(ObjectCreator)  #Pass as an argument

ObjectCreator.new_attr = 'foo'  #Increase attributes
assert hasattr(ObjectCreator, 'new_attr') == True

ObjectCreatorMirror = ObjectCreator  #Assign to another variable

5-3-2. Dynamic creation of classes

Classes are also objects, so you should be able to create them at runtime just like any other object. First, let's create a function that creates a class using the class reserved words.

def choose_class(name):
    if name == 'foo':
        class Foo:
            pass
        return Foo
    else:
        class Bar:
            pass
        return Bar


MyClass = choose_class('foo')
print(MyClass)
print(MyClass())

Execution result:

<class '__main__.choose_class.<locals>.Foo'>
<__main__.choose_class.<locals>.Foo object at 0x7fad2abc8340>

I was able to create a class with conditional branching. However, this method is not so "dynamic". If the class is also an object, there must be something to create the class. In fact, that "something" is the type introduced in [5.2 Types and Objects](# 5-2-Types and Objects).

As most people have used it, Python has a function called type.

print(type(1))
#Execution result:<class 'int'>
print(type('1'))
#Execution result:<class 'str'>
print(type(ObjectCreatorMirror))
#Execution result:<class 'type'>

However, type has another feature. It's the ability to create classes at runtime. The reason why one function has two functions is that there is an old class that inherits type in Python 2 as introduced in [3-1. Class](# 3-1-class). I will. For later compatibility, type has two functions.

MyShinyClass = type("MyShinyClass", (), {})
print(MyShinyClass)
print(MyShinyClass())

Execution result:

<class '__main__.MyShinyClass'>
<__main__.MyShinyClass object at 0x7f9cd02bddc0>

When creating a class with type, we need three arguments.

Next, let's take a closer look at how to use type.

class Foo:
    bar = True

    def echo_bar(self):
        print(self.bar)

If you create a class with the same structure as above with type, it will be as follows.

def echo_bar(self):
    print(self.bar)


Foo = type('Foo', (), {'bar': True, 'echo_bar': echo_bar})

Create a class with inheritance relationship.

class FooChild(Foo):
    pass

If you make it with type, it will be as follows.

FooChild = type('FooChild', (Foo, ), {})

5-3-3. Definition of metaclass

[Metaclass](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF], as in [5-2-Types and Objects] % E3% 83% A9% E3% 82% B9) is the class of the class, which is the class that creates the class. I explained that " type is the parent of all metaclasses, and you can create a metaclass by inheriting type." However, type itself is also a metaclass. The relationship between metaclasses, classes, and instances is shown in the figure below.                      image.png

The type function is a special metaclass. In fact, when creating a class using class, Python uses type behind the scenes. Therefore, all ʻobjects are instances of type`.

x = 30
print(x.__class__)
#Execution result:<class 'int'>
print(x.__class__.__class__)
#Execution result:<class 'type'>

type is a built-in metaclass. I explained about making my own metaclass in [5-2. Types and Objects](5-2-Types and Objects), but it is as follows.

class Meta(type):
    pass


class Foo(metaclass=Meta):
    pass

5-3-4. How to use the metaclass

The purpose of using a metaclass is to do some customization automatically when you create the class. For example, in a module, you can create a metaclass like this when you want to capitalize the attribute names of all classes.

class UpperAttrMetaClass(type):
    # __new__Is a constructor that creates an instance self
    # __init__Is an initializer that initializes the created instance self
    def __new__(cls, new_class_name,
                new_class_parents, new_class_attr):
        uppercase_attr = {}
        for name, val in new_class_attr.items():
            #Excluding special methods
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        return type.__new__(cls, new_class_name, new_class_parents, new_class_attr)
        #Same as below
        # return super().__new__(cls, new_class_name, new_class_parents, new_class_attr)

Metaclasses can be used for data type checking (https://en.wikipedia.org/wiki/Type_introspection), inheritance controls, and more. Introducing a metaclass can make your code a bit more complicated, but the role of the metaclass itself is simple. All you have to do is interrupt the process of creating the default class, make modifications, and return the modified class.

In addition, the Python standard library has a module called types, which provides functions related to metaclasses and class generation.

types.prepare_class (name, bases = (), kwds = None) is a function that chooses the appropriate metaclass for the new class you are about to create. The return value of the function will be a tuple of metaclass, namespace, kwds. types.new_class (name, bases = (), kwds = None, exec_body = None) is the function that creates the new class, and ʻexec_body receives the callback function to build the namespace for the new class. .. For example, you can use an ordered dictionary with ʻexec_body = lambda ns: collections.OrderedDict () to build a namespace (not required since Python 3.6).

import types


class A(type):
    expected_ns = {}

    def __new__(cls, name, bases, namespace):
        return type.__new__(cls, name, bases, namespace)

    @classmethod
    def __prepare__(cls, name, bases, **kwargs):
        expected_ns.update(kwargs)
        return expected_ns


B = types.new_class("B", (object,))
C = types.new_class("C", (object,), {"metaclass": A})

#The bottom of the metaclass inheritance chain is A, not type
meta, ns, kwds = types.prepare_class("D", (B, C), {"metaclass": type, 'x': 1})

assert meta is A  #You can see that metaclass A at the bottom of the inheritance chain is selected
assert ns is expected_ns  #Of A__prepare__Can be confirmed that is used
print(kwds)  #You can see that the metaclass keyword argument has been removed (because it returned the appropriate metaclass as the return value)
#Execution result:{'x': 1}

An ORM is a practical example of a metaclass. Let's take Django's ORM as an example.

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()


guy = Person.objects.get(name='bob')
print(guy.age)  # output is 35

Django's ORM is very easy to use as mentioned above. Django uses metaclasses to enable complex database queries and more. I'll show you an implementation example of ORM later, so for more information on Django ORM, see django.db.models.base.ModelBase See /base.py#L72).

5-4. Descriptor

5-4-1. Descriptor basics

In [4-2. Properties](# 4-2-Properties), we have seen the property decorator. property can not only make a method like an instance variable, but also check for value assignment etc.

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            print('Please input an int')
            return
        self._score = value

There are two problems with value checking with the property decorator.

Descriptor is a solution to this problem. Descriptors are for customizing the browsing, saving and deleting of object attributes. If you implement one of __get__, __set__, and __delete__ in the class, it becomes a descriptor. When using it, the descriptor must be defined as a class variable of the owning class.

There are two types of descriptors:

5-4-2. Non-data descriptors and data descriptors

Let's create a simple descriptor to get the number of files in a directory.

import os


class DirectorySize:
    def __get__(self, instance, owner):
        return len(os.listdir(instance.dirname))


class Directory:
    size = DirectorySize()  #descriptor

    def __init__(self, dirname):
        self.dirname = dirname


debug = Directory('debug')
print(debug.size)

In addition to self, the descriptor method __get__ takes two arguments: its own class ʻowner and its instance ʻinstance. Instantiate the descriptor DirectorySize in the Directory class and put it in the class variable size. And when you call size, the __get__ method of DirectorySize is called.

print(vars(debug))
#Execution result:{'dirname': 'debug'}

As you can see from the code above, the non-data descriptor does not exist in the instance namespace.

Next, let's implement a data descriptor that implements __get__ and __set__.

import logging


logging.basicConfig(level=logging.INFO)


class LoggedAgeAccess:
    def __get__(self, instance, owner):
        value = instance._age
        logging.info('Accessing %r giving %r', 'age', value)
        return value

    def __set__(self, instance, value):
        logging.info('Updating %r to %r', 'age', value)
        instance._age = value


class Person:
    age = LoggedAgeAccess()  #descriptor

    def __init__(self, name, age):
        self.name = name  #Ordinary instance variable
        self.age = age  #Call descriptor

    def birthday(self):
        self.age += 1  # __get__When__set__Both are called


mary = Person('Mary M', 30)
mary.age
mary.birthday()

Execution result:

INFO:root:Updating 'age' to 30
INFO:root:Accessing 'age' giving 30
INFO:root:Accessing 'age' giving 30
INFO:root:Updating 'age' to 31

The descriptor method __set__ receives an instance of the owning class ʻinstance and a value valueto assign to the descriptor. Assigning a value to the descriptor calls thesetmethod. And the same is true when initializinginit`.

print(vars(mary))
#Execution result:{'name': 'Mary M', '_age': 31}

In the data descriptor, when you assign a value to an instance variable, it appears in the namespace.

The descriptor has a method called __set_name__. You can get the variable name assigned to the descriptor (ʻagein the above example), and you can modify it. The example below is a simple ORM implemented in a data descriptor using a metaclass andset_name`.

import sqlite3


conn = sqlite3.connect('entertainment.db')


class MetaModel(type):
    def __new__(cls, clsname, bases, attrs):
        table = attrs.get('table')
        if table:
            col_names = [k for k in attrs.keys() if k != 'table' and not k.startswith('__')]
            #Give a dummy data type
            col_names_with_type = [f'{c} {attrs[c].datatype} PRIMARY KEY' if attrs[c].is_primary_key
                                   else f'{c} {attrs[c].datatype}'
                                   for c in col_names ]
            #Creating a table
            create_table = f"CREATE TABLE IF NOT EXISTS {table} ({', '.join(col_names_with_type)});"
            conn.execute(create_table)
            conn.commit()
            attrs['_columns_'] = col_names  #Store column name of each model

        return super().__new__(cls, clsname, bases, attrs)


class Model(metaclass=MetaModel):
    def __init__(self, *col_vals):
        self.col_vals = col_vals  #Stores the value of each column of the record
        cols = self._columns_
        table = self.table
        pk = self.primary_key
        pk_val = self.primary_key_value = col_vals[cols.index(pk)]
        record = conn.execute(f'SELECT * FROM {table} WHERE {pk}=?;',
                              (pk_val,)).fetchone()
        if not record:
            params = ', '.join(f':{c}' for c in cols)
            conn.execute(f"INSERT INTO {table} VALUES ({params});", col_vals)
            conn.commit()
        else:
            params = ', '.join(f"{c}=?" for c in cols)
            upate_col_vals = col_vals + (pk_val,)
            conn.execute(f"UPDATE {table} SET {params} WHERE {pk}=?;", upate_col_vals)


class Field:
    def __init__(self, datatype, primary_key=False):
        self.datatype = datatype
        self.is_primary_key = primary_key

    def __set_name__(self, owner, name):
        if self.is_primary_key:
            owner.primary_key = name
        self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.primary_key}=?;'
        self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.primary_key}=?;'

    def __get__(self, instance, owner):
        return conn.execute(self.fetch, [instance.primary_key_value]).fetchone()[0]

    def __set__(self, instance, value):
        conn.execute(self.store, [value, instance.primary_key_value])
        conn.commit()
        if self.is_primary_key:
            instance.primary_key_value = value


class MovieModel(Model):
    table = 'Movie'
    title = Field(datatype='TEXT', primary_key=True)
    director = Field(datatype='TEXT')
    year = Field(datatype='INTEGER')


class MusicModel(Model):
    table = 'Music'
    title = Field(datatype='TEXT', primary_key=True)
    artist = Field(datatype='TEXT')
    year = Field(datatype='INTEGER')
    genre = Field(datatype='TEXT')


star_wars = MovieModel('Star Wars', 'George Lucas', 1977)
print(f'{star_wars.title} released in {star_wars.year} by {star_wars.director}')
star_wars.director = 'J.J. Abrams'
print(star_wars.director)
country_roads = MusicModel('Country Roads', 'John Denver', 1973, 'country')
print(f'{country_roads.title} is a {country_roads.genre} song of {country_roads.artist}')

Execution result:

Star Wars released in 1977 by George Lucas
J.J. Abrams
Country Roads is a country song of John Denver

In this way, ORM can be easily implemented by combining metaclasses and data descriptors. Of course there is no restriction that you have to use both, for example Django's Field does not use descriptors. The actual ORM implements more complicated functions such as type evaluation and cache at the application layer so as to reduce the number of communications with the DB as much as possible.

5-4-3. Descriptor mechanism

I mentioned __getattribute__ in [5-1. Special Methods](# 5-1-Special Methods). __getattribute__ is a method that has the function of" called when accessing all class members regardless of whether it is undefined or defined ", and it is called like bx for a class using a descriptor. Is replaced with a process like type (b) .__ dict __ ['x'] .__ get__ (b, type (b)).

class Descriptor:
    def __get__(self, instance, owner):
        return self._x

    def __set__(self, instance, value):
        self._x = value


class B:
    x = Descriptor()

    def __init__(self, x):
        self.x = x

    def __getattribute__(self, key):
        attr = type(self).__dict__[key]
        if hasattr(attr, '__get__'):
            return attr.__get__(self, type(self))
        else:
            return attr

Therefore, if you customize __getattribute__, you will not be able to use descriptors. And, as you might expect, data descriptors that implement __get__ and __set__ check variable assignments, so they always overwrite instance variables. In the above example, b.x remains a descriptor even if b = B (1); b.x = 2. On the other hand, the non-data descriptor that implements only __get__ does not check the variable assignment, so if you update the class variable directly, the descriptor will be overwritten.

5-4-4. How to use descriptor

In fact, you can use descriptors to achieve the same functionality as the property, classmethod, and staticmethod decorators introduced in [4. Python Object-Oriented Basics](# 4-python Object-Oriented Basics).

5-4-4-1. property property can be implemented as follows.

class Property:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

When the above descriptor is used for somemethod in the form of decorator @ Property, it is actually the process of somemethod = Property (somemethod). Then, assign the first argument fget to self.fget of Property to create an instance. Next, in the setter method of the Property instance created with @ somemethod.setter, assign fset to the instance argument. You can then assign fdel to the instance as well with @ somemethod.deleter. This flow is the same as [4-2. Properties](# 4-2-Properties).

5-4-4-2. method and function

I briefly introduced MethodType in [4-1. Class Variables and Methods](# 4-1-Class Variables and Methods). The same functionality can be implemented in Python code as follows:

class MethodType:
    def __init__(self, func, obj):
        self.__func__ = func
        self.__self__ = obj

    def __call__(self, *args, **kwargs):
        func = self.__func__
        obj = self.__self__
        return func(obj, *args, **kwargs)

And you can create a descriptor that turns a function into a method inside a class like this.

class Function:
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return MethodType(self, obj)

In [5-1. Special Method](# 5-1-Special Method), I explained that "ʻinstance.method.func returns a function object that is the entity of the method". However, when I access it with ʻinstance.method, it returns a method object. This behavior can be simulated with the descriptor above.

5-4-4-3. Class method and static method

These two decorators are very easy to implement using the MethodType above. First, classmethod can be implemented as follows:

class ClassMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if hasattr(obj, '__get__'):
            return self.f.__get__(cls)
        return MethodType(self.f, cls)

When used in the form @ ClassMethod, we havesomemethod = ClassMethod (somemethod), and we can band somemethod to a class instead of an instance.

Next, let's look at the static method.

class StaticMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

The entity of Python's static method staticmethod is an ordinary function. Using the above StaticMethod in the form @StaticMethod also results in somemethod = StaticMethod (somemethod), simply storing the function somemethod in the descriptor instance variable self.f, and the class Prevents it from being banded to an instance. And when it is called, it returns self.f as it is.

5-4-4-5. types.DynamicClassAttribute Lesser known is the Python standard library, which has a descriptor called types.DynamicClassAttribute. Usage is the same as property. This descriptor is exactly the same as a normal property when accessed from an instance, and its functionality changes only when accessed from a class. When accessed from a class, if __getattr__ is not defined in the __getattr__ method of the class, it will be transferred to the metaclass __getattr__.

from types import DynamicClassAttribute


class EnumMeta(type):
    def __new__(cls, name, bases, namespace):
        reserved_names = ('name', 'value', 'values')
        enum_namespace = namespace.copy()
        enum_namespace['_member_map_'] = {}
        enum_namespace['_member_map_']['values'] = []
        for k, v in namespace.items():
            if not (k in reserved_names or k.startswith('_')):
                member_namespace = namespace.copy()
                member_namespace.update({"_name_": k, "_value_": v})
                member_cls = super().__new__(cls, name, bases, member_namespace)
                enum_namespace['_member_map_']['values'].append(v)
                enum_namespace['_member_map_'][k] = member_cls()
                enum_namespace[k] = enum_namespace['_member_map_'][k]

        return super().__new__(cls, name, bases, enum_namespace)

    def __getattr__(self, item):
        return self._member_map_[item]


class Enum(metaclass=EnumMeta):
    @DynamicClassAttribute
    def name(self):
        return self._name_

    @DynamicClassAttribute
    def value(self):
        return self._value_

    @DynamicClassAttribute
    def values(self):
        return self._values_


class Color(Enum):
    red = 1
    blue = 2
    green = 3


print(Color.red.value)
#Execution result: 1
Color.red._values_ = [1]
print(Color.red.values)  #Instance values
#Execution result:[1]
print(Color.values)  #Class values
#Execution result:[1, 2, 3]

The above is a simple enumeration type that I made. We'll talk more about enums later. Each class variable of the ʻEnum class here has been converted to an instance of ʻEnum by the metaclass ʻEnumMeta. And with types.DynamicClassAttribute, the valuesof the class and thevaluesof the instance could coexist without interfering with each other. In this way, if you want to achieve different behavior for classes and instances, it is easier to usetypes.DynamicClassAttribute`.

5-4-4-5. __slots__ Python has a special attribute __slots__ that can prevent existing classes from adding new attributes in monkey patches. The usage is as follows.

class Student:
    __slots__ = ('name', 'age')


student = Student()

student.name = 'Mike'
student.age = 20

student.grade = 'A'
#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 10, in <module>
#     student.grade = 'A'
# AttributeError: 'Student' object has no attribute 'grade'

According to the Official Documentation, this feature was actually made possible by descriptors as well. Although not implemented here, such a function can be realized by combining a metaclass and a descriptor.

5-5. Abstract base class

Although a little touched upon in [3-18-7. Pure Artificials](# 3-18-7-Pure Artificials), abstract base classes are a very powerful weapon in object-oriented programming. You can use an abstract base class to determine if a class provides a particular interface. The Python standard library has a module called ʻabc` that provides the tools needed for abstract base classes, which allow you to create abstract base classes, abstract methods, and more.

5-5-1. Interface

In [3. Object-Oriented Concepts](# 3-Object-Oriented Concepts), the term "interface" was repeated many times. Interface is a very important concept in the field of software engineering. A well-known interface is the API (Application Programming Interface) (https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%97%E3%83%AA%E3% 82% B1% E3% 83% BC% E3% 82% B7% E3% 83% A7% E3% 83% B3% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E3% 82% A4% E3% 83% B3% E3% 82% BF% E3% 83% 95% E3% 82% A7% E3% 83% BC% E3% 82% B9). Interfaces in object orientation refer to those at the object level.

However, unlike Java and C ++, Python does not have a built-in interface class. There are several ways to achieve the same functionality as an interface in Python.

5-5-1-1. Interface with virtual base class

Virtual base classes have no explicit inheritance relationship, but they impose interface constraints. In Python, you can use metaclasses to implement virtual base classes.

class RESTAPIMeta(type):
    def __instancecheck__(cls, instance):
        return cls.__subclasscheck__(type(instance))

    def __subclasscheck__(cls, subclass):
        return (hasattr(subclass, 'get') and
                callable(subclass.get) and
                hasattr(subclass, 'post') and
                callable(subclass.post))


class RESTAPIInterface(metaclass=RESTAPIMeta):
    ...


class ItemList:
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList:
    def get(self, id):
        pass


print(issubclass(ItemList, RestAPIInterface))
#Execution result: True
print(ItemList.__mro__)
#Execution result:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Execution result: False

The above is an example of a virtual base class that defines REST API. Implemented to determine a class with get and post methods as a child class in the __subclasscheck__ of the metaclass RESTAPIMeta. This allows you to put some restrictions on the interface of your class without explicit inheritance.

5-5-1-2. Interface with abstract base classes

Let's implement the above virtual base class using the ʻabcmodule. Abstract base classes can be created in the formatclass MyClass (abc.ABC)orclass MyClass (metaclass = abc.ABCMeta)`.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, subclass):
        return (hasattr(subclass, 'get') and
                callable(subclass.get) and
                hasattr(subclass, 'post') and
                callable(subclass.post))


class ItemList:
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList:
    def get(self, id):
        pass


print(issubclass(ItemList, RestAPIInterface))
#Execution result: True
print(ItemList.__mro__)
#Execution result:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Execution result: False

The __subclasshook__ method is implemented as a class method of the instance class created from ʻabc.ABCMeta, and when you call ʻissubclass, it functions as a hook.

You can also use ʻABCMeta.register` to register virtual subclasses.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


class UserList:
    def get(self, id):
        pass


RestAPIInterface.register(UserList)
print(issubclass(UserList, RestAPIInterface))
#Execution result: True

It can also be used as a decorator.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


@RestAPIInterface.register
class UserList:
    def get(self, id):
        pass


print(issubclass(UserList, RestAPIInterface))

You can also get the cache token of the current abstract base class with ʻabc.get_cache_token (). This token changes each time ʻABCMeta.register is executed, so it can be used for equivalence verification.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


class UserList:
    def get(self, id):
        pass


token_old = abc.get_cache_token()
RestAPIInterface.register(UserList)
token_new = abc.get_cache_token()

print(f'{token_old} >>> {token_new}')
#Execution result: 36>>> 37
5-5-1-2. Abstract method

Since the interface so far is a virtual base class, there is no inheritance relationship and the restrictions on child classes are weak. If you do not implement a specific interface, you need to use the abstract base class and the abstract method together instead of the virtual base class when you want to realize the function that causes an error.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get(self, id):
        raise NotImplementedError

    @abc.abstractmethod
    def post(self, id):
        raise NotImplementedError


class ItemList(RestAPIInterface):
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList(RestAPIInterface):
    def get(self, id):
        pass


item_list = ItemList()
user_list = UserList()

Execution result:

Traceback (most recent call last):
  File "resource.py", line 29, in <module>
    user_list = UserList()
TypeError: Can't instantiate abstract class UserList with abstract methods post

Also, ʻabc.abstractmethod can be used with classmethod, staticmethod, property`, etc.

import abc


class Model(abc.ABC):
    @classmethod
    @abc.abstractmethod
    def select_all(cls):
        ...

    @staticmethod
    @abc.abstractmethod
    def show_db_name(age):
        ...

    @property
    @abc.abstractmethod
    def row_id(self):
        ...

By implementing the descriptor __isabstractmethod__ when using it together with the descriptor used in the form of a decorator as introduced in [5-4-4. How to use the descriptor](# 5-4-4-How to use the descriptor) , ʻAbc.abstractmethod` can be used together.

import abc


class StaticMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

    @property
    def __isabstractmethod__(self):
        return getattr(self.f, '__isabstractmethod__', False)


class Model(abc.ABC):
    @StaticMethod
    @abc.abstractmethod
    def show_db_name():
        ...


class ItemModel(Model):
    pass


item_model = ItemModel()
#Execution result:
Traceback (most recent call last):
  File "oop.py", line 27, in <module>
    item_model = ItemModel()
TypeError: Can't instantiate abstract class ItemModel with abstract methods show_db_name

And Python abstract methods aren't just interfaces, they can be inherited with super to get the contents of the method.

5-5-1-3. Container abstract base class

The standard library collections.abc provides an abstract base class for Python built-in data structures (containers).

ABC Inheriting class Abstract method mixin method
Container __contains__
Hashable __hash__
Iterable __iter__
Iterator Iterable __next__ __iter__
Reversible Iterable __reversed__
Generator Iterator send、throw close、__iter__ 、__next__
Sized __len__
Callable __call__
Collection Sized、Iterable、
Container
__contains__、
__iter__ 、__len__
Sequence Reversible、
Collection
__getitem__、
__len__
__contains__、__iter__、__reversed__、
index、count
MutableSequence Sequence __getitem__、
__setitem__、
__delitem__、
__len__、insert
Methods inherited from Sequence and
append、reverse、extend、pop、remove、
__iadd__
ByteString Sequence __getitem__、
__len__
Methods inherited from Sequence
Set Collection __contains__、
__iter__、__len__
__le__、__lt__、__eq__、__ne__、__gt__、
__ge__、__and__、__or__、__sub__、__xor__、
isdisjoint
MutableSet Set __contains__、
__iter__、__len__、
add、discard
Methods inherited from Set and clear, pop,
remove、__ior__、__iand__、__ixor__、
__isub__
Mapping Collection __getitem__、
__iter__、__len__
__contains__、keys、items、values、get、
__eq__、__ne__
MutableMapping Mapping __getitem__、
__setitem__、
__delitem__、
__iter__、__len__
Methods inherited from Mapping, pop,
popitem、clear、update、setdefault
MappingView Sized __len__
ItemView MappingView、Set __contains__、__iter__
KeysView MappingView、Set __contains__、__iter__
ValuesView MappingView、
Collection
__contains__、__iter__
Awaitable __await__
Coroutine Awaitable send、throw close
AsyncIterable __aiter__
AsyncIterator AsyncIterable __anext__ __aiter__
AsyncGenerator AsyncIterator asend、athrow aclose、__aiter__、__anext__

(See: collections.abc — Abstract Base Classes for Containers)

Usage is the same as a normal abstract base class.

from collections.abc import Set


class ListBasedSet(Set):
    def __init__(self, iterable):
        self.elements = []
        for value in iterable:
            if value not in self.elements:
                self.elements.append(value)

    def __str__(self):
        return repr(self.elements)

    def __iter__(self):
        return iter(self.elements)

    def __contains__(self, value):
        return value in self.elements

    def __len__(self):
        return len(self.elements)


s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2  # __and__Is inherited, so you can calculate the intersection as it is
print(overlap)
#Execution result:['d', 'e', 'f']

The above is an implementation of a list-based set. A normal set of Python is implemented using a hash table like a dictionary, so it is faster than a list in terms of time complexity, but it has a little more spatial complexity, so this implementation is when you want to save memory consumption. Can be used.

5-6. Enumeration type

Enumeration is a finite set of variables (formally, identifiers) etc. It is an abstract data structure that is bundled as. It has nothing to do with object-oriented programming. For example, the process-oriented programming language C also supports enums. However, in object-oriented programming languages like Java and Python, enums are implemented in the form of class objects.

The reason why enums are needed is that there are quite a lot of data in the real world that are limited to a finite range. For example, a day of the week is seven types of finite data limited to weekly units. Similarly, there are 12 types of data for each month. In software, there are innumerable sets of finite states such as CSS color names, HTTP codes, boolean values, and file descriptors. Representing these as enums rather than incomprehensible numbers improves the readability of your code and makes your logic look cleaner.

The Python standard library provides a module for creating enums called ʻenum`. First, let's see the basic usage.

import enum


class Color(enum.Enum):
    red = 1
    green = 2
    blue = 3


print(Color.red)
#Execution result: Color.red
print(Color['red'])
#Execution result: Color.red
print(Color(1))
#Execution result: Color.red
print(Color.red.value)
#Execution result: 1
print(Color.red.name)
#Execution result: red

for color in Color:
    print(color)
#Execution result:
# Color.red
# Color.green
# Color.blue

Unlike class variables, enums are iterable objects.

Color.red = 4
#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 26, in <module>
#     Color.red = 4
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 383, in __setattr__
#     raise AttributeError('Cannot reassign members.')
# AttributeError: Cannot reassign members.

Members of enums cannot be modified externally.

print(Color.red is Color.green)
#Execution result: False
print(Color.red == Color.green)
#Execution result: False
red = Color.red
print(Color.red == red)
#Execution result: True
print(Color.red < Color.green)
#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 30, in <module>
#     print(Color.red < Color.green)
# TypeError: '<' not supported between instances of 'Color' and 'Color'

The ʻenum.Enum enumeration only supports consistency and equivalence evaluation between members. If you want to compare other values, you can use ʻenum.IntEnum.

import enum


class Color(enum.IntEnum):
    red = 1
    green = 2
    blue = 3
    purple = enum.auto()  #auto increment of value


print(Color.purple > Color.blue)
#Execution result: True

Also, if you want to realize the combination of members in the bid operation, you can use ʻenum.Flag`.

import enum


class Color(enum.Flag):
    red = enum.auto()
    green = enum.auto()
    blue = enum.auto()
    purple = enum.auto()


print(Color.__members__)
#Execution result:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Execution result: Color.purple|blue
print(Color.purple | 2)
#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 13, in <module>
#     print(Color.purple | 2)
# TypeError: unsupported operand type(s) for |: 'Color' and 'int'

ʻEnum.Flag supports bid operations between members, but cannot calculate with integer values. To achieve this, you need to use ʻenum.IntFlag.

import enum


class Color(enum.IntFlag):
    red = enum.auto()
    green = enum.auto()
    blue = enum.auto()
    purple = enum.auto()


print(Color.__members__)
#Execution result:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Execution result: Color.purple|blue
print(Color.purple | 2)
#Execution result: Color.purple|green

ʻEnum.IntFlag` treats members as integer values.

Next, let's take a closer look at the ordinary enum ʻenum.Enum`.

import enum


class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    BAD_MESSAGE = 2


print(MessageResult(2))
#Execution result: MessageResult.INVALID_MESSAGE


class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    INVALID_MESSAGE = 4

#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 14, in <module>
#     class MessageResult(enum.Enum):
#   File "oop.py", line 18, in MessageResult
#     INVALID_MESSAGE = 4
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 99, in __setitem__
#     raise TypeError('Attempted to reuse key: %r' % key)
# TypeError: Attempted to reuse key: 'INVALID_MESSAGE'

Enums guarantee the uniqueness of a member's name, but do not constrain it to value.

import enum


@enum.unique
class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    BAD_MESSAGE = 2

#Execution result:
# Traceback (most recent call last):
#   File "oop.py", line 4, in <module>
#     class MessageResult(enum.Enum):
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 865, in unique
#     raise ValueError('duplicate values found in %r: %s' %
# ValueError: duplicate values found in <enum 'MessageResult'>: BAD_MESSAGE -> INVALID_MESSAGE

Applying ʻenum.uniqueto a class as a decorator also guarantees the uniqueness ofvalue`.

import enum


MessageResult = enum.Enum(
    value='MessageResult',
    names=('SUCCESS INVALID_MESSAGE INVALID_PARAMETER'),
)

print(MessageResult.__members__)
#Execution result:
# {'SUCCESS': <MessageResult.SUCCESS: 1>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 3>}

You can also dynamically create enums with the Functional API instead of hard-coding. If you pass a space-separated string as the names argument, the numbers will be assigned automatically.

import enum


MessageResult = enum.Enum(
    value='MessageResult',
    names=(('SUCCESS', 3),
           ('INVALID_MESSAGE', 2),
           ('INVALID_PARAMETER', 1))
)

print(MessageResult.__members__)
#Execution result:
# {'SUCCESS': <MessageResult.SUCCESS: 3>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 1>}

You can specify the value for each member by passing a layered iterable object as the names argument.

import enum


class Message(enum.Enum):
    DB_SAVE_SUCCESS = ('Saved successfully', 201)
    INTERNEL_ERROR = ('Internal error happened', 500)
    DB_DELETE_SUCCESS = ('Deleted successfully', 200)
    DB_ITEM_NOT_FOUND = ('Item not found', 404)

    def __init__(self, message, code):
        self.message = message
        self.code = code

    @property
    def ok(self):
        if str(self.code).startswith('2'):
            return True
        return False


print(Message.DB_SAVE_SUCCESS)
#Execution result: Message.DB_SAVE_SUCCESS
print(Message.DB_DELETE_SUCCESS.ok)
#Execution result: True
print(Message.DB_ITEM_NOT_FOUND.ok)
#Execution result: False

The members of the enum can be any data type, not just integers. Also, if you implement the initializer __init__, the member values will be passed to __init__ when evaluating the class. And you can use tuples to pass multiple variables.

However, __init__ cannot customize member values. If you want to customize the members, you need to implement the constructor __new__.

import enum


class Coordinate(bytes, enum.Enum):
    def __new__(cls, value, label, unit):
        obj = bytes.__new__(cls, [value])
        obj._value_ = value
        obj.label = label
        obj.unit = unit
        return obj

    PX = (0, 'P.X', 'km')
    PY = (1, 'P.Y', 'km')
    VX = (2, 'V.X', 'km/s')
    VY = (3, 'V.Y', 'km/s')


print(Coordinate.PY.label, Coordinate.PY.value, Coordinate.PY.unit)
#Execution result: P.Y 1 km
print(Coordinate.PY)
#Execution result: Coordinate.PY

The above is an example from the official documentation, which is an enumeration type that stores member values and other information together in a binary object.

Since enums are classes, you can implement methods or dancers internally. However, enums have many differences from ordinary classes. First, enums are implemented in a special metaclass, where members (class variables) are instances of the class. Therefore, __new__ and __init__ work during class evaluation, not at instantiation timing. Then enums have some special attributes.

By the way, it seems that the attribute with one underscore before and after is called _sunder_.

5-7. Data class

So far, we've seen most of Python's object-oriented programming. It's not just a problem with Python, it's a problem with object-oriented programming languages in general, but class definitions are very complicated. In Python, in most cases, the definition of __init__ is the minimum required when creating a class. You may have to implement other special methods as well. On top of that, there are times when you need to create a large number of similar classes. Such code redundancy is called the Boilerplate Code (https://en.wikipedia.org/wiki/Boilerplate_code).

You can use types.SimpleNamespace as an easy way to create a class.

import types

bbox = types.SimpleNamespace(x=100, y=50, w=20, h=20)

print(bbox)
#Execution result: namespace(h=20, w=20, x=100, y=50)
print(bbox==bbox)
#Execution result: True

If you implement the same function as types.SimpleNamespace in Python, it will be as follows.

class SimpleNamespace:
    def __init__(self, /, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        items = (f"{k}={v!r}" for k, v in self.__dict__.items())
        return "{}({})".format(type(self).__name__, ", ".join(items))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

They implemented __init__, __repr__, and __eq__, but when you want to implement __hash__ or other special methods for comparison operations, it is quite troublesome, and on the contrary, the readability of the code may decrease. There is sex. types.SimpleNamespace is just for creating simple classes (namespaces).

Some third-party library developers have noticed a problem with boilerplate code and have developed a tool that simplifies class definitions. attrs is one of them. The basic usage is as follows.

from attr import attrs, attrib


@attrs
class Person:
    name = attrib(type=str)
    sex = attrib(type=str)
    age = attrib(type=int, default=0)


mary = Person('Mary', 'F', 18)
print(mary)
#Execution result: Person(name='Mary', sex='F', age=18)
print(mary == mary)
#Execution result: True

ʻAttrs can be used in the form of class decorators. Here we have defined three instance variables and set ʻage to the default argument. ʻAttrsautomatically definesinitandrepr. It also defines eq, ne, lt, __ le__, gt, and ge`, and the comparison target is a tuple of instance variables.

I won't go into detail, but ʻattrsis a more advanced tool thantypes.SimpleNamespace`, with a lot of features and a powerful tool for Python's object-oriented programming.

To eliminate the boilerplate code, there is a module called dataclasses introduced from Python 3.7 as an official move. This module provides similar functionality to ʻattrs`, such as generating special methods.

from dataclasses import dataclass
from typing import ClassVar
from functools import cached_property

import boto3


@dataclass
class S3Image:
    bucket: str
    key: str
    img_id: int = 1
    client: ClassVar = boto3.client('s3')  #Class variables

    @cached_property
    def image_url(self, http_method: str) -> str:
        return self.client.generate_presigned_url(...)


item_image_1 = S3Image('Image', 'ItemImage')
print(item_image_1)
#Execution result: S3Image(bucket='Image', key='ItemImage', img_id=1)
print(item_image_1 == item_image_1)
#Execution result: True

Data classes can define class member variables in the form of Python type annotations. And it implements __init__ and __repr__. Unlike ʻattrs, special methods for comparison operations only implement eqby default. And the method you want to implement can be defined as an argument of thedataclass` class decorator. The default is as follows.

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
    ...

The role of each argument is almost self-explanatory.

In addition, there is a function called field in the data class, and you can control each field.

from dataclasses import dataclass, field
from typing import List


@dataclass
class C:
    mylist: List[int] = field(default_factory=list)


c = C()
c.mylist += [1, 2, 3]

field takes the following arguments and returns a Field object.

You can get all the field objects of the data class in the form of tuples with the fields function.

from dataclasses import fields


print(fields(c))
#Execution result:
# (Field(name='mylist',type=typing.List[int],default=<dataclasses._MISSING_TYPE object at 0x7f8aa098a9a0>,
# default_factory=<class 'list'>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD),)

ʻAsdict and ʻasuple convert instances of a data class into dictionaries and tuples.

@dataclass
class Point:
    x: int
    y: int


@dataclass
class Points:
    pl: List[Point]


p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}
assert astuple(p) == ((10, 20))

ps = Points([Point(0, 0), Point(10, 4)])
assert asdict(ps) == {'pl': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}  #Processed recursively

There is also a function called make_dataclass that dynamically creates a data class.

from dataclasses import dataclass, field, make_dataclass


C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

#Same as below
@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1

A function replace is also provided to modify an instance of the data class and create a new object of the same type.

from dataclasses import dataclass, replace


@dataclass
class Point:
    x: int
    y: int

    def __post_init__(self):
        print('__post_init__')


p1 = Point(1, 2)
#Execution result:__post_init__
p2 = replace(p1, x=2)
#Execution result:__post_init__
print(p2)
#Execution result: Point(x=2, y=2)

replace calls __init__ to create a new instance, but if __post_init__ is defined, it is called after __init __.

In addition, the function ʻis_dataclassfor determining the data class is also provided. It returnsTrue` only when used for a data class or an instance of a data class.

Summary

From object-oriented history, we've looked at every detail of OOP's related concepts, Python's object-oriented programming. However, object-oriented programming is so profound that there is still much to learn. I couldn't explain the design pattern in detail this time. I would like to introduce it in another article.

reference

Data Model Built-in Functions inspect — Inspect live objects types — Dynamic type creation and names for built-in types Descriptor HowTo Guide abc — Abstract Base Classes collections.abc — Abstract Base Classes for Containers enum — Support for enumerations dataclasses — Data Classes What are metaclasses in Python? Python Types and Objects

Recommended Posts

A complete understanding of Python's object-oriented programming
Complete understanding of numpy.pad functions
Understanding memo of collective intelligence programming
A memorandum of using Python's input function
[Python] A rough understanding of the logging module
[Python] A rough understanding of iterators, iterators, and generators
[PyTorch] A little understanding of CrossEntropyLoss with mathematical formulas
[Note] Beginning of programming
Recruitment of programming masters
Simulation of complete gacha
elasticsearch_dsl Memorandum of Understanding
Instantly create a diagram of 2D data using python's matplotlib
Create a pixel art of Levi Captain with Python programming!
[For beginners] A word summary of popular programming languages (2018 version)