Callbacks In Python

These are my notes on callbacks in Python.

This is my favorite Python book on Amazon, if you are interested in learning Python I highly recommend it


Callbacks In Python

In Python, a named function is created using the "def" keyword to specify a function name, which can be used to call that function at 
any time in the program to execute the statements it contains. Optionally, the named function can return a value to the caller.

Python also allows an anonymous function to be created using the lambda keyword. 
An anonymous function may only contain a single expression that must always return a value. 

Unlike the usual creation of a function with the "def" keyword, the creation of a function with a lambda keyword returns a function object.
This can be assigned a variable, which can then be used to reference the function at any time in the program to execute the expression it

The lambda keyword offers the programmer an alternative syntax for the creation of a function. For example:

def square(x):
 return x**2

can alternatively be written as:

square = lambda x:x**2

In either case, the call square(5) returns the result 25 by passing in an integer argument to the function. 
Note that the lambda keyword is followed by an argument without parentheses, and the specified expression does not require the return 
keyword as all functions created with lambda must implicitly return a value.

While the lambda keyword offers an alternative way to create a function, it is mostly used to embed a function within the code.
For instance, callbacks are frequently coded as inline lambda expressions embedded directly in a caller's arguments list.
Instead of being defined with the "def" keyword elsewhere in the program and referenced by name.
For example:

def function_1: statements
def function_2: statements
callbacks = [ function_1, function_2 ]

This can be written as:

callbacks = [ lambda: expression, lambda: expression ]

def function_1(x): return x**2
def function_2(x): return x**3
def function_3(x): return x**4

callbacks = [function_1, function_2, function_3]

print('\nNamed Functions:')
for function in callbacks:print('Result:', function(3))

callbacks = \
[lambda x:x**2, lambda x:x**3, lambda x:x**4]

print('\nAnonymous Functions:')
for function in callbacks:print('Result:', function(3))

Function definitions that contain just one statement can be written on just one line.
The \ backslash character can be used to allow code to continue on the nect line.


The Python "pass" keyword is useful when writing program code as a temporary placeholder that can be inserted into the code at places where further code needs to be
added later. The "pass" keyword is inserted where a statement is required syntactically, but it merely performs a null operation. When it is executed nothing happens and no
code needs to be executed. This allows an incomplete program to be executed for testing by simulating correct syntax so the interpreter does not report errors.

bool = True
if bool:
    print('Python is better than R')

In loop structures it is important not to confuse the "pass" keyword, which allows the interpreter to process all subsequent statements 
on that iteration, with the "continue" keyword, which skips subsequent statements on that iteration of the loop only.

title = '\nPython is better than R\n'

for char in title:print(char, end = '')

for char in title:
    if char == 'y':
        print('*', end = '')
    print(char, end = '')

for char in title:
    if char == 'y':
        print('*', end = '')
    print(char, end = '')

Producing Generators

When a Python function is called, it executes the statements it contains and may return a value specified to the return keyword. After the function ends, control returns
to the caller and the state of the function is not retained. When the function is next called, it will process its statements from start to finish once more.

A Python generator is a special function that returns a generator object to the caller rather than a data value. This retains the state of the function when it was last 
called, so it will continue from that point when next called.

Generator functions are produced by definition just like regular functions, but contain a yield statement. This begins with the Python yield keyword and specifies the
generator object to be returned to the caller. When the yield statement gets executed, the state of the generator object is frozen, and the current value in its expression 
list is retained. The generator object returned by the yield statement can be conveniently assigned to a variable. Python's built-in "next()" function can then specify that
variable name within its parentheses to continue execution of the function from the point at which it was frozen, exactly as if the yield statement were just another external call.

Repeatedly calling the generator object with the "next()" function continues execution of the function until it raises an exception. This can be avoided by enclosing the
yield statement within an infinite loop so it will return successive values on each iteration.

def fibonacci_generator():

    while True:
        yield a


for i in fib:
    if i > 100:

In the above script, the variables are initialized with a common value in a single statement.
You can use the inbuilt "type()" function to confirm the object type. here, the type(fib) is confirmed as a generator class object.