Эквивалент Python "с" в Ruby

В Python оператор with используется, чтобы удостовериться, что код очистки всегда вызывается, независимо от того, что выбраны исключения или вызовы функций. Например:

with open("temp.txt", "w") as f:
    f.write("hi")
    raise ValueError("spitespite")

Здесь файл закрыт, хотя исключение было создано. Лучшее объяснение здесь.

Есть ли эквивалент для этой конструкции в Ruby? Или вы можете закодировать один, поскольку Ruby имеет продолжения?

Ответ 1

Ruby имеет синтаксически облегченную поддержку для буквальных анонимных процедур (называемых блоками в Ruby). Поэтому для этого не требуется новая языковая функция.

(В общем, это плохой знак, если вам нужно добавить языковые функции. Вы должны иметь возможность реализовать все в библиотеке, иначе это признак плохого дизайна языка.)

Итак, что вы обычно делаете, это написать метод, который берет блок кода, выделяет ресурс, выполняет блок кода в контексте этого ресурса и затем закрывает ресурс.

Что-то вроде этого:

def with(klass, *args)
  yield r = klass.open(*args)
ensure
  r.close
end

Вы можете использовать его следующим образом:

with File, 'temp.txt', 'w' do |f|
  f.write 'hi'
  raise 'spitespite'
end

Однако это очень процедурный способ сделать это. Ruby - объектно-ориентированный язык, что означает, что ответственность за надлежащее выполнение блока кода в контексте File должна принадлежать классу File:

File.open 'temp.txt', 'w' do |f|
  f.write 'hi'
  raise 'spitespite'
end

Это может быть реализовано примерно так:

def File.open(*args)
  f = new(*args)
  return f unless block_given?
  yield f
ensure
  f.close if block_given?
end

Это общий шаблон, который реализуется множеством классов в основной библиотеке Ruby, стандартных библиотеках и сторонних библиотеках.


Более тесное соответствие с общим протоколом менеджера контекста Python будет:

def with(ctx)
  yield ctx.setup
ensure
  ctx.teardown
end

class File
  def setup; self end
  alias_method :teardown, :close
end

with File.open('temp.txt', 'w') do |f|
  f.write 'hi'
  raise 'spitespite'
end

Обратите внимание, что это практически неотличимо от примера Python, но не требовало добавления нового синтаксиса к языку.

Ответ 2

Эквивалент в Ruby должен был передать блок методу File.open.

File.open(...) do |file|
  #do stuff with file
end  #file is closed

Это идиома, которую использует Ruby, и тот, с которым вам должно быть удобно.

Ответ 3

Вы можете использовать Block Arguments для этого в Ruby:

class Object  
    def with(obj)  
        obj.__enter__  
        yield  
        obj.__exit__  
    end  
end

Теперь вы можете добавить методы __enter__ и __exit__ в другой класс и использовать его следующим образом:

with GetSomeObject("somefile.text") do |foo|  
    do_something_with(foo)
end  

Ответ 4

Можно записать в файл атомарно в Ruby, например:

File.write("temp.txt", "hi")
raise ValueError("spitespite")

Написание такого кода означает, что невозможно случайно оставить файл открытым.

Ответ 5

Я просто добавлю несколько дополнительных объяснений для других; кредит должен идти к ним.

В самом деле, в Ruby код очистки, как и другие, указан в пункте ensure; но обертывание вещей в блоках вездесуще в Ruby, и именно так это делается наиболее эффективно и в духе Ruby. При переводе, не переводите прямо дословно, вы получите очень странные предложения. Точно так же не ожидайте, что все из Python будет иметь взаимно однозначное соответствие Ruby.

Из опубликованной вами ссылки:

class controlled_execution:
    def __enter__(self):
        set things up
        return thing
    def __exit__(self, type, value, traceback):
        tear things down

with controlled_execution() as thing:
     some code

Ruby way, что-то вроде этого (человек, я, наверное, все это неправильно: D):

def controlled_executor
  begin
    do_setup
    yield
  ensure
    do_cleanup
  end
end

controlled_executor do ...
  some_code
end

Очевидно, вы можете добавить аргументы как к controlled executor (для вызова в обычном порядке), так и для вывода (в этом случае вам также нужно добавить аргументы в блок). Таким образом, чтобы реализовать то, что вы указали выше,

class File
  def my_open(file, mode="r")
    handle = open(file, mode)
    begin
      yield handle
    ensure
      handle.close
    end
  end
end

File.my_open("temp.txt", "w") do |f|
  f.write("hi")
  raise Exception.new("spitesprite")
end

Ответ 6

Вы всегда можете использовать блок try..catch..finally, где раздел finally содержит код для очистки.

Изменить: извините, опечатка: вам нужно begin..rescue..ensure.

Ответ 7

Я считаю, что вы ищете ensure.