Python. Nested functions. Nested scopes. Name search rules in case of nested functions. Factory functions. Passing values to a nested function

Nested functions. Nested scopes. Name search rules in case of nested functions. Factory functions. Passing values to a nested function


Contents


Search other websites:




1. What are nested functions?

Nested functions can be used in the Python programming language. This means that there can be another def statement inside a def statement. The number of function nesting is arbitrary. In the most general case, the form of declaring nested functions is as follows:

def Fn_1(parameters1):
    ...
    def Fn_2(parameters2):
        ...
        def Fn_N(parametersN):
            ...
            return
        ...
        return
    return

here

  • Fn_1, Fn_2, Fn_N – names of functions that receive parameters1, parameters2, parametersN respectively.

 

2. Names lookup rules inside a function in case of nested scopes

If a name (variable) is declared inside a function that is nested in another function, then the following rules apply:

1. If a name (variable) is declared inside a function that is nested in another function, then the following rules apply:

  • search in the local scope;
  • search in all enclosing functions in order from the inside out;
  • search in the current global scope (search in the current module);
  • search in built-in scope (buildins module).

2. If a global name is declared inside a function using the global statement, then the search for the name starts immediately from the global scope.

3. If the default assignment operation (t = value) is used for some object, then this operation:

  • creates a new name in the local scope;
  • changes the name in the current local scope.

4. If a global name is defined inside the function (using the global keyword), then the assignment operation:

  • creates a new name in the scope of the enclosing module;
  • changes the meaning of the name that was declared in the enclosing module.

5. If a nonlocal name is defined inside the function (nonlocal instruction), then the assignment operation:

  • creates a new name in the nearest enclosing function;
  • changes the name that was created in the nearest enclosing function.

 

3. Examples of nested functions

Example 1. You are given a SquareEquation() function that calculates solutions to a quadratic equation. Inside the function is a nested Disc() function that calculates the discriminant.

# Nested functions

import math

# Quadratic equation solution function,
# this function is enclosing the nested function Disc().
# The function returns the solution to the equation as a list
def SquareEquation(a, b, c):

    # Nested function Disc() calculating a discriminant
    def Disc(a, b, c):
        d = b*b-4*a*c
        return d

    # Calculate the discriminant
    D = Disc(a, b, c)

    if D>=0:
        x1 = (-b - math.sqrt(D))/(2*a)
        x2 = (-b + math.sqrt(D))/(2*a)
        return [x1, x2]
    else:
        return None

# Call the function to solve the equation 2*x^2+3*x-5=0
Res = SquareEquation(2, 3, -5)

if Res!=None:
    print('Result = ', Res)
else:
    print('The equation has no roots')

The result of the program:

Result = [-2.5, 1.0]

Example 2.

The example implements the CalcComplex() function, which, based on the specified value n, implements the nested calculation functions:

  • sums of complex numbers – the AddComplex() function;
  • difference of complex numbers – SubComplex () function.

The CalcComplex() function returns the result as a list.

# Nested functions

import math

# Function CalcComplex(), which, based on the specified number,
# calls the nested function to evaluate the corresponding expression.

def CalcComplex(n, a1, b1, a2, b2):

    if n==1:
        # Define AddComplex () function - addition of complex numbers
        def AddComplex(a1, b1, a2, b2):
            real = a1+a2 # real part of a complex number
            imag = b1+b2 # imaginary part of a complex number
            return [real, imag]

        # Call the AddComplex() function
        result = AddComplex(a1, b1, a2, b2)

        # return the result
        return result

    if n==2:
        # Define SubComplex() function
        def SubComplex(a1, b1, a2, b2):
            real = a1-a2 # real part
            imag = b1-b2 # imaginary part
            return [real, imag]

        # call the SubComplex() function
        result = SubComplex(a1, b1, a2, b2)

    # Return the result
    return result

# Otherwise return None
return None

# Find the sum of complex numbers:
# (2-3*j) + (-2+5*j)
Result = CalcComplex(1, 2, -3, -2, 5)

# Return the result
print("Result = ", Result)

The result of the program

Result = [0, 2]

 

4. Factory functions. Basic concepts. Example

As you know, when a function is called, an object of this function is created. This object has its own address (reference). For nested functions, you can store the nested function object in an enclosing function. If a nested function object is stored, then such a function is called a factory function. In this case, the requirement is related to the enclosing function: the enclosing function must return a reference to the object of the nested function in the return statement.

After declaring a factory function in a program, it can be used as follows:

  • create a factory function object by calling the enclosing function. This creates a reference to the factory function object. In addition, the factory function object stores all values that were passed to it from the enclosing function;
  • call the factory function using the generated reference.

In general, the process of creating and using a factory function is as follows.

1. Declare nested functions as shown below

def Fn1(parameters1):
    ...
    def Fn2(parameters2): # this is a factory function
        ...
        # using parameters1 parameters of the enclosing function
        # ...
        ...
        return value # function Fn2 () returns some result
    ...
    return Fn2 # function Fn1() returns a reference to the object of function Fn2()

2. Get a reference to a nested factory function object. To get a reference, you need to call the enclosing function Fn1() with the arguments that are passed to it

ref = Fn1(arguments1)

In this case, arguments1 of the enclosing Fn1() function are converted to parameters1. These parameters are used and stored in the object of the nested function Fn2().

3. Call factory function object Fn2() using ref

ref(arguments2)

here arguments2 is converted to parameters2 of the nested function Fn2().

Thus, the program calls the factory function

Fn1(arguments1)(arguments2)

Example 1. A function that calculates the factorial of a number n: n! = 1·2·3· … ·n.

# Factory functions. Calculating the factorial of a number

# 1. Define an external function Factorial (n),
# which just declares and returns a nested function Fact()
def Factorial(n):
    # Declare a nested function for calculating factorial
    def Fact():
        # function Fact() saves the value n
        # from the enclosing scope and uses it
        if (n<=0):
            return 0
        i=1
        res=1
        while i<=n:
            res=res*i
            i=i+1
        return res

    # Return the result of a Fact () function without calling it
    return Fact

# 2. Save the reference to the Factorial (5) function,
#   which calculates 5!
ref5 = Factorial(5)

# Print the reference
print("ref5 = ", ref5) # ref5 =   <function Factorial.<locals>.Fact at 0x035176A8>

# Call the function for calculating factorial 5!
# follow the reference and get the result
F5 = ref5()
print("5! = ", F5) # 5! = 120

# 3. Get another reference to the function that calculates 8!
ref8 = Factorial(8)

# Print ref8 and the result of the calculation 8!
print('ref8 = ', ref8)
print("8! = ", ref8())

# 4. Go straight to the function for calculating factorial 10!
print("10! = ", Factorial(10)())

# 5. Call the calculation function 5 again! via ref5 reference,
#   ref5 is stored
print("5! = ", ref5()) # 5! = 120

The result of the program

ref5  <function Factorial.<locals>.Fact at 0x032E2150>
5! = 120
ref8 = <function Factorial.<locals>.Fact at 0x033376A8>
8! = 40=320
10! = 3628800
5! = 120

Example 2. Using a factory function that takes parameters. The example declares functions that calculate the n-th root of the number x. The enclosing function receives the value n, which is saved in the factory function. The factory function, in turn, receives an x parameter.

# Nested functions. Factory functions
# Getting the n-th root of x

# 1. Declare an enclosing Root() function with a nested Rt() function
def Root(n): # the root of the n-th degree
    # Declare a nested function that receives the number x
    def Rt(x):
        # the nested function saves the parameter n of the enclosing function
        return x**(1.0/n)

    # Return a reference to the nested function
    return Rt

# 2. Save reference to enclosing function with parameter 3
ref = Root(3) # ref = <function Root.<locals>.Rt at 0x0032B588>
print('ref = ', ref)

# 3. Calculate the root of the third degree of the number 27
result = ref(27) # calls Root(3)(27)
print('27(1/3) = ', result)

The result of the program

ref = <function Root.<locals>.Rt at 0x030E2150>
27^(1/3) = 3.0

 

5. Passing data from the enclosing scope to a nested function using default arguments. Example

From an enclosing function, you can pass data to a nested function using default arguments. The general form is as follows

def FnOut:
    varOut = value
    def FnIn(varIn = varOut): # varOut - default argument
        # using varIn
        ...
        return
    ...
    return

here

  • FnOut – enclosing function;
  • FnIn – nested function;
  • varOut – variable in the enclosing function that is passed to the inner function;
  • value – the value that varOut receives in the enclosing function;
  • varIn – a variable in a nested function that is passed the value of the enclosing function’s varOut variable. The value is passed as a string varIn = varOut.

Example.

The example implements the calculation of the area of a triangle using Heron’s formula. The nested Semiperimeter() function is passed three default arguments from the AreaTriangle() function.

# Nested functions

# Include math module
import math

# Determine the AreaTriangle() function
# which calculates the area of a triangle according to Heron's formula
# by the lengths of its sides a, b, c.
def AreaTriangle(a, b, c):
    # Checking whether it is possible to form a triangle from the sides a, b, c
    if (((a+b)<c)or((b+c)<a)or((a+c)<b)):
        return 0

    # The AreaTriangle() function determines a nested function Semiperimeter()
    # that receives the parameters a, b, from the enclosing function
    # and, based on these parameters, calculates the semiperimeter.
    # Parameters a, b, c are obtained as default arguments
    def SemiPerimeter(a=a, b=b, c=c):
        return (a+b+c)/2

    # Calculate the semiperimeter
    p = SemiPerimeter(a, b, c)

    # Calculate the area
    area = math.sqrt(p*(p-a)*(p-b)*(p-c))
    return area

# Demonstrate the AreaTriangle() function
Area = AreaTriangle(5, 7, 3)
print('Area = ', Area)

Area = AreaTriangle(1, 1, 5)
print('Area = ', Area)

The result of the program

Area = 6.49519052838329
Area = 0

 


Related topics