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.
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.
verb (subject, object)
. The verb is the main, and the subject and object are passed as arguments.subject, verb, object
. In other words, the subject is the main. Then it calls a verb with a subject and passes the object as an argument. In Japanese, the verb comes later, so the SVO type may not fit well, but English, many European languages, Chinese, etc. are languages similar to the SVO type, so the object-oriented method is semantically Will be natural.** 2. Definition method **
sum
, and returns
the sum of them.class
. In that class
, define various methods (which can be understood as functions). Then create an instance of class
and call the method from that instance. is an integer, which is an instance of
class called ʻint
. Integer instances can use ʻint` methods like addition and subtraction.class
, and the methods that can be used are also decided. For example, when a string comes in, you can see what methods you can use by looking at class str
.** 3. Calling method ** In practice, when you want to do the same for multiple objects:
class
and its class
methods, creates an instance of class
for all objects, and calls the method from that instance.When there are many such processes
class
, and the methods that can be used by objects are self-explanatory.Next, let's take a closer look at the benefits of object orientation through Python code examples.
We define three class
s: 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.
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.
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 class
s," 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.
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.
[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`.
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.
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.
[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.
Class Variables A variable shared by a class and its instances.
[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.
[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.
[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.
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).
[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.
Encapsulation is It refers to the act of grouping data and processing into an object to create a boundary.
[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.
[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).
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.
[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.
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 called
run ()` 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).
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.
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.
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.
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. (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.
(Source: Agile Principles, Patterns, and Practices in C #)
The Dependency Inversion Principle involves two rules.
This principle is for decoupling between modules. The following are examples. (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. (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.
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 the
class.
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
.
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.
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.
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 method
SaveOrder () 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.
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.
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.
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.
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
[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.
I explained the concept of object orientation. Now let's create four classes to see the basic structure of Python's object-oriented programming.
property
that defines the propertysuper
for class member inheritancePython 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
self
that points to the instance itself.cls
.MethodType
or direct assignment.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
@property
decorator converts a method into a variable.property
function. The fourth argument, the string " I'm the'city' property. "
, Is in the documentation and can be found in Dog.city.__doc__
.@cached_property
is a property
that caches the values implemented in Python 3.8. When processing variables with a large amount of calculation, if it is cached, recalculation is not necessary, which leads to performance improvement.Let's define the Cat
class and its child class BlackCat
and look at inheritance and overriding of private variables and methods.
__init__
.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'
weight
is an ordinary variable and can be used externally._weight
, are private variables that are not recommended for external use and are not restricted in their use. However, if you use an object name (class name, function name, module scope variable name, etc.), it will not be imported by from module import *
.__weight
are private variables that are prohibited from being used from the outside. However, it can be forcibly called in the form <class> ._ <class> __ x
. It should be used when you want to avoid attribute conflicts due to inheritance.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
return super (). eat ()
just returns the ʻeatmethod of the parent class, you don't need to use
super unless you define the ʻeat
method in the child class.super () .__ init__ (name, age)
runs the parent class initializer __init__
. Without it, you cannot call self.name
and self.age
. There are several ways to write equivalent to super () .__ init__ (name, age)
as follows.** 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
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.
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 to
dir (). 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.
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. And
type 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.
snake
class that inherits from reptile
, you can say "snake is a kind of reptile". If you want to refer to the parent class, you can use __base__
.snake
called Squasher
and say "Squasher is an instance of snake". The snake
here is defined as the type class of Squasher
. If you want to reference the type class of an instance, you can use the __class__
or thetype ()
function.The relationship between these two types is shown in the figure below.
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.
From the above verification, we arrived at the following results.
s are instances of
type`.type
are classes that inherit from ʻobject and ʻobject
, which are" classes "in Python's object orientation.type
, that is, a further instance of a" class ", is a class that does not have __bases__
, and these are "instances" in Python's object orientation.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.
type
is the parent of all metaclasses, and you can create a metaclass by inheriting type
.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.
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
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, ), {})
[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.
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
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).
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.
property
and x.setter
decorators to the variables respectively, and the more variables you have, the more verbose your code will be.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:
__get__
is called a non-data descriptor.__get__
and __set__
is called a data descriptor.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 the
setmethod. And the same is true when initializing
init`.
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 and
set_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.
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.
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).
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.
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 the
valuesof 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 use
types.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.
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.
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.
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.
Let's implement the above virtual base class using the ʻabcmodule. Abstract base classes can be created in the format
class MyClass (abc.ABC)or
class 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
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.
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.
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 of
value`.
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.
__members__
: A mapping of member_name: member
, read-only._name_
: Member name_Value_
: The member value, __new__
, can be set or changed._missing_
: A search function used when a value is not found; it can be overridden._ignore_
: In a list or string, class variables that match the elements in it will no longer be converted to members._order_
: A class attribute to maintain the order of members; for example, if you define with _order_ ='red green blue'
, if you define members in a different order, an error will occur._generate_next_value
: Used for Functional API
and ʻenum.auto` to get the appropriate value for a member; it can be overridden.By the way, it seems that the attribute with one underscore before and after is called _sunder_
.
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 defines
initand
repr. 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 than
types.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 the
dataclass` 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.
is a flag for automatic addition of
ne,
lt,
__ le__,
gt, and
ge, which are special methods for comparison operations other than
eq. The ʻeq
argument must also be True to be True. is a flag that controls the behavior of
hash. ʻUnsafe_hash = True
will automatically add the __hash__
method, but this can cause problems when it is not an immutable object. By default, when both ʻeqand
frozen are True, the
hash method is added automatically, so no flag control is required. If ʻeq = True
, frozen = False
, it inherits the parent class __hash__
, and if ʻeq = False,
frozen = True, it is set to
hash = None. Also, ʻunsafe_hash = True
cannot be set with __hash__
defined.frozen
is a flag that controls the assignment of values to fields (members). If True
, block the assignment and make it read-only.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.
default
is an argument that provides the default value for the field. default
and default_factory
cannot coexist.default_factory
takes an object that can be called with no arguments and provides the default factory for the field. For example, the above example provides a list
, which creates the default data structure for the field. is a flag to include fields in
__ init __`.repr
is a flag to include fields in __repr__
.compare
is a flag to include fields in special methods for comparison operations such as __eq__
and __gt__
.hash
is a flag to include fields in __hash__
. If hash = None
, use compare
to calculate the hash value. Officially, hash = None
is recommended.metadata
takes a mapping or None
and in the case of mapping it is wrapped in a read-only dictionary types.MappingProxyType
. It is mainly used by third-party modules.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 returns
True` only when used for a data class or an instance of a data class.
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.
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