Pythonでインスタンスのクラスを強制的に変えてみる

オブジェクト指向っていいですよね。
特に継承によって機能を拡張できるのが好みです。

## クラス継承

Pythonにおけるクラス継承を使った例として以下のようなプログラムを作りました。

```python
class Syain():
    def __init__(self, name = ''):
        self.name = name

    def syukkin(self):
        print(f'{self.name}が出勤しました。')

    def taikin(self):
        print(f'{self.name}が退勤しました。')

    def nanoru(self):
        print(f'{self.name}と申します。')

class Kanbu(Syain): #Syainクラスを継承
    def __init__(self, name = '', yakusyoku = ''):
        super().__init__(name) #super()は親のSyainクラスを示す
        self.yakusyoku = yakusyoku

    def meirei(self, buka):
        print(f'{self.name}は{buka.name}に命令しました。')

    def nanoru(self): #Syainクラスのnanoruメソッドをオーバーライド
        print(f'{self.yakusyoku}の{self.name}と申します。')
```

Syainクラスは社員を表します。
出勤と退勤することと、自分の名前を名乗るメソッドを持ちます。

Kanbuクラスは幹部を表します。
幹部も社員ですのでSyainクラスを継承することで同じメソッドを持ちます。
また、幹部だけの機能として社員に命令を下すことができます。
幹部は名乗るときには役職名を付けて名乗ります。


さて、こいつを普通に使うならば以下のようになるでしょう。

```python
class Syain():
    def __init__(self, name = ''):
        self.name = name

    def syukkin(self):
        print(f'{self.name}が出勤しました。')

    def taikin(self):
        print(f'{self.name}が退勤しました。')

    def nanoru(self):
        print(f'{self.name}と申します。')


class Kanbu(Syain):
    def __init__(self, name = '', yakusyoku = ''):
        super().__init__(name)
        self.yakusyoku = yakusyoku

    def meirei(self, buka):
        print(f'{self.name}は{buka.name}に命令しました。')

    def nanoru(self):
        print(f'{self.yakusyoku}の{self.name}と申します。')

suzuki = Kanbu('suzuki', '課長')
tanaka = Syain('tanaka')

suzuki.syukkin()
tanaka.syukkin()

suzuki.nanoru()
tanaka.nanoru()

suzuki.taikin()
tanaka.taikin()
```

```:実行結果
suzukiが出勤しました。
tanakaが出勤しました。
課長のsuzukiと申します。
tanakaと申します。
suzukiが退勤しました。
tanakaが退勤しました。
```

課長（幹部）である鈴木さんはKanbuクラスのインスタンスとして、
田中さんはSyainクラスのインスタンスとして、
それぞれコンストラクタを呼び出します。

名乗らせてみると、鈴木さんは肩書き込みで名乗ってますね。

## インスタンスのクラス名を確認

さて、鈴木さんや田中さんもただの社員なのか幹部なのかはどうやって確認しましょう？
所属するクラス名を表示するには以下のようにします。

```python
print(suzuki.__class__)
print(tanaka.__class__)
```

```:実行結果
<class '__main__.Kanbu'>
<class '__main__.Syain'>
```

## \_\_class\_\_って何？

pythonにおいて\_\_xxx\_\_という形式で定義されているのは特殊メソッドや特殊フィールドと呼ばれるもので、
「\_\_class\_\_」はそのオブジェクトのクラス情報を表す特殊フィールドです。

そして、**<u>この特殊フィールド、書き換えることが可能なんです</u>**

```python
suzuki.Syain('suzuki')
print(suzuki.__class__)
suzuki.__class__ = Kanbu().__class__
print(suzuki.__class__)
```

```:実行結果
<class '__main__.Syain'>
<class '__main__.Kanbu'>
```

言語仕様としてどうなの？という気がしないでもないですが、
せっかく面白い仕様なので何かに使ってみたいです。

## 悪用

ということで、これを悪用すると以下のようなことができます。

```python
class Syain():
    def __init__(self, name = ''):
        self.name = name

    def syukkin(self):
        print(f'{self.name}が出勤しました。')

    def taikin(self):
        print(f'{self.name}が退勤しました。')

    def nanoru(self):
        print(f'{self.name}と申します。')


class Kanbu(Syain):
    def __init__(self, name = '', yakusyoku = ''):
        super().__init__(name)
        self.yakusyoku = yakusyoku

    def meirei(self, buka):
        print(f'{self.name}は{buka.name}に命令しました。')

    def nanoru(self):
        print(f'{self.yakusyoku}の{self.name}と申します。')

#鈴木さんも田中さんもSyainクラスのインスタンスとして作成
suzuki = Syain('suzuki')
tanaka = Syain('tanaka')

#鈴木さんのクラス情報を上書き
suzuki.__class__ = Kanbu().__class__
suzuki.meirei(tanaka)
```

```:実行結果
suzukiはtanakaに命令しました。
```

Syainクラスのインスタンスであった鈴木さんが、命令できています。
途中でクラス情報を書き換えたためです。

イメージとしては、
社員の鈴木さんが幹部代理として権限を持ったような感じでしょうか。

## 定義していないフィールドは当然存在しない

無理やり幹部の権限を持たせた鈴木さんですが、
役職名はありませんので幹部として名乗らせようとするとエラーになります。

```python
suzuki.nanoru()
```

```:実行結果
AttributeError: 'Kanbu' object has no attribute 'yakusyoku'
```

当然と言えば当然ですね。