Python でクラスキャスト

cast 関数

def cast(ParentalClass, child_object):
  child_object.__class__ = ParentalClass

◯ 動作確認と使用例

__class__ への代入の動作についてマニュアルの文言を見つけられなかったので、実際に触って期待した動作をするか動作確認しました。

>>> class Person():
...   id = 1
...   age = 30
...   name = 'Yaruo'
... 
>>> 
>>> class Student(Person):
...   id = 100
...   grade = 'junior'
... 
>>> 
>>> p = Person()
>>>
>>> id(p)
4415976168
>>> p.id = 2
>>> p.age = 31
>>>
>>>
>>> # キャスト
>>> cast(Student, p)
>>> 
>>> # 1) キャストしても
>>> # id は変化しない
>>> id(p)
4415976168
>>>
>>> # 2) キャストすると
>>> # 属性が追加されている。
>>> p.grade
'junior'
>>>
>>> # 3) キャストしても
>>> # もともとあった属性は変更されない。
>>> p.id
2
>>> p.age
31

◯ なんでこんな動作をするの?

インスタンスオブジェクトは、下図のように属性 __class__ にクラスオブジェクトへの参照が代入されています。

この属性 __class__ に別のクラスを代入して、型を切り替えているという訳です。
Python で定義前の変数、関数、クラスの参照するときは...
f:id:domodomodomo:20170406193028j:plain

◯ __class__ への代入条件

よくわかりませんが、一応、条件があるようです。

__class__ への代入は、両方のクラスが同じ __slots__ を持っているときのみ動作します。

3.3.2.3.1. __slots__ を利用する際の注意

◯ __*__ への代入について

一般に動作を保証されていません。何故なら __*__ への代入は、マニュアルで記載されている通り動作の保証がなされていないからです。

ただし、本稿では上記のマニュアルの「動作する」という文言を持って、動作するという判断をしています。

このドキュメントで明記されている用法に従わない、 あらゆる __*__ の名前は、いかなる文脈における利用でも、警告無く損害を引き起こすことがあります。

2. 字句解析 — Python 3.6.3 ドキュメント

copy_parental_value 関数

新たに別のオブジェクトが生成されるので id は同じではありません。

# cast っぽいメソッド
def copy_parental_value(child_obj, parental_obj):
  for attr_name in parental_obj.__dict__:
    setattr(child_obj, 
            attr_name,
            getattr(parental_obj, attr_name))
  return child_obj


# 動作例
# 親クラスと子クラスを作成
class ParentalClass():
  parental_attr = ""

class ChildClass(ParentalClass):
  def __init__(self, child_attr):
    self.child_attr = child_attr

parental_object = ParentalClass()
parental_object.parental_attr = "hello"

# 継承
child_object = copy_parental_value(
    ChildClass("Yaruo"), parental_object)
print(child_object.parental_attr)
print(child_object.child_attr)

そのほか

◯ 調べたこと

組み込み関数としてクラスキャストをサポートするものはなさそう。

◯ 検討したこと

__init__ で定義してあげると、もっと自然な方法になると思うのですが、例えば、つぎのような具合に。

child_object = ChildClass(parent_object)

python - How to convert (inherit) parent to child class? - Stack Overflow
 

ただし、それは、つぎの2つの理由から却下しました(。-`ω-)ンー
1) 本来の目的で __init___ が使えなくなる。(例えば、Django の models.Model クラスを継承したクラスには適用できません。)
2) クラスごとに __init__ を定義しないといけない。
python - Writing a __init__ function to be used in django model - Stack Overflow

でも組み込みの str, int クラス(型)は、そのような実装をしています。Python多態性はどうやって実装するんやろか。__init__ で場合分けさせてるのでしょうか... ちなみに Python においてクラス class と型 type は同義です。

>>> # 整数 -> 文字列
>>> str(1)
'1'
>>> # 文字列 -> 整数
>>> int('1')
1
>>> # 浮動小数 -> 整数
>>> int(0.1)
0