8

In Ruby, I can pass a block of code to a method.

For example, I can pass different code blocks to get_schedules_with_retries method.

And invoke the block by calling black.call

I'd like to know how could I implement that logic in Python,

Because I have lots of code blocks, need retry pattern.

I don't like copy paste the retry logic in many code blocks

Example:

def get_schedules_with_retries(&block)
  max_retry_count = 3
  retry_count = 0
  while (retry_count < max_retry_count)
    begin
      schedules = get_more_raw_schedules
      block.call(schedules)
    rescue Exception => e
      print_error(e)
    end
    if schedules.count > 0
      break
    else
      retry_count+=1
    end
  end
  return schedules
end

get_schedules_with_retries do |schedules|
  # do something here
end

get_schedules_with_retries do |schedules|
  # do another thing here
end  
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
newBike
  • 14,385
  • 29
  • 109
  • 192
  • Python doesn't have "blocks". What it has are first class functions. You'd simply pass a callback function. – deceze Feb 15 '16 at 09:45

3 Answers3

5

In Python, a block is a syntactic feature (an indentation under block opening statements like if or def) and not an object. The feature you expect may be a closure (which can access variables outside of the block), which you can achieve using inner functions, but any callable could be used. Because of how lambda works in Python, the inline function definition you've shown with do |arg| is limited to a single expression.

Here's a rough rewrite of your sample code in Python.

def get_schedules_with_retries(callable, max_retry_count = 3):
  retry_count = 0
  while retry_count < max_retry_count:
    schedules = get_more_raw_schedules()
    try:
      callable(schedules)
    except:  # Note: could filter types, bind name etc.
      traceback.print_exc()
    if schedules.count > 0:
      break
    else:
      retry_count+=1
  return schedules

get_schedules_with_retries(lambda schedules: single_expression)

def more_complex_function(schedules):
  pass # do another thing here
get_schedules_with_retries(more_complex_function)

One variant uses a for loop to make it clear the loop is finite:

def call_with_retries(callable, args=(), tries=3):
  for attempt in range(tries):
    try:
      result=callable(*args)
      break
    except:
      traceback.print_exc()
      continue
  else:  # break never reached, so function always failed
    raise  # Reraises the exception we printed above
  return result

Frequently when passing callables like this, you'll already have the function you want available somewhere and won't need to redefine it. For instance, methods on objects (bound methods) are perfectly valid callables.

Yann Vernier
  • 15,414
  • 2
  • 28
  • 26
4

You could do it like this:

def codeBlock(paramter1, parameter2):
    print("I'm a code block")

def passMeABlock(block, *args):
    block(*args)

#pass the block like this
passMeABlock(codeBlock, 1, 2)
Fredrik
  • 1,389
  • 1
  • 14
  • 32
  • What if I have to pass parameters in `code block` ? and is it also available to pass a instance method with instance variables to first class method like `passMeABlock` ? – newBike Feb 15 '16 at 10:16
2

You do so by defining a function, either by using the def statement or a lambda expression.

There are other techniques however, that may apply here. If you need to apply common logic to the input or output of a function, write a decorator. If you need to handle exceptions in a block of code, perhaps creating a context manager is applicable.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343