<jupyter_start><jupyter_text>Function* A function is a block of code which only runs when it is called.* Functions help break our program into smaller and modular chunks. As our program grows larger and larger, functions make it more organized and manageable.* Furthermore, it avoids repetition and makes the code reusable.* Functions can be both built-in or user-defined. Syntax Of Function```pythondef function_name(parameters):	"""docstring"""	statement(s)```* Above shown is a function definition that consists of the following components.1. Keyword ```def``` that marks the start of the function header.2. A function name to uniquely identify the function. 3. Parameters (arguments) through which we pass values to a function. They are optional.4. A colon (:) to mark the end of the function header.5. Optional documentation string (docstring) to describe what the function does.6. One or more valid python statements that make up the function body. Statements must have the same indentation level7. An optional ```return``` statement to return a value from the function. Example :-<jupyter_code>def my_function():
  print("Hello from a function")

my_function()<jupyter_output>Hello from a function<jupyter_text>Arguments * Information can be passed into functions as arguments.* Arguments are specified after the function name, inside the parentheses. You can add as many arguments as you want, just separate them with a comma.* The following example has a function with one argument (fname). When the function is called, we pass along a first name, which is used inside the function to print the name in a statement:<jupyter_code>def my_function(fname):
  print("Hello ,"+fname + " from the function ")

my_function("Aman")
my_function("Prathamesh")
my_function("Linus")<jupyter_output>Hello ,Aman from the function 
Hello ,Prathamesh from the function 
Hello ,Linus from the function<jupyter_text>Number of Arguments* By default, a function must be called with the correct number of arguments. Meaning that if your function expects 2 arguments, you have to call the function with 2 arguments, not more, and not less.* Example :-<jupyter_code># The function expects 2 arguments, and gets 2 arguments
def my_function(fname, lname):
  print(fname + " " + lname)

my_function("Linus", "Torvald")
def my_function(fname, lname):
  print(fname + " " + lname)

my_function("Linus")<jupyter_output><empty_output><jupyter_text>Keyword Arguments* You can also send arguments with the key = value syntax.* This way the order of the arguments does not matter.* In this example , arguments are passed using the keys so the order does not matter :-<jupyter_code>def my_function(child3, child2, child1):
  print("The youngest child is " + child3)

my_function(child1 = "Dhairya", child2 = "Shreyas", child3 = "Harsh")<jupyter_output>The youngest child is Harsh<jupyter_text>Arbitrary Arguments, *args* If you do not know how many arguments that will be passed into your function, add a * before the parameter name in the function definition* This way the function will receive a tuple of arguments, and can access the items accordingly:<jupyter_code># If the number of arguments is unknown, add a * before the parameter name:
def my_function(*kids):
  print("The youngest child is " + kids[2])

my_function("Dhairya", "Shreyas", "Harsh")<jupyter_output>The youngest child is Harsh<jupyter_text>Arbitrary Keyword Arguments, **kwargs* If you do not know how many keyword arguments that will be passed into your function, add two asterisk: ** before the parameter name in the function definition.* This way the function will receive a dictionary of arguments, and can access the items accordingly* The main difference between args and kwargs is that, args receive tuple as arguments , while kwargs receives dictionary as arguments<jupyter_code>def my_function(**kid):
  print("His last name is " + kid["lname"])

my_function(fname = "Linus", lname = "Torwald")<jupyter_output>His last name is Torwald<jupyter_text>Default Parameter Value* The following example shows how to use a default parameter value.* If we call the function without argument, it uses the default value:<jupyter_code>def my_function(country = "Japan"):
  print("I am from " + country)

my_function("India")
my_function("USA")
my_function()
my_function("Australia")<jupyter_output>I am from India
I am from USA
I am from Japan
I am from Australia<jupyter_text>Passing a List as an Argument* You can send any data types of argument to a function (string, number, list, dictionary etc.), and it will be treated as the same data type inside the function.*  if you send a List as an argument, it will still be a List when it reaches the function:<jupyter_code>def my_function(food):
  for x in food:
    print(x)

fruits = ["apple", "banana", "cherry"]

my_function(fruits)<jupyter_output>apple
banana
cherry<jupyter_text>Return Values* To let a function return a value, use the return statement.* Return statement is used to exit from the function.* Example :-<jupyter_code>def square_value(num):
    return num**2
 
 
print(square_value(2))
 
print(square_value(-4))<jupyter_output>4
16<jupyter_text>Recursion* Python also accepts function recursion, which means a defined function can call itself.* Recursion is a common mathematical and programming concept. It means that a function calls itself. This has the benefit of meaning that you can loop through data to reach a result.* The developer should be very careful with recursion as it can be quite easy to slip into writing a function which never terminates, or one that uses excess amounts of memory or processor power. However, when written correctly recursion can be a very efficient and mathematically-elegant approach to programming.* In this example, we will find factorial of a number using recursion :-<jupyter_code>def factorial(num):
    if num > 1 :
        return num * factorial(num-1)
    else :
        return 1

print(factorial(6))<jupyter_output>720<jupyter_text>Pass by Reference or pass by value?  Mutability* Most python objects (booleans, integers, floats, strings, and tuples) are immutable. This means that after you create the object and assign some value to it, you can’t modify that value.* A mutable object is an object whose value can change.Mutable objects are often objects that can store a collection of data. Lists (Python type list) and dictionaries (Python type dict) are examples of mutable objects.<jupyter_code># since li are mutalbes there id doesn't change
a=[1,4]
print(id(a))
a[0] = 2
print(id(a))
# since integers are immutalbes, once we change there values, binding to them is lost id is changed
a = 1
print(id(a))
a = 3
print(id(a))<jupyter_output>4323486000
4323486064<jupyter_text>Pass by reference * All parameters (arguments) in the Python language are passed by reference.* If you pass immutable arguments like integers, strings or tuples to a function, the passing acts like Call-by-value.<jupyter_code># Here List is mutable hence it is pass by reference
def myFun(x):
    x[0] = 20
 

lst = [10, 11, 12, 13, 14, 15]
myFun(lst)
print(lst)
# Here integer in immutable hence it is passed by value
def myFun(x):
    x = 20
 
 
x = 10
myFun(x)
print(x)
# When we pass a reference and change the received reference to something else, 
# the connection between the passed and received parameter is broken. For example, consider the below program.
def myFun(x):
 
    # After below line link of x with previous
    # object gets broken. A new object is assigned
    # to x.
    x = [20, 30, 40]
 

lst = [10, 11, 12, 13, 14, 15]
myFun(lst)
print(lst)<jupyter_output>[10, 11, 12, 13, 14, 15]<jupyter_text>Docstrings* The first string after the function header is called the docstring and is short for documentation string. It is briefly used to explain what a function does.* Although optional, documentation is a good programming practice. Unless you can remember what you had for dinner last week, always document your code.* In the above example, we have a docstring immediately below the function header . We generally use triple quotes so that docstring can extend up to multiple lines. This string is available to us as the __doc__ attribute of the function.<jupyter_code>def greet(name):
    """
    This function greets to
    the person passed in as
    a parameter
    """
    print("Hello, World")


# To access the documentation of function greet
print(greet.__doc__)<jupyter_output>This function greets to
    the person passed in as
    a parameter<jupyter_text>Lambda Function* In Python, an anonymous function is a function that is defined without a name.* While normal functions are defined using the ```def``` keyword in Python, anonymous functions are defined using the ```lambda``` keyword.* Hence, anonymous functions are also called lambda functions.* In Python, we generally use it as an argument to a higher-order function (a function that takes in other functions as arguments).* Syntax is as follows :- ```pythonlambda arguments: expression```<jupyter_code># lambda function to calculate double of any number
double = lambda x: x * 2

print(double(5))<jupyter_output>10
{'fraction_non_alphanumeric': 0.07299748661348486, 'fraction_numerical': 0.01212982187739045, 'mean_word_length': 5.0131406044678055, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '624917', 'license': 'MIT', 'n_tokens_mistral': 2594, 'n_tokens_neox': 2462, 'n_words': 1239, 'path': '1_python_numpy_basics/6_functions.ipynb', 'repo_name': 'MOLOCH-dev/Pixels_Seminar', 'size': '9151'}