LoginSignup
2
1

More than 3 years have passed since last update.

変数で見るRubyとPythonの違い

Last updated at Posted at 2020-01-16

ローカル変数のスコープ

rubyではローカル変数の参照は定義されたスコープ内のみです。

ruby
a = 1

def func
  puts a
end

func #=> error (undefined local variable)

pythonでは子孫スコープからも参照できます。

python
a = 1

def func():
  print(a)

func() #=> 1

さらに、ローカル変数は定義されているスコープが消滅しても、子孫スコープなどから参照されている限り消去されません。この性質を応用してインスタンス変数を持つクラスのようなものを作ることもできます。このクラスもどきのインスタンスメソッドもどきをクロージャーと言います。

python
def cls(): # クラスもどき
  x = {} # インスタンス変数もどき

  def a(): # クロージャー
    x['a'] = 1

  def b(): # クロージャー
    x['b'] = 1

  def g(): # クロージャー
    return x

  return a, b, g

# xのスコープは関数呼び出し終了とともに消滅する
a, b, g = cls()
a(); b();
print(g()) #=> {'a': 1, 'b': 1}

定数クロージャー

rubyでも定数ならクロージャーが存在します。例を挙げる前に、少しだけrubyの説明をします。

rubyでは定数と変数は全く別の手法で管理されています。変数のスコープは関数定義(do構文等含まず)やクラス定義で変化します。一方、定数のスコープはモジュール定義(クラス定義含む)でのみ変化します。そして定数の探索は、

ネストが存在する場合、この定数はそのネストの要素の中で順に探索される
(定数の自動読み込みと再読み込みより)

という様に行われます(但しオープンクラスの場合は探索しない)。つまり、定数は子孫スコープからも参照できます。

この性質を使うと、クロージャーを作ることが出来ます。

ruby
module A
  X = 'x' # インスタンス変数もどき

  module B # クラスもどき
    def show # クロージャー
      puts X
    end
  end
end

class C # インスタンスもどき
  extend A::B
end

C.show #=> x

ローカル変数に見えるインスタンスメソッド

定義が見当たらないローカル変数への参照は、実は引数なしのインスタンスメソッド呼び出しかもしれません。本当によく出会います。

ruby
class C
  def hoge # インスタンスメソッド
    return "hoge"
  end

  def show
    fuga = "fuga"

    puts hoge # インスタンスメソッド呼び出し
    puts fuga # ローカル変数参照
  end
end

さらに、クラスマクロのattr_accessor、attr_reader、attr_writerを使えば、ローカル変数に見えるインスタンスメソッドを簡単に作れます。変数の実体は、引数のシンボルに@を前置した名前のインスタンス変数です。

ruby
class C
  attr_accessor :hoge # インスタンス変数@hogeが実体

  def initialize
    self.hoge = "hoge" # インスタンスメソッドhoge=を呼び出し
  end

  def show
    fuga = "fuga" # ローカル変数fugaを定義&初期化

    puts hoge # インスタンスメソッドhogeを呼び出し
    puts fuga # ローカル変数fugaを参照
  end
end

C.new.methods.include?(:hoge) #=> true
C.new.methods.include?(:hoge=) #=> true

pythonの場合は、インスタンスメソッド呼び出しにselfが付いていたり、関数呼び出しの括弧が省略できなかったりするので、簡単に見分けられます。それよりも、インスタンスメソッド呼び出しとクラスメソッド呼び出しを区別するのが大変です。

python
class C:
  def hoge(self):
    return "hoge"

  @classmethod
  def piyo(cls):
    return "piyo"

  def show(self):
    fuga = "fuga"

    print(self.hoge()) # インスタンスメソッド呼び出し
    print(fuga) # ローカル変数参照

    print(self.piyo()) # クラスメソッド呼び出し

ローカル変数による上書き

(scivola様のコメントを元に追加しました)

先ほどの例でインスタンスメソッドhoge=を呼び出す際、selfをレシーバーとして明示しました。

ruby(再掲)
class C
  attr_accessor :hoge

  def initialize
    self.hoge = "hoge" # ここ
  end
  ...

これは、selfを明示しないとローカル変数hogeの定義として扱われるからです。

ruby
class C
  attr_accessor :hoge

  def initialize
    hoge = "hoge" # ローカル変数hogeの定義
  end

  def show
    puts hoge
  end
end

C.new.show #=> nil

同じことが、pythonでも起こり得ます。例えば、先ほど紹介したクラスもどきの場合、インスタンス変数もどきは更新できません。

python
def cls():
  x = {} # インスタンス変数的振る舞いをする

  def u():
    x = 'updated' # 実はローカル変数xの定義

  def g():
    return x

  return u, g

# xのスコープは関数呼び出し終了とともに消滅する
u, g = cls()
u();
print(g()) #=> {}

pythonではselfを省略できないので、インスタンス変数の更新の際に間違うことはないでしょう。

python
class C:
  def __init__(self):
    self.hoge = "not updated"

  def wrong(self):
    hoge = "updated" # 間違いに気付き易い

  def correct(self):
    self.hoge = "updated"

  def show(self):
    #print(hoge) #=> error (name 'hoge' is not defined)
    print(self.hoge)

c = C()
c.wrong(); c.show(); #=> not updated
c.correct(); c.show(); #=> updated

include先のインスタンス変数を参照

ruby
module M
  def show
    puts @message
  end
end

class C
  include M
  def initialize
    @message = "accessible"
  end
end

C.new.show #=> accessible

pythonでも似たことが出来ます。

python
class S:
  def show(self):
    print(self.message)

class C(S):
  def __init__(self):
    self.message = "accessible"

C().show() #=> accessible

関数を変数に代入

rubyでは関数呼び出しの括弧を省略できます。そのため、関数を変数に代入することは出来ません。

ruby
def func
  return "called"
end

a = func # 関数funcの呼び出し
puts a #=> called

Object#methodインスタンスメソッドを使って、関数をMethodオブジェクトに変換すると、変数に代入できます。

ruby
def func
  return "called"
end

a = method(:func) # Methodオブジェクトの代入
puts a #=> #<Method: main.func>
puts a.call #=> called

一方、pythonでは関数を変数に代入できます。

python
def func():
  return "called"

a = func # 関数funcの代入
print(func) #=> <function func at 0x...>
2
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1