11. Изключения. Енумератори

11. Изключения. Енумератори

11. Изключения. Енумератори

14 ноември 2012

Днес

След две седмици...

Quine

програма, принтираща кода си

->_{_%_}["->_{_%%_}[%r]"]

Quine

to the eleven

v=0000;eval$s=%q~d=%!^Lcf<LK8,                  _@7gj*LJ=c5nM)Tp1g0%Xv.,S[<>YoP
4ZojjV)O>qIH1/n[|2yE[>:ieC       "%.#%  :::##"       97N-A&Kj_K_><wS5rtWk@*a+Y5
yH?b[F^e7C/56j|pmRe+:)B     "##%      ::##########"     O98(Zh)'Iof*nm.,$C5Nyt=
PPu01Avw^<IiQ=5$'D-y?    "##:         ###############"    g6`YT+qLw9k^ch|K'),tc
6ygIL8xI#LNz3v}T=4W    "#            #.   .####:#######"    lL27FZ0ij)7TQCI)P7u
}RT5-iJbbG5P-DHB<.   "              ##### # :############"   R,YvZ_rnv6ky-G+4U'
$*are@b4U351Q-ug5   "              #######################"   00x8RR%`Om7VDp4M5
PFixrPvl&<p[]1IJ   "              ############:####  %#####"   EGgDt8Lm#;bc4zS^
y]0`_PstfUxOC(q   "              .#############:##%   .##  ."   /,}.YOIFj(k&q_V
zcaAi?]^lCVYp!;  " %%            .################.     #.   "  ;s="v=%04o;ev"%
(;v=(v-($*+[45,  ":####:          :##############%       :   "  ])[n=0].to_i;)%
360)+"al$s=%q#{  "%######.              #########            "  ;;"%c"%126+$s<<
126}";d.gsub!(/  "##########.           #######%             "  |\s|".*"/,"");;
require"zlib"||  "###########           :######.             "  ;d=d.unpack"C*"
d.map{|c|n=(n||  ":#########:           .######: .           "  )*90+(c-2)%91};
e=["%x"%n].pack   " :#######%           :###### #:          "   &&"H*";e=Zlib::
Inflate.inflate(   "  ######%           .####% ::          "   &&e).unpack("b*"
)[0];22.times{|y|   "  ####%             %###             "   ;w=(Math.sqrt(1-(
(y*2.0-21)/22)**(;   " .###:             .#%             "   ;2))*23).floor;(w*
2-1).times{|x|u=(e+    " %##                           "    )[y*z=360,z]*2;u=u[
90*x/w+v+90,90/w];s[(    " #.                        "    ;y*80)+120-w+x]=(""<<
32<<".:%#")[4*u.count((     " .                   "     ;"0"))/u.size]}};;puts\
s+";_ The Qlobe#{" "*18+ (       "#  :#######"       ;"Copyright(C).Yusuke End\
oh, 2010")}";exit~;_ The Qlobe                  Copyright(C).Yusuke Endoh, 2010

Изключения

Изключенията в Ruby

Непълна йерархия

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   |   +-- SyntaxError
   |   +-- LoadError
   +-- SecurityError
   +-- StandardError
       +-- ArgumentError
       +-- IndexError
       |   +-- KeyError
       |   +-- StopIteration
       +-- NameError
       |   +-- NoMethorError
       +-- RuntimeError
       +-- TypeError 

После ще видим пълната.

Предизвикване на изключения

# Предизвиква RuntimeError
raise "'Prophet!' said I, 'Thing of evil!" # error: RuntimeError

# Като горното, но с различен текст
raise RuntimeError, 'prophet still, if bird or devil!' # error: RuntimeError

# Друг начин да предизвикаме RuntimeError
raise RuntimeError.new('Whether tempter sent, or whether...') # error: RuntimeError

Хващане на изключения

begin
  puts '...tempest tossed thee here ashore'
  raise NameError, 'Desolate yet all undaunted'
rescue => ex
  ex.message   # "Desolate yet all undaunted"
  ex.class     # NameError
end

Хващане на изключения

хващане на конкретен тип

begin
  raise KeyError, 'on this desert land enchanted'
rescue ArgumentError => ex
  puts 'on this home by horror haunted'
rescue KeyError, TypeError => ex
  ex.message  # "on this desert land enchanted"
  ex.class    # KeyError
end

Какво хваща rescue?

rescue хваща "само" наследници на StandardError, ако не сме указали друго:

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   +-- StandardError
       +-- ArgumentError
       +-- NameError
       |   +-- NoMethorError
       +-- RuntimeError 

Въпрос към вас

Какво ще се случи тук?

begin
  raise KeyError, 'tell me truly, I implore'
rescue IndexError => ex
  puts 'IndexError'
rescue KeyError => ex
  puts 'KeyError'
end

Хващане на изключения

приоритет на rescue клаузите

Припомняне KeyError < IndexError

$eh = 'foo'

begin
  raise KeyError, 'Is there - is there balm in Gilead?'
rescue IndexError => ex
  $eh = 'index'
rescue KeyError => ex
  $eh = 'key'
end

$eh    # "index"

Изпълнява се първия rescue, за който изключението е kind_of? типа.

Запомнете

Динамичните езици обикновено ползват прости правила

Хващане на изключения

ensure клауза

Кодът в ensure клаузата се изпълнява винаги.

begin
  raise 'tell me - tell me, I implore!' if rand(2).zero?
ensure
  puts '????? ??? ?????, "?????????"'
end

Хващане на изключения

else клауза

else клаузата се изпълнява когато няма възникнало изключение.

begin
  launch_nukes
rescue
  puts 'Uh-oh! Something went wrong :('
else
  puts 'War... War never changes'
end

begin/end in all its glory!

begin
  get_a_life
rescue NoFriendsError => ex
  puts 'Goodbye cruel world'
rescue InsufficientVespeneGasError, NotEnoughMineralsError => ex
  puts 'I think I play too much StarCraft'
rescue
  puts ';('
else
  puts 'Woohoo!'
ensure
  puts 'rm -rf ~/.history'
end

rescue в метод

В случай, че ползвате rescue в метод така:

def execute
  begin
    potentially_dangerous
  rescue SomeException => e
    # Handle the error
  ensure
    # Ensure something always happens
  end
end

rescue в метод

Предпочитания вариант

По-добре е да го запишете без begin/end, което е еквивалентно на предното:

def execute
  potentially_dangerous
rescue SomeException => e
  # Handle the error
ensure
  # Ensure something always happens
end

Предизвикване на изключение

по време на обработка на друго

Ако възникне изключение при обработка друго, старото се игнорира и се "вдига" новото.

begin
  raise KeyError
rescue
  raise TypeError
  puts "I'm a line of code, that's never executed ;("
end

raise в rescue

raise в rescue клауза "вдига" същото изключение, което се обработва.

begin
  raise KeyError, 'But high she shoots through air and light'
rescue
  puts 'Whoops'
  raise
end

begin/end

...е израз, като всичко друго в ruby

result = begin
  raise KeyError if rand(3).zero?
  raise NameError if rand(3).zero?
rescue KeyError
  'nyckel'
rescue NameError
  'namn'
else
  'ingenting'
end

result    # "nyckel"

rescue като модификатор

[].fetch(1) rescue 'baba' # "baba"

Exception#exception

raise type, message всъщност извиква type.exception(message) за да конструира изключение.

class Thing
  def exception(message)
    NameError.new(message)
  end
end

thing = Thing.new
raise thing, 'whoops' # error: NameError

Как да ползваме изключения

Може да разделим изключенията на два вида.

За първите обикновено създаваме клас. За вторите обикновено ползваме raise.

Кога ползваме изключения

Разсъждения

catch и throw

catch и throw

def iterate_pairs(hash)
  hash.values.each { |array| iterate_values array }
end

def iterate_values(array)
  array.each do |item|
    if item == 'Nemo'
      puts 'Found Nemo!'
      throw :done
    end
  end
end

animals = {cats: %w[Simba], fish: %w[Crispy Nemo], boars: %w[Pumba]}
catch(:done) { iterate_pairs(animals) }

Този пример е доста синтетичен.

catch и throw

накратко

Enumerator-и

Някои методи на Enumerable могат да не вземат блок.

numbers = []
1.upto(5) { |x| numbers << x }

numbers               # [1, 2, 3, 4, 5]

other = 1.upto(5)
other                 # #<Enumerator: 1:upto(5)>
other.to_a            # [1, 2, 3, 4, 5]

1.upto(5).map(&:succ) # [2, 3, 4, 5, 6]

Enumerator-и

нещо като итератори

Енумераторите могат да се държат като итератори.

numbers = 1.upto(3)

numbers.next   # 1
numbers.next   # 2
numbers.next   # 3
numbers.next   # error: StopIteration

Kernel#loop

loop прави безкраен цикъл. Спира на StopIteration.

numbers = 1.upto(3)

loop do
  puts numbers.next
end

#with_object и #with_index

Енумераторите имат някои интересни методи.

numbers = 1.upto(3)

numbers.with_index.to_a      # [[1, 0], [2, 1], [3, 2]]
numbers.with_object(:x).to_a # [[1, :x], [2, :x], [3, :x]]

Object#enum_for

Може да извадите енумератор от произволен метод с enum_for.

class Numbers
  def primes
    yield 2
    yield 3
    yield 5
    yield 7
  end
end

first_four_primes = Numbers.new.enum_for(:primes)
first_four_primes.to_a     # [2, 3, 5, 7]

map_with_index

навръзване на енумератори

Ако ви се е случвало да ви трябва индекс в map:

words = %w( foo bar baz ).map.with_index do |word, index|
  "#{index}: #{word.upcase}"
end

words # ["0: FOO", "1: BAR", "2: BAZ"]

Въпроси