selfをめぐる冒険

Japan.R 2025

@igjit

@igjit

RユーザーもPythonを書く

たまにPythonを書くときに思うこと

メソッド定義の仮引数にselfって書くのはなぜ

class Counter:
    def __init__(self):
        self.n = 0
    
    def increase(self):
        self.n += 1
        return self.n


counter = Counter()
counter.increase()
1

Rだとどうか

Rではオブジェクトシステムを選べる

  • base R
    • S3
    • S4
    • RC (reference class)
  • CRAN package
    • R6
    • R.oo
    • proto
    • S7

R6の場合

Counter <- R6::R6Class("Counter", list(
  n = 0,
  increase = function() {
    self$n <- self$n + 1
    self$n
  })
)


counter <- Counter$new()
counter$increase()
[1] 1

Python

class Counter:
    def __init__(self):
        self.n = 0
    
    def increase(self):
        self.n += 1
        return self.n

R (R6)

Counter <- R6::R6Class("Counter", list(
  n = 0,
  increase = function() {
    self$n <- self$n + 1
    self$n
  })
)

Rでは

R6パッケージがselfに特別な意味を持たせている

Pythonでは

インスタンスオブジェクトが関数の第1引数として渡されます。
x.f()という呼び出しは、MyClass.f(x)と厳密に等価なものです。

selfという名前は、Pythonでは何ら特殊な意味を持ちません。

https://docs.python.org/ja/3/tutorial/classes.html

Pythonのselfは

単なる慣習

つまりこれは

class Counter:
    def __init__(self):
        self.n = 0
    
    def increase(self):
        self.n += 1
        return self.n

こうしても動く

class Counter:
    def __init__(foo):
        foo.n = 0
    
    def increase(bar):
        bar.n += 1
        return bar.n

(こんなコードは書くべきではない)

なにそれすごい

Rでもやりたい

やってみた。

rm1

https://github.com/igjit/rm1


rm1は “R minus 1” の意

インストール

# install.packages("pak")
pak::pak("igjit/rm1")

あそびかた

パッケージを読み込んだら

library(rm1)

こんな感じで書ける

Counter <- rm1_class(list(
  .init = function(self) {
      self$n <- 0
  },
  increase = function(self) {
      self$n <- self$n + 1
      self$n
  }))


counter <- Counter()
counter$increase()
[1] 1

もちろんselfじゃなくても動く

CounterB <- rm1_class(list(
  .init = function(foo) {
      foo$n <- 0
  },
  increase = function(bar) {
      bar$n <- bar$n + 1
      bar$n
  }))


counter_b <- CounterB()
counter_b$increase()
[1] 1

だいぶPythonっぽくなった

Python

class Counter:
    def __init__(self):
        self.n = 0
    
    def increase(self):
        self.n += 1
        return self.n

R (igjit/rm1)

Counter <- rm1_class(list(
  .init = function(self) {
      self$n <- 0
  },
  increase = function(self) {
      self$n <- self$n + 1
      self$n
  }))

実装

実装は簡単。

今回紹介した機能はこれで動く。

rm1_class <- function(methods) {
  function(...) {
    self <- new.env()
    for (name in names(methods)) {
      assign(name, function(...) methods[[name]](self, ...), self)
    }

    methods$.init(self, ...)
    self
  }
}

念のため言っておくと

実用性は全く無い

単に作るのが楽しいから作っている。

作って動かして理解する

まとめ

Pythonのselfは慣習

オブジェクトシステムは自作できる

プログラミングは楽しい

Enjoy!