# Python High Order Function

In Python, a higher-order function is a function that takes one or more functions as arguments, or returns a function as its result. These functions are used to build more complex functions and are an important tool for functional programming in Python.

Here are some examples of higher-order functions in Python:

1. Map: The map() function takes a function and a sequence as arguments and returns a new sequence in which each element is the result of applying the function to the corresponding element of the original sequence. For example:
```def square(x):
return x ** 2

numbers = [1, 2, 3, 4, 5]
squares = map(square, numbers)
print(list(squares))  # Output: [1, 4, 9, 16, 25]
```
1. Filter: The filter() function takes a function and a sequence as arguments and returns a new sequence containing only the elements for which the function returns True. For example:
```def is_even(x):
return x % 2 == 0

numbers = [1, 2, 3, 4, 5, 6]
evens = filter(is_even, numbers)
print(list(evens))  # Output: [2, 4, 6]
```
1. Reduce: The reduce() function takes a function and a sequence as arguments and returns a single value that is the result of applying the function to the elements of the sequence, in a cumulative way. For example:
```from functools import reduce

return x + y

numbers = [1, 2, 3, 4, 5]
print(total)  # Output: 15
```
1. Lambda Functions: Lambda functions are anonymous functions that can be defined on the fly and passed to other functions as arguments. For example:
```numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))  # Output: [1, 4, 9, 16, 25]
```
1. Decorators: A decorator is a higher-order function that takes a function as input and returns a new function as output. The new function can add some behavior to the original function without modifying its code. For example:
```def my_decorator(func):
def wrapper():
print("Before the function is called.")
func()
print("After the function is called.")
return wrapper

@my_decorator
def say_hello():
print("Hello!")

say_hello()  # Output: Before the function is called. Hello! After the function is called.
```

### Properties of High order functions in Python:

Here are some of the key properties of higher-order functions in Python:

1. Functions as arguments: Higher-order functions can take other functions as arguments. This allows functions to be more flexible and reusable, as the behavior of the function can be modified by passing in different functions as arguments.
2. Functions as return values: Higher-order functions can also return functions as their result. This can be useful when creating functions that need to be customized based on the inputs or other factors.
3. Anonymous functions: In Python, anonymous functions can be created using lambda expressions. These functions are not named and can be passed as arguments or returned by higher-order functions.
4. Partial functions: Higher-order functions can also be used to create partial functions. This allows a function to be partially applied with arguments, creating a new function that takes fewer arguments than the original.
5. Function composition: Higher-order functions can be used to compose multiple functions together. This can be useful for creating more complex functions that perform multiple operations on their input data.

Overall, higher-order functions are a powerful tool in Python that allow for more flexible and modular code. By taking advantage of these properties, developers can create more reusable, efficient, and maintainable code.

### Method 1: Using functions as objects in High order function:

In Python, functions are first-class objects, which means they can be treated like any other object in the language. This means they can be passed as arguments to other functions and returned as values from functions. Here is an example of using functions as objects in a higher-order function:

```def apply_function(func, arg):
return func(arg)

def square(x):
return x ** 2

result = apply_function(square, 5)
print(result)  # Output: 25
```

In this example, we define a function called `apply_function` that takes two arguments: `func` and `arg`. The `func` argument is a function, and the `arg` argument is the input value to be passed to the function. Inside the `apply_function` function, we simply call `func` with `arg` as its argument and return the result.

We also define a function called `square` that takes one argument and returns its square. We then pass `square` and the value `5` to `apply_function`, which calls `square(5)` and returns the result, which is `25`.

This is a simple example, but it illustrates the basic idea of using functions as objects in higher-order functions. By passing functions as arguments to other functions, we can create more flexible and reusable code.

### Method 2: Functions as a parameter for another function:

In Python, it is common to pass functions as parameters to another function. This is one of the key features of higher-order functions. Here is an example of a higher-order function that takes a function as a parameter:

```def operate_on_list(func, lst):
result = []
for item in lst:
result.append(func(item))
return result

def square(x):
return x ** 2

numbers = [1, 2, 3, 4, 5]
squared_numbers = operate_on_list(square, numbers)
print(squared_numbers)  # Output: [1, 4, 9, 16, 25]
```

In this example, we define a function called `operate_on_list` that takes two arguments: `func` and `lst`. The `func` argument is a function that will be applied to each element in the list `lst`. Inside the function, we loop over each item in `lst`, apply `func` to it, and append the result to a new list called `result`. Finally, we return the `result` list.

We also define a function called `square` that takes one argument and returns its square. We then pass `square` and a list of numbers to `operate_on_list`, which applies `square` to each number in the list and returns a new list of the squared numbers.

This example demonstrates how functions can be passed as parameters to other functions to create more flexible and reusable code. By abstracting the operation that is applied to each element of the list into a separate function, we can easily change the behavior of `operate_on_list` by passing in a different function.

### Method 3: Returning function as a result in high order function:

In Python, higher-order functions can also return functions as their result. Here is an example of a higher-order function that returns a function:

```def create_multiplier(x):
def multiplier(y):
return x * y
return multiplier

double = create_multiplier(2)
triple = create_multiplier(3)

print(double(5))  # Output: 10
print(triple(5))  # Output: 15
```

In this example, we define a function called `create_multiplier` that takes one argument `x`. Inside the function, we define another function called `multiplier` that takes one argument `y`. The `multiplier` function returns the product of `x` and `y`.

Finally, the `create_multiplier` function returns the `multiplier` function. We then call `create_multiplier` twice to create two new functions: `double` and `triple`. The `double` function multiplies its input by 2, and the `triple` function multiplies its input by 3.

We can then call `double(5)` and `triple(5)` to get the results 10 and 15, respectively.

This example demonstrates how higher-order functions can be used to create new functions that have specific behaviors. By returning a function from `create_multiplier`, we can create new functions with different multipliers without having to define a separate function for each multiplier.

### Method 4: Decorators as high order function:

Decorators are a common example of higher-order functions in Python. A decorator is a function that takes another function as its argument and returns a new function that wraps the original function with some additional functionality. Here is an example of a decorator:

```def uppercase_decorator(func):
def wrapper(text):
original_result = func(text)
modified_result = original_result.upper()
return modified_result
return wrapper

@uppercase_decorator
def greet(name):
return f"Hello, {name}!"

print(greet("John"))  # Output: HELLO, JOHN!
```

In this example, we define a decorator function called `uppercase_decorator` that takes another function `func` as its argument. Inside the decorator function, we define a new function called `wrapper` that takes one argument `text`. The `wrapper` function calls the original `func` with the `text` argument, stores the result in a variable called `original_result`, converts the `original_result` to uppercase, and returns the modified result.

We then use the `@uppercase_decorator` syntax to apply the decorator to the `greet` function. This means that when we call `greet`, the `uppercase_decorator` function will be called with `greet` as its argument, and the returned function (`wrapper`) will be used instead of the original `greet` function.

When we call `greet("John")`, the output will be `"HELLO, JOHN!"`. This is because the `uppercase_decorator` has modified the output of the original `greet` function by converting it to uppercase.

This example demonstrates how decorators can be used to modify the behavior of functions without changing their code. By passing a function as an argument to a decorator, we can create a new function that has the same behavior as the original function with some additional functionality.