Python boot camp by Dr.Angela day 21

#Inheritance (継承)

```python:inheritance_example.py
class World:

    def __init__(self):
        self.continent_num = 6
        self.oceans_num = 3
    
    def globe(self):
        print(f"This is the world")
        print(f"Continent: {self.continent_num}")
    
class Asia(World):
    def __init__(self):
        super().__init__()             #Class Worldの__init__()を呼び出している
        print("Where is the world?")   #Super Classの既存の__init()__に追加編集

    def sea(self):
        super().globe()                #Class Worldのglobe()を呼び出している
        print(f"Oceans: {self.oceans_num}")    #---> *

asia = Asia()
asia.sea()              #実質asiaが直接呼び出しているのはsea()メソッドだけ。

#出力結果
Where is the world?
This is the world.
Continent: 6
Oceans: 3
```
def sea(self)の中で直接的にClass Worldのdef \_\_init()\_\_を呼び出していないが、*の行でself.oceans_numを指定した時にエラーなく、Class Worldのdef \_\_init( )\_\_を呼び出せている。これは、def \_\_init( )\_\_は他の関数が呼び出されるときに必ず呼び出されていることを明示している。

|トレースすると以下のような感じ|
|::|
|Class Asiaのseaメソッドをcall|
|↓|
|seaメソッドが呼ばれたので、その前のAsiaのinitメソッドが先にcallされる|
|↓|
|変数定義： self.continent_num = 6 & self.oceans_num = 3 <br>print("Where is the world?")|
|↓|
|Asia.seaメソッド内1行目でClass Worldのglobeメソッドが呼ばれたので、その前のWorldのinitメソッドが先にcallされる|
|↓|
|また変数定義： self.continent_num = 6 & self.oceans_num = 3|
|↓|
|Class Worldのglobeメソッドがcallされ、 <br>print(f"This is the world") & print(f"Continent: {self.continent_num}")|
|↓|
|Asiaのsea( )メソッド内2行目がcallされ、print(f"Oceans: {self.oceans_num}")|
以上のような感じで出力結果の順番的には
変数定義
"Where is the world?"
"This is the world"
変数定義(2回目)
f"Continent: {self.continent_num}"
f"Oceans: {self.oceans_num}"
となる。

#Mission>> Invader Game Program

```python:invader_main.py
from turtle import Turtle, Screen
from invader_bar import Bar1, Bar2
from invader_food import Food
from invader_scoreboard import Scoreboard1, Scoreboard2
import threading

screen = Screen()
screen.setup(600,600)
screen.bgcolor("black")
screen.title("Invader Game")
line = Turtle()
line.hideturtle()
line.color("white")
line.write(arg="-"*120,align="center" )

bar1 = Bar1()
bar2 = Bar2()

food = Food()
scoreboard1 = Scoreboard1()
scoreboard2 = Scoreboard2()
scoreboard1.update_scoreboard()
scoreboard2.update_scoreboard()

screen.listen()
screen.onkey(bar1.left, "Left")
screen.onkey(bar1.right, "Right")
screen.onkey(bar2.left, "a")
screen.onkey(bar2.right, "s")

target1 = bar1.move
target2 = bar2.move

thread1 = threading.Thread(target=target1)       #下：bar1, 上：bar2
thread2 = threading.Thread(target=target2)
thread1.start()
#thread1.join()
thread2.start()
#thread2.join()

screen.exitonclick()
```

```python:invader_bar.py
from turtle import Turtle, Screen

#BAR = Turtle(shape = "square")  #default square size = 20×20  BARとした方がいいのかself.barとした方がいいのか
GAME_IS_ON = True
LEFT = 180
RIGHT = 0

class Bar1:
    
    def __init__(self):
        self.new_bar = []
        self.bar = Turtle(shape = "square")
        self.screen = Screen()
        self.create_bar()   #ここでcreate_bar()を呼んでいるので、main.pyの方でcreate_bar()を呼ばなくて良い！

    def create_bar(self):
        x = -260
        y = -260
        self.bar.penup()
        self.bar.setpos(x,y)
        self.bar.color("white","white")
        for i in range(0,3):
            self.new_bar.append(i)
            length = len(self.new_bar)
        #print(length)
        self.bar.turtlesize(stretch_wid=0.5,stretch_len=length,outline=1)  #barの大きさ長くする
    
    def move(self):
        self.bar.penup()

        while GAME_IS_ON:
            if self.bar.xcor() > 260:
                self.bar.speed("fastest")   #端に行ったときにくるっと回転するのが見えなくなる
                self.bar.setheading(180)
            elif self.bar.xcor() < -260:
                self.bar.speed("fastest")
                self.bar.setheading(0)
            self.bar.speed(5)
            self.bar.forward(20)   

    def right(self):
        self.bar.speed("fastest")   #端に行ったときにくるっと回転するのが見えなくなる
        self.bar.setheading(RIGHT)

    def left(self):
        self.bar.speed("fastest")   #端に行ったときにくるっと回転するのが見えなくなる
        self.bar.setheading(LEFT)
    
    def home(self):     #まだ使用していない
        GAME_IS_ON = False
        self.bar.home() 


class Bar2(Bar1):
    
    def __init__(self):
        super().__init__()
    
    def create_bar(self):
        super().create_bar()
        x = -260
        y = 260
        self.bar.penup()
        self.bar.goto(x,y)
    
    def move(self):
        super().move()

    def right(self):
        super().right()

    def left(self):
        super().left()
```

このあとやること---> それぞれのbarにfoodが衝突したら、入射角と同じ角度の反射角で跳ね返るfood.move()を作成する。
\#入射角に応じてfoodの反射角を.setheadingしてbarに当たるまで永遠にfoward()ループさせる処理。はまたその次。

#Multi Inheritance (多重継承)
__「複数のクラスからその機能を継承する」__こと。classAとclassBを元にclassCを作成するといったような感じ。今回、def move( ): を作成する上で、class Foodを作成するのにclass Turtleとclass Barを継承する必要ができてきたので、これを学習する。さまざまな理由から多重継承をサポートしない言語が増えている中、pythonは多重継承をサポートしている。

```python:multi-inheritance
class A:
    def hello(self):
        print('Hello from A')

class B(A):
    pass

class C(A):
    def hello(self):
        print('Hello from C')

class D(B, C):
    pass

d = D()
d.hello()

#出力結果
Hello from C
```

上記のように多重継承の場合は、継承先でメソッドを呼び出しても、どちらの親classのメソッドがcallされるかどうやって決まっているのだろう？という疑問が浮かぶ。以下に答えを見つけた。
>この順序はPython 3（およびPython 2.3以降）では「C3線形化」と呼ばれるアルゴリズムを用いて決定され、メソッド検索の中であるクラス（この場合はAクラス）が何度も登場することがないようになっている。
引用：https://www.atmarkit.co.jp/ait/articles/1908/27/news027.html