概要
@name
とself.name
の挙動の違いをまとめます。
初学者の方向けの記事です。
以下のようなコードをたまに見かけることがありました。
class User
attr_reader :name
def initialize(name)
@name = name
end
def introduce
puts "#{name}です" #@がなくても参照できてる!
end
end
user = User.new ("川上")
user.introduce
川上です
「introduceメソッドの中のname
はローカル変数なの??@
が付いてないのに,なぜ値が入っているの?」
と思ってた謎が解けたので,まとめます。
結論
上記の"#{name}です"
は,実は"#{self.name}です"
の略。
self.name
は,値を参照するname
メソッド(ゲッターメソッド)を呼び出している。
name
メソッド(ゲッターメソッド)の戻り値は@name
なので,initializeメソッドで@nameに格納しておいた値が返る。
(なんのことかサッパリですよね^^; 詳しくは後述します)
それに対し,@nameはクラスの中で共有されるインスタンス変数。
同じ挙動をするように見えるけど,全くの別物。
@name(インスタンス変数)を使用する書き方
まず,よく見るインスタンス変数を使った書き方から見てみます。
class User
def initialize(name)
@name = name
end
def introduce
puts "#{@name}です"
end
end
user = User.new ("川上")
user.introduce
川上です
インスタンスを生成した際,initializeメソッド内で@nameに実引数の値川上
がセットされます。
クラスの中ではインスタンス変数の値が保持されるので,introduceメソッド内の@nameの値も川上
です。
こちらは問題なさそうです。
self.nameを使用する書き方
class User
attr_accessor :name
def initialize(name)
self.name = name
end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
川上です
introduceメソッドの中だけでなく,initializeメソッドの中もself.name
となっています。
すっかり忘れていたのですが,ProgateのRubyの演習ではこの形式で書かれているそうです。
(教えていただき見直してみると,確かにそうなっています。※2021年4月26日時点)
ここで注意したいのが,さりげなく**attr_accessor :name
**と書かれている点です。
実は,この記述がない場合はエラーになってしまうんです。
もし,attr_accessor :name
がなかったらどうなるか試してみます。
class User
# attr_accessor :name # コメントアウト
def initialize(name)
self.name = name
end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
実行してみると,以下のエラーが出ました。(2つのうちの1つ目)
Traceback (most recent call last):
2: from user.rb:12:in `<main>'
1: from user.rb:12:in `new'
user.rb:4:in `initialize': undefined method `name=' for #<User:0x00007f7eec13c348> (NoMethodError)
name=
メソッドが定義されていない,という内容ですね!
self.name = はname=メソッド(セッターメソッド)を呼び出している
では,name=
メソッドを追加してみましょう。
ちなみにname=
メソッド(セッターメソッド)は何かというと,@name
に値をセットするメソッドです。
class User
def initialize(name)
self.name = name # 実はセッターメソッドを呼び出している
end
# セッターメソッドを追加
def name=(name)
@name = name
end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
これでもう一度実行すると,上記のエラーに関してはなくなります。
実は,initializeメソッドの中のself.name =
はname=
メソッド(セッターメソッド)を呼び出しています。
(self.name =
,name=
と両方イコールがついている点に注意してください)
name=
メソッドが定義されていないと,呼び出すことができないのでエラーになっていました。
self.name =
がname
(川上
という値が入った変数)を実引数としてname=
メソッドを呼び出す
→@name
に川上
という値がセットされる,という流れです。
では,この状態でもう一度実行してみましょう。
すると,次は以下のエラー(2つ目のエラー)が出ます。
Traceback (most recent call last):
1: from user.rb:17:in `<main>'
user.rb:12:in `introduce': undefined method `name' for #<User:0x00007fbcf209ff90 @name="川上"> (NoMethodError)
Did you mean? name=
今度はname
メソッドが定義されていない,という内容です。
self.nameはnameメソッド(ゲッターメソッド)を呼び出している
では,name
メソッド(ゲッターメソッド)を定義してみましょう。
name
メソッド(ゲッターメソッド)は@name
を戻り値とするメソッドです。
class User
def initialize(name)
self.name = name # 実はセッターメソッドを呼び出している
end
# セッターメソッド
def name=(name)
@name = name
end
# ゲッターメソッドを追加
def name
@name #戻り値
end
def introduce
puts "#{self.name}です" # 実はゲッターメソッドを呼び出している
end
end
user = User.new ("川上")
user.introduce
これでエラーはなくなります。
川上です
introduceメソッドの中のself.name
はname
メソッド(ゲッターメソッド)を呼び出しています。
(こちらはイコールなしです)
こちらも,メソッドが定義されていなかったので,呼び出すことができずエラーになっていました。
self.name
がname
メソッド(ゲッターメソッド)を呼び出す
→@nameに格納されていた川上
という値が戻り値としてself.name
に入る,という流れです。
セッターとゲッターをあわせてattr_accesorと書き換えることができる
まず,セッターメソッドは以下のように書き換えることができます。(class User
の直下)
class User
attr_writer :name # セッターメソッドを呼び出す
def initialize(name)
self.name = name
end
# セッターメソッド
# def name=(name)
# @name = name
# end
# ゲッターメソッド
def name
@name
end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
(追記:コメントで教えていただいたので,「# 裏でセッターメソッドを呼び出す」という表記から「# セッターメソッドを呼び出す」という表記に変更いたしました!次のゲッターメソッドの書き換えについても同様です)
そして,ゲッターーメソッドは以下のように書くことができます。
class User
attr_writer :name # セッターメソッドを呼び出す
attr_reader :name # ゲッターメソッドを呼び出す
def initialize(name)
self.name = name
end
# セッターメソッド
# def name=(name)
# @name = name
# end
# ゲッターメソッド
# def name
# @name
# end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
attr_writer :name
とattr_reader :name
をあわせてattr_accessor :name
と書くことができます。
これが「self.nameを使用したコード」に出てきた書き方になります。
class User
attr_accessor :name # セッターとゲッターを呼び出す
def initialize(name)
self.name = name
end
# セッターメソッド
# def name=(name)
# @name = name
# end
# ゲッターメソッド
# def name
# @name
# end
def introduce
puts "#{self.name}です"
end
end
user = User.new ("川上")
user.introduce
川上です
【補足1】selfはインスタンス自身を表している
では,「このself
って何?」というところなんですが,self
はuser
というインスタンスを表しています。
クラスの外でインスタンスメソッドを呼び出す際,例えば
user.introduce
というように,インスタンス.インスタンスメソッド
という形で書きますが,クラスの中ではインスタンス部分をself
(インスタンス自身)と書きます。
クラスの中で
self.name
と書くと,user
というインスタンス自身に対してname
メソッドを呼び出していることになります。
言われてみれば「なるほど!」と納得できますが,なかなか見かけないので馴染みがありませんでした^^;
【補足2】self.name
のself.
は省略することができる
self.name
のself.
は,意味が曖昧にならない限り省略することができます。
ただし,self.name =
のself.
は省略することができません。(ローカル変数name
への代入とみなされます)
class User
attr_accessor :name
def initialize(name)
self.name = name # self.を省略できない
end
def introduce
puts "#{name}です" # self.を省略できる
end
end
user = User.new ("川上")
user.introduce
ちなみに,私が疑問に思っていた冒頭のコードは,@nameへの値のセットはセッターを介さずに行い,値を参照する時だけゲッターを介している形でした。
class User
attr_reader :name #ゲッターのみ
def initialize(name)
@name = name
end
def introduce
puts "#{name}です" # self.を省略している
end
end
user = User.new ("川上")
user.introduce
謎が解けてスッキリ!
(ただ,この書き方をされている場合,意図してこうされているわけではなく,「@
をつけ忘れているけど,他の目的でattr_reader :name
と書いてるから偶然参照できている」ケースが多いのかな,と思いました)
【補足3】@nameとself.nameは全く異なるもの
とても極端でありえない例ですが,attr_readerを使わずにname
メソッドを書くとき,メソッドの中が
def name
@name
end
ではなく,例えば以下のような値だった場合,
def name
1000
end
self.name
でname
メソッドを呼ぶと
1000
が戻り値として返ります。
このように,純粋な挙動を見ると,self.name
は単に「name
メソッドを呼ぶ」という処理をするだけで,メソッドの中身は関係ありません。
(同様に,self.name =
も,全く異なる名前のインスタンス変数(たとえば@age
)に値をセットすることができてしまいます)
attr_accessorを使用すれば上記のようにメソッドの中身がおかしくなることはありませんが,ゲッターメソッド(もしくはセッターメソッド)を呼ぶ,という挙動自体は変わりません。
インスタンス変数を使用する場合は,(追記:コメント頂き,以下のように修正いたします)@name
を直接定義するし,一度定義されたら@name
自身が値を保持します。
@nameはメソッドを呼ぶものではなく,インスタンス変数です。
両者は全くの別物です。
【補足4】同じ使用法であれば,@nameを使用したほうが良い
クラスの中で共有する値をセットしたり参照する場合は,普通に@name
を使用したほうが良いです。
self.name =
で値をセットしたりself.name
で値を取得する場合,それぞれセッター,ゲッターを呼び出す必要があり,その分の処理が増えるからです。
逆に,self.name
を使用したほうが良いケースが思い浮かばず・・ありましたらぜひ教えていただけたら幸いです🙇♀️
追記:コメント欄にてself.name =
を使用するケースを教えていただきました!ありがとうございます。
終わりに
以上,私自身が疑問に思っていた部分だったのでまとめました。
もし内容に間違いがありましたら,ご指摘いただけますと幸いです☺️
参考
- Progate RubyⅣ (有料部分なのでURL添付なし)
- ドットインストール Ruby入門 #20 アクセサを使ってみよう (同上)
- やんばるエキスパート 講座