はじめに
移植やってます。
( from python 3.7 to ruby 2.7 )
多重継承 (Python)
前回の記事のコメントにて、@Nabetani さんよりRuby3.1
ではクラス変数のオーバーライドがエラーになることを教えていただきました。
親クラスに、子クラスで既に定義されている同名のクラス変数を追加した場合、子クラスが、そのクラス変数を参照した際に例外 RuntimeError が発生します。
また、クラス変数について興味深い記事を投稿されています。
失敗(Ruby)
module Base
def __init__
@name = 'Base'
@base = 'Base'
puts 'Base'
end
end
module C4
include Base
def __init__
@name = 'C4'
puts 'C4'
super
end
end
module C2
include C4
def __init__
@name = 'C2'
puts 'C2'
super
end
end
module C3
include Base
def __init__
@name = 'C3'
puts 'C3'
super
end
end
class C1
include C2, C3
attr_reader :name, :base
def initialize
__init__
end
def __init__
@name = 'C1'
puts 'C1'
super
end
end
c = C1.new
p [c.name, c.base]
p C1.ancestors
# output
C1
C2
C4
C3
Base
["Base", "Base"]
[C1, C2, C4, C3, Base, Object, Kernel, BasicObject]
継承チェーンでBase
がC3
の後から呼ばれています、よしよし。
クラス変数からインスタンス変数に替えましたが、@name
が継承元で'Base'
に書き換えられています、ぐぬぬ。
成功(Ruby)
module Base
def __init__
@name = 'Base'
@base = 'Base'
puts 'Base'
end
end
module C4
include Base
def __init__
puts 'C4'
super
@name = 'C4'
end
end
module C2
include C4
def __init__
puts 'C2'
super
@name = 'C2'
end
end
module C3
include Base
def __init__
puts 'C3'
super
@name = 'C3'
end
end
class C1
include C2, C3
attr_reader :name, :base
def initialize
__init__
end
def __init__
puts 'C1'
super
@name = 'C1'
end
end
c = C1.new
p [c.name, c.base]
p C1.ancestors
# output
C1
C2
C4
C3
Base
["C1", "Base"]
[C1, C2, C4, C3, Base, Object, Kernel, BasicObject]
super
の後でインスタンス変数を初期化することにより、正しいオーバーライドになるようです。
継承の実験
module C4
include Base
def __init__
puts 'C4'
- super
@name = 'C4'
end
end
# output
C1
C2
C4
["C1", nil]
[C1, C2, C4, C3, Base, Object, Kernel, BasicObject]
仮に、成功(Ruby)のコードで、module C4
のsuper
を削除した場合、継承チェーンのC4
の次のC3
のdef __init__
が呼ばれないためputs 'C3'
が実行されないことが分かります。
また、その結果Base
のdef __init__
も実行されないため、@base
が初期化されていないこともわかります。
追記 成功(Ruby nilガード)
module Base
def __init__
puts 'Base'
@name ||= 'Base'
@base ||= 'Base'
end
end
module C4
include Base
def __init__
puts 'C4'
@base ||= 'newBase'
super
end
end
module C2
include C4
def __init__
puts 'C2'
super
end
end
module C3
include Base
def __init__
puts 'C3'
super
end
end
class C1
include C2, C3
attr_reader :name, :base
def initialize(name = nil)
@name = name || 'C1'
__init__
end
def __init__
puts 'C1'
super
end
end
c = C1.new("newC1")
p [c.name, c.base]
p C1.ancestors
# output
C1
C2
C4
C3
Base
["newC1", "newBase"]
[C1, C2, C4, C3, Base, Object, Kernel, BasicObject]
お馴染みnilガード ||=
をmodule
内で使用してみました。
これだとsuper
のタイミングを気にしなくてよさそう。
メモ
- Python の 多重継承の6 を学習した
- 百里を行く者は九十里を半ばとす