4

in Ruby, a block argument works like this:

def foo_bar (&block)
    block.(4)
end

foo_bar do |x|
    puts x
    puts x * 2
end
=begin
4
8
=end

I've seen the following equivalent in Python, but I find it quite unsatisfactory, because it requires defining the function and only then passing it as an argument:

def foo_bar(block):
    block(4)

def callback(x):
    print(x)
    print(x * 2)

foo_bar(callback) 
'''
4
8
'''

is there any alternative to it in Python, that doesn't require the function to be defined first?

Sapphire_Brick
  • 1,560
  • 12
  • 26

3 Answers3

3

Nope, python doesn't allow such syntax sugar.It doesn't have anonymous functions; the closest it offers is lambdas, but those have a number of restrictions, namely, they can only have one expression, i.e, one "line" of code.

Defining functions with def is the pythonic way to create a reusable block of code.

erik258
  • 14,701
  • 2
  • 25
  • 31
  • 5
    it's too bad, though. – Sapphire_Brick Oct 03 '19 at 01:34
  • every language makes trade offs. Python attempts to prefer readability and idiomatic style above convenient shorthand. Ruby is more about being expressive regardless of what the next maintainer can figure out. If you prefer ruby, use it. If you for some reason have to use python, try to embrace what it's attempting to do rather than running against the grain. – erik258 Oct 03 '19 at 01:36
  • 4
    I know that, I just didn't know that _not having block argument are so important for readability_. – Sapphire_Brick Oct 03 '19 at 01:49
  • I mean "... block argument[s] [is] so ..." – Sapphire_Brick Jul 13 '20 at 14:13
2

The closest I have found to a callback function without prior definition is the decorator syntax:

def loop_thru(block):
    arr = [3, 76, 2, 8, 24]
    for item in arr:
        block(item)

@loop_thru
def puts(item):
    print(item, end=" ") # 3 76 2 8 24

...although doing so still requires a named function.
There are also lambdas of course:

loop_thru(lambda num: print(num, end=" "))

However, they have limitations:

  • They are limited to one line of code
  • They disallow = assignment:
foo = lambda: bar = 'baz'
'''
  File "script.py", line 1
    foo = lambda: bar = 'baz'
          ^
SyntaxError: cannot assign to lambda
'''

foo = lambda: (bar = 'baz')
'''
  File "script.py", line 1
    foo = lambda: (bar = 'baz')
                       ^
SyntaxError: invalid syntax
'''

although that := is legal inside a lambda, := only works with variables, not attributes or subscripts.

foo = {}
bar = lambda: (foo['baz'] := 23)
'''
  File "script.py", line 2
    bar = lambda: (foo['baz'] := 23)
                   ^
SyntaxError: cannot use named assignment with subscript
'''
Sapphire_Brick
  • 1,560
  • 12
  • 26
0

You cannot pass blocks as parameters, but the code in your example is equivalent to this:

def foo_bar():
    yield 4

for x in foo_bar(): 
    print(x)
    print(x * 2)

If you want return more values:

def foo_bar():
    print("start")
    yield (2,3,4)
    print("end")

for x,y,z in foo_bar(): 
    print(f"{x},{y},{z}")
    
---output--
start
2,3,4
end
-----------

Passing blocks in Ruby is elegant, but the equivalent in Python is also elegant.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 09 '23 at 09:27