Официальный FAQ по Ruby
If you wish to report errors or suggest improvements for this FAQ, please go to our GitHub repository and open an issue or pull request.
Переменные, константы и аргументы
Создает ли присваивание новую копию объекта?
Все переменные и константы ссылаются (указывают) на какой-либо объект. (За исключением неинициализированных локальных переменных, которые ни на что не ссылаются. Они вызывают исключение NameError при использовании). Когда вы присваиваете значение переменной или инициализируете константу, вы устанавливаете объект, на который ссылается эта переменная или константа.
Само по себе присваивание никогда не создает новую копию объекта.
В некоторых особых случаях есть чуть более глубокое объяснение. Экземпляры Fixnum, NilClass, TrueClass и FalseClass содержатся непосредственно в переменных или константах — здесь нет ссылок. Переменная, содержащая число 42, или константа true фактически хранят значение, а не ссылку на него. Присваивание, таким образом, физически создает копию объектов этих типов. Мы обсудим это подробнее в разделе Непосредственные и ссылочные объекты.
Какова область видимости локальной переменной?
Новая область видимости для локальной переменной вводится в (1) верхнем уровне (main), (2) определении класса (или модуля) или (3) определении метода.
var = 1 # (1)
class Demo
var = 2 # (2)
def method
var = 3 # (3)
puts "in method: var = #{var}"
end
puts "in class: var = #{var}"
end
puts "at top level: var = #{var}"
Demo.new.method
Результат:
in class: var = 2
at top level: var = 1
in method: var = 3
(Обратите внимание, что определение класса — это исполняемый код: трассировочное сообщение, которое он содержит, записывается по мере определения класса).
Блок ({ ... } или do ... end) почти вводит новую область видимости ;-) Локальные переменные, созданные внутри блока, недоступны вне блока. Однако, если локальная переменная внутри блока имеет то же имя, что и существующая локальная переменная в области видимости вызывающей стороны, то новая локальная переменная не создается, и вы можете впоследствии обращаться к этой переменной вне блока.
a = 0
1.upto(3) do |i|
a += i
b = i*i
end
a # => 6
# b здесь не определена
Это становится важным при использовании многопоточности — каждый поток получает свою собственную копию переменных, локальных для блока потока:
threads = []
["one", "two"].each do |name|
threads << Thread.new do
local_name = name
a = 0
3.times do |i|
Thread.pass
a += i
puts "#{local_name}: #{a}"
end
end
end
threads.each {|t| t.join }
Может вывести (в случае, если планировщик переключает потоки, как указано в Thread.pass; это зависит от ОС и процессора):
one: 0
two: 0
one: 1
two: 1
one: 3
two: 3
while, until и for — это структуры управления, а не блоки, поэтому локальные переменные внутри них будут доступны во внешней среде. loop, однако, является методом, и связанный с ним блок вводит новую область видимости.
Когда локальная переменная становится доступной?
На самом деле, вопрос лучше поставить так: «в какой момент Ruby понимает, что что-то является переменной?» Проблема возникает потому, что простое выражение a может быть либо переменной, либо вызовом метода без параметров. Чтобы решить, что это за случай, Ruby ищет инструкции присваивания. Если в какой-то момент в исходном коде до использования a он видит, что a присваивается значение, он решает парсить a как переменную, в противном случае он рассматривает её как метод. В качестве несколько патологического примера рассмотрите этот фрагмент кода, изначально представленный Clemens Hintze:
def a
puts "method `a' called"
99
end
[1, 2].each do |i|
if i == 2
puts "a = #{a}"
else
a = 1
puts "a = #{a}"
end
end
Результат:
a = 1
method `a' called
a = 99
Во время парсинга Ruby видит использование a в первой инструкции puts и, поскольку он еще не видел никакого присваивания a, предполагает, что это вызов метода. Однако к тому моменту, когда он добирается до второй инструкции puts, он уже видел присваивание и поэтому рассматривает a как переменную.
Обратите внимание, что присваивание не обязательно должно быть выполнено — Ruby просто должен его увидеть. Эта программа не вызывает ошибки:
a = 1 if false; a # => nil
Эта проблема с переменными обычно не является помехой. Если вы столкнетесь с ней, попробуйте добавить присваивание вида a = nil перед первым обращением к переменной. Это дает дополнительное преимущество в виде ускорения времени доступа к локальным переменным, которые впоследствии появляются в циклах.
Какова область видимости константы?
Константа, определенная в определении класса или модуля, может быть доступна напрямую внутри определения этого класса или модуля.
Вы можете напрямую обращаться к константам во внешних классах и модулях из вложенных классов и модулей.
Вы также можете напрямую обращаться к константам в суперклассах и включенных модулях.
Помимо этих случаев, вы можете обращаться к константам классов и модулей, используя оператор ::, например ModuleName::CONST1 или ClassName::CONST2.
Как передаются аргументы?
Фактический аргумент присваивается формальному аргументу при вызове метода. (Смотрите присваивание для более подробной информации о семантике присваивания.)
def add_one(number)
number += 1
end
a = 1
add_one(a) # => 2
a # => 1
Поскольку вы передаете ссылки на объекты, возможно, что метод изменит содержимое переданного в него изменяемого объекта.
def downer(string)
string.downcase!
end
a = "HELLO" # => "HELLO"
downer(a) # => "hello"
a # => "hello"
В Ruby нет эквивалента семантике передачи по ссылке, как в других языках.
Влияет ли присваивание формальному аргументу на фактический аргумент?
Формальный аргумент является локальной переменной. Внутри метода присваивание формальному аргументу просто изменяет аргумент так, чтобы он ссылался на другой объект.
Что происходит, когда я вызываю метод через формальный аргумент?
Все переменные Ruby (включая аргументы методов) выступают в качестве ссылок на объекты. Вы можете вызывать методы в этих объектах, чтобы получить или изменить состояние объекта и заставить объект что-то сделать. Вы можете делать это с объектами, переданными в методы. Вам нужно быть осторожными при этом, так как побочные эффекты такого рода могут сделать программы трудными для понимания.
Что означает * перед аргументом?
При использовании в списке формальных параметров звездочка позволяет передавать методу произвольное количество аргументов, собирая их в массив и присваивая этот массив параметру со звездочкой.
def foo(prefix, *all)
all.each do |element|
puts "#{prefix}#{element}"
end
end
foo("val = ", 1, 2, 3)
Результат:
val = 1
val = 2
val = 3
При использовании в вызове метода * разворачивает массив, передавая его отдельные элементы в качестве аргументов.
a = [1, 2, 3]
foo(*a)
Вы можете добавить * перед последним аргументом в следующих случаях:
- Левая часть множественного присваивания.
- Правая часть множественного присваивания.
- Определение формальных аргументов метода.
- Фактические аргументы в вызове метода.
- В условии
whenструктурыcase.
Например:
x, *y = [7, 8, 9]
x # => 7
y # => [8, 9]
x, = [7, 8, 9]
x # => 7
x = [7, 8, 9]
x # => [7, 8, 9]
Что означает & перед аргументом?
Если перед последним формальным аргументом метода стоит амперсанд (&), блок, следующий за вызовом метода, будет преобразован в объект Proc и присвоен формальному параметру.
Если последний фактический аргумент в вызове метода является объектом Proc, вы можете поставить перед его именем амперсанд, чтобы преобразовать его в блок. Затем метод может использовать yield для его вызова.
def meth1(&b)
puts b.call(9)
end
meth1 {|i| i + i }
def meth2
puts yield(8)
end
square = proc {|i| i * i }
meth2 {|i| i + i }
meth2 &square
Результат:
18
16
64
Как я могу указать значение по умолчанию для формального аргумента?
def greet(p1="hello", p2="world")
puts "#{p1} #{p2}"
end
greet
greet("hi")
greet("morning", "mom")
Результат:
hello world
hi world
morning mom
Значение по умолчанию (которое может быть произвольным выражением) вычисляется при вызове метода. Оно вычисляется с использованием области видимости метода.
Как мне передать аргументы в блок?
Формальные параметры блока указываются между вертикальными чертами в начале блока:
proc {|a, b| a <=> b }
Эти параметры на самом деле являются локальными переменными. Если при выполнении блока существует локальная переменная с таким же именем, эта переменная будет изменена вызовом блока. Это может быть как хорошо, так и плохо.
Обычно аргументы передаются в блок с помощью yield (или итератора, который вызывает yield) или с помощью метода Proc.call.
Почему мой объект неожиданно изменился?
A = a = b = "abc"
b.concat("d") # => "abcd"
a # => "abcd"
A # => "abcd"
Переменные хранят ссылки на объекты. Присваивание A = a = b = "abc" помещает ссылку на строку "abc" в A, a и b.
Когда вы вызываете b.concat("d"), вы вызываете метод concat для этого объекта, изменяя его с "abc" на "abcd". Поскольку a и A также ссылаются на тот же самый объект, их видимые значения тоже меняются.
На практике это представляет меньше проблем, чем может показаться.
Кроме того, все объекты могут быть заморожены, что защищает их от изменений.
Меняется ли когда-нибудь значение константы?
Константа — это переменная, имя которой начинается с заглавной буквы. Константам нельзя переприсваивать значения из методов экземпляра, но в остальных случаях их можно менять по желанию. Когда константе присваивается новое значение, выдается предупреждение.
Почему я не могу загрузить переменные из отдельного файла?
Допустим, file1.rb содержит:
var1 = 99
и какой-то другой файл загружает его:
require_relative "file1"
puts var1
Результат:
prog.rb:2:in `<main>': undefined local variable or method `var1' for main:Object (NameError)
Вы получаете ошибку, потому что load и require организуют хранение локальных переменных в отдельном анонимном пространстве имен, фактически отбрасывая их. Это сделано для защиты вашего кода от загрязнения.