私は最近、Python クラスと R S4 クラスの比較を書きました。
http://practicalcomputing.org/node/80
R と python では、クラスの宣言方法、使用方法、動作方法の両方において、クラスが大きく異なります。
コメントでの mbinette の要求に従って、投稿の全文を以下に示します (私は 2 つの権限しか持っていないため、ほとんどのハイパーリンクを除いています)。
Python、C++、Java、またはその他の一般的なオブジェクト指向言語でプログラミングしたことがある人にとって、R でのオブジェクト指向プログラミングは非常に混乱する可能性があります。本書の Rosetta コード例の精神に従い、Python でクラスを作成して使用するコードと、R でクラスを作成して使用するコードを比較します。
混乱の最初の層は、R がオブジェクト指向プログラミング用にいくつかの異なるシステム (S3、S4、および R5) を持っていることです。直面する最初の決定は、これらのどれをプロジェクトに選択するかです。S3 は最も長く使用されており、広く使用されています。その機能はいくつかの重要な点で制限されていますが、プログラマーはクラスのコーディング方法にかなりの柔軟性を持っています。新しいシステムである S4 は、S3 のいくつかの制限に対処しています。コードは少し複雑で厳格ですが、最終的にはより強力に使用できます。一般に、S3 オブジェクトが既に存在する既存のコードで作業する場合は S3 を使用し、新しいコードをゼロから実装する場合は S4 を使用します。たとえば、多くの新しいバイオコンダクタ パッケージは、S4 で記述されています。Hadley Wickham は、 S3、S4、および R5の優れた要約を持っています。、R の他の側面の中でも特に、R でのオブジェクト指向プログラミングについて学習するのに最適な場所です。
ここでは、S4 システムに焦点を当てます。
以下は、Python での単純な Circle クラスの定義です。新しいインスタンスが作成されるときに値を設定するための__init__()
コンストラクター メソッド、値を設定するためのいくつかのメソッド、値を取得するためのいくつかのメソッド、および半径から直径を計算してクラス インスタンスを変更するためのメソッドがあります。
class Circle:
## Contents
radius = None
diameter = None
## Methods
# Constructor for creating new instances
def __init__(self, r):
self.radius = r
# Value setting methods
def setradius(self, r):
self.radius = r
def setdiameter(self, d):
self.diameter = d
# Value getting methods
def getradius(self):
return(self.radius)
def getdiameter(self):
return(self.diameter)
# Method that alters a value
def calc_diameter(self):
self.diameter = 2 * self.radius
このクラスを作成したら、インスタンスを (ipython で) 作成して使用すると、次のようになります。
In [3]: c = Circle()
In [4]: c.setradius(2)
In [5]: c.calc_diameter()
In [6]: c.getradius()
Out[6]: 2
In [7]: c.getdiameter()
Out[7]: 4
この関数は、 で定義されたコンストラクターを使用してCircle()
、クラスの新しいインスタンスを作成します。メソッドを使用して半径値を設定し、メソッドを使用して半径から直径を計算し、クラス インスタンスの直径値を更新します。次に、作成したメソッドを使用して、半径と直径の値を取得します。もちろん、関数の呼び出しに使用したのと同じドット表記を使用して、半径と直径の値に直接アクセスすることもできます。Circle
__init__()
.setradius()
.calc_diameter()
In [8]: c.radius
Out[8]: 2
In [9]: c.diameter
Out[9]: 4
C++、Java、およびその他の多くの一般的な言語と同様に、メソッドとデータ変数の両方がクラスの属性です。さらに、メソッドには、データ属性への直接の読み取りおよび書き込みアクセスがあります。この場合、.calc_diameter()
メソッドは直径の値を新しい値に置き換えましたが、クラス インスタンスに関して他に何も変更する必要はありません。
次に、R の S4 オブジェクトについて説明します。これは非常に大きく異なります。R の同様の Circle クラスを次に示します。
setClass(
Class = "Circle",
representation = representation(
radius = "numeric",
diameter = "numeric"
),
)
# Value setting methods
# Note that the second argument to a function that is defined with setReplaceMethod() must be named value
setGeneric("radius<-", function(self, value) standardGeneric("radius<-"))
setReplaceMethod("radius",
"Circle",
function(self, value) {
self@radius <- value
self
}
)
setGeneric("diameter<-", function(self, value) standardGeneric("diameter<-"))
setReplaceMethod("diameter",
"Circle",
function(self, value) {
self@diameter <- value
self
}
)
# Value getting methods
setGeneric("radius", function(self) standardGeneric("radius"))
setMethod("radius",
signature(self = "Circle"),
function(self) {
self@radius
}
)
setGeneric("diameter", function(self) standardGeneric("diameter"))
setMethod("diameter",
signature(self = "Circle"),
function(self) {
self@diameter
}
)
# Method that calculates one value from another
setGeneric("calc_diameter", function(self) { standardGeneric("calc_diameter")})
setMethod("calc_diameter",
signature(self = "Circle"),
function(self) {
self@diameter <- self@radius * 2
self
}
)
このクラスを作成したら、(R 対話型コンソールで) インスタンスを作成して使用すると、次のようになります。
> a <- new("Circle")
> radius(a) <- 2
> a <- calc_diameter(a)
> radius(a)
[1] 2
> diameter(a)
[1] 4
このnew("Circle")
呼び出しにより、クラスの新しいインスタンスが作成されCircle
、それが という変数に割り当てられましたa
。このradius(a)<- 2
行は、オブジェクト a のコピーを作成し、radius の値を 2 に更新してから、a が新しく更新されたオブジェクトを指すようにしました。これは、radius<-
上で定義した方法で達成されました。
calc_diameter()
クラスのメソッドとして定義しましたCircle
が、クラスの属性であるかのように呼び出さないことに注意してください。つまり、 のような構文は使用しませんa.calc_diameter()
。代わりに、calc_diameter()
他のスタンドアロン関数と同じように呼び出し、オブジェクトを最初の引数としてメソッドに渡します。
さらに、 を呼び出すだけでなくcalc_diameter(a)
、出力を に割り当てましたa
。これは、R のオブジェクトが参照ではなく値として関数に渡されるためです。この関数は、元のオブジェクトではなく、オブジェクトのコピーを取得します。そのコピーは関数内で操作されます。変更されたオブジェクトを元に戻したい場合は、2 つのことを行う必要があります。まず、関数の最終行でオブジェクトを実行する必要があります (そのためself
、メソッド定義の行が孤立しています)。R では、これは を呼び出すようなものreturn()
です。次に、メソッドを呼び出すときに、更新された値をオブジェクト変数にコピーする必要があります。これが、フルラインがa <- calc_diameter(a)
.
radius(a)
とのdiameter(a)
呼び出しは、これらの値を返すために定義したメソッドを実行します。
Python のオブジェクトと同じように、R のオブジェクトのデータ属性に直接アクセスすることもできます。ただし、ドット表記を使用する代わりに、次の@
表記を使用します。
> a@radius
[1] 2
> a@diameter
[1] 4
R では、データ属性は「スロット」と呼ばれます。この@
構文により、これらのデータ属性にアクセスできます。しかし、メソッドはどうですか?Python とは異なり、R のメソッドはオブジェクトの属性ではなく、setMethod()
特定のオブジェクトに作用するように定義されています。メソッドが作用するクラスは、signature
引数によって決定されます。ただし、同じ名前の複数のメソッドが存在する可能性があり、それぞれが異なるクラスに作用します。これは、呼び出されるメソッドがメソッドの名前だけでなく、引数の型にも依存するためです。おなじみの例はメソッドplot()
です。ユーザーには 1 つの関数のように見えますplot()
が、実際にはplot()
、特定のクラスに固有のメソッドが多数存在します。plot()
.
これによりsetGeneric()
、クラス定義の行に到達します。既存の名前 ( などplot()
) を使用して新しいメソッドを定義する場合は、その必要はありません。これはsetMethod()
、既存のメソッドの新しいバージョンを定義するためです。新しいバージョンは、同じ名前の既存のバージョンとは異なるデータ型のセットを取ります。ただし、新しい名前で関数を定義する場合は、最初に関数を宣言する必要があります。setGeneric()
はこの宣言を処理し、本質的にプレースホルダーを作成します。これをすぐにオーバーライドします。
Python と R のクラスの違いは表面的なものだけではなく、内部で非常に異なることが起こっており、各言語でクラスが異なる方法で使用されています。ただし、R でクラスを作成して使用する際に特にイライラすることがいくつかあります。R で S4 クラスを作成するには、より多くの入力が必要であり、その多くは冗長です (たとえば、上記の例では、各メソッド名を 3 回指定する必要がありました)。R メソッドは、オブジェクト全体のコピーを作成することによってのみデータ属性にアクセスできるため、オブジェクトが大きくなると、単純な操作でも大きなパフォーマンス ヒットが発生する可能性があります。この問題は、オブジェクトを変更するメソッドの場合に複雑になります。データは、途中で 1 回コピーし、次に途中で 1 回コピーする必要があるためです。これらの問題は、pandas などの数値解析用の Python ツールの人気が最近急速に高まっている一因であると考えられます。とはいえ、R は依然として強力なツールであり、多くの一般的な問題に適しています。R ライブラリの豊富なエコシステムは多くの分析に不可欠です。