.NETクラスからの継承

日記で何度かふれたことがありますが、IronPythonのクラスは.NETで言うところの型ではなく、IronPython独自のクラスになっています(恐らくPython同様の実装)。

import sys

class Spam:
    def Hi(self):
        print 'Spam'

class Eggs:
    def Say(self):
        print 'Eggs'

s = Spam ()
s.Hi ()

e = Eggs ()
e.Say ()

# Pythonは多重継承もサポートしている
class SpamEggs(Spam, Eggs):
    pass

se = SpamEggs ()
se.Say ()
se.Hi ()

# インスタンスにNameメンバを追加してみる
se.Name = 'Python' 

# クラスにメソッドを追加してみる
SpamEggs.Goodby = lambda self: sys.stdout.write ('Goodby ' + self.Name + '\n')

# 呼び出してみる
se.Goodby ()

# 結果
# Spam
# Eggs
# Eggs
# Spam
# Goodby Python

実行時にメンバを追加できたりと、.NETの型には無い柔軟性があります。勿論、これだけだと.NETライブラリが呼べるだけのPythonに過ぎません。IronPythonでは.NETの型から継承してPythonのクラスを作成することも出来ます。ということで、C#アセンブリを作ってIronPythonで継承してみましょう。

using System;

namespace Spam {
    public class Foo {
        public void Say () {
            Console.WriteLine ("Say Foo.");
        }
    }

    public sealed class SealedFoo {
        public void Say () {
            Console.WriteLine ("Say SealedFoo.");
        }
    }
}

面白くもなんともないコードですが、これをコンパイルしてFoo.dllとします。では、IronPythonから呼んでみることに。

import clr
# Foo.dllを参照する
clr.AddReference('Foo.dll')

# Spam名前空間から全ての型をインポート
from Spam import *

foo = Foo ()
foo.Say ()

sfoo = SealedFoo ()
sfoo.Say ()

# .NET型から継承可能
class MyFoo (Foo):
    pass

myfoo = MyFoo ()
myfoo.Say ()

# sealed型は不可
# class MySealedFoo (SealedFoo):
#    pass

class Bar:
    pass

# ベースクラスがPythonクラスでなければ多重継承は不可
# この場合Fooが.NET型なので駄目
# class FooBar(Foo, Bar):
#   pass

# 結果
# Say Foo.
# Say SealedFoo.
# Say Foo.

多重継承が出来ないなどの多少の制限がありますが、問題なく使えますね。

ところで、上の例にわざわざsealedを入れたのにはわけがあって、

class MyString(String):
  pass

このようにsealedであるはずのString型から継承出来てしまうんですよね。この違いが分からなくて調査しました。

実は、Stringから継承出来たように見えるのですがStringのメンバにアクセスするとエラーになります。色々弄ってみてなんとなく分かったんですが、組込型は.NET型とPython独自型から多重継承したような型みたいです。String型をimportしなければ、文字列型はPython文字列としてのメンバしか持ちません。ところが、String型をimportした瞬間、.NET型のメンバが含まれます。で、先ほどのStringからの継承はPython文字列型からの継承を意味していて、Python文字列型のメンバにアクセスする分には正常に動作します。