-> String:

    Multi-line string: 
        For ex:
                        '''
                        Hello 
                        There
                        ''' 

->Formatting String:

    for python 3:
    f'....{<variable-name>}.....{<variable-name}...'

    'Hello there {} and {}'.format(name1, name2)

    'Hello there {1} and {0}'.format(name1, name2)

    {1} : name2
    {0} : name1

    'Hello there {var1} and {var2}'.format(var1 = value, var2 = value)

-> Creating virtual environment:

    For creating virtual environment:

    Use:
        python -m venv <environment-folder-name>
        source .venv/bin/activate
        python -m pip install --upgrade pip setuptools wheel

-> Referencing the list and copying the list:

    for example:

    amazon_cart = [
        'notebooks',
        'sunglasses',
        'toys',
        'grapes'
    ]

    amazon_cart[0] = 'laptop'
    new_cart_2 = amazon_cart[:] # Copying the list
                               # As list slicing creates a new
                                # list

    new_cart_1 = amazon_cart  # Referencing the list 


    print(new_cart_1)
    print(new_cart_2)
    print(amazon_cart)

-> Sorted function and sort method:

    Sorted function creates a new list.

    sort method do the sorting inplace in the list.

-> List unpacking:

    a,b,c = [1,2,3]

    #a = 1 , b = 2 , c = 3

    a, b, c, *other, d = [1, 2, 3, 4, 5, 6, 7]

    #a = 1, b = 2, c = 3, other = [4,5,6], d = 7


-> Dictionary key:

    Dictionary key need to be immutable i.e. value of key shouldn't be
    modified in any case.

    The key also has to unique. If the same key which has been defined
    is used again then the key with the new assigned value will override
    the old value and access the value memory with that same key will
    provide the new overrided value instead of previous value.
    Hence, key of Dictionary should be unique

    tuple can be used as key for Dictionary but not list.
    
-> list and tuple:

    list are mutable but tuple are immutable

-> set:

    set is a unordered collection of unique objects.

    for ex:
        my_set = { 1,2,3,4,5,5 }
        my_set.add(100)
        my_set.add(2)
        print(my_set)

-> using keyword and using methods:

    using keyword to access anything which may not exist 
    might result in error, but to avoid that we can use
    methods instead of keyword for accessing any value
    and avoiding error.

-> difference_update method in sets:

    It is used to delete members which is present in set
    which is passed as parameter to this method and the 
    object which used this method having same elements 
    as present in the set passed as parameter is removed
    and the object set is modified.


-> union and intersection operator of set:

    union : |
    intersection : &

-> Truthy and falsey:

    some value if their type is converted using
    type() function.

    Truthy: Conversion to True value from some value is a
            Truthy value.

    Falsey: Conversion to False value from some value is a
            Falsey value.

    For ex:

        bool("hello") -> True (Truthy)
        bool('')      -> False (Falsey)
        bool(0)       -> False (Falsey)
        bool(199)     -> True  (Truthy)

-> Ternary Operator or conditional expression:

    <statement_if_true> if <condition> else <statement_if_false>

-> Short Circuiting:

    true and true : Both are evaluated
    false and true : first is evaluated
    false and false : first is evaluated
    true or true : first is evaluated
    false or true : both are evaluated
    false or false : both are evaluated

-> Multiple logical operator in one statement:

    for ex:
        a < b > c < d

-> is v/s == :

    == : It checks if the value on both side are same. Both
        side should of same value and type to equate to true.

    is : It check if the location where the value is stored
        same for both side.

-> Type conversion:

    Some operands cannot be compared because of their incompatible type
    and even type conversion will also not be allowed as it would make 
    sence.

    Note: keep in mind what could be compared and what couldn't be.

-> What is a iterable?

    for item in 'Zero to mastery'
                    ^
                    |
                This is a iterable.
    Anything which can be looped through is a iterable.

    Iterable is an object or a collection which can be iterable over.

    #iterable - list, dictionary, tuple, set, string.

-> To iterate in reverse using for loop:

    for ex:
        for x in range(10, 0 , -1):   

-> while loop with else:

    for ex:

    while <condition>:
        ....Statement....
    else:
        ....Statement....

    This else part is executed only when the while condition fails.

-> pass keyword:

    pass keyword doesn't do anything. It's just a dummy statement.
    For example, when we don't what to do inside for loop but still
    want to run the program without throwing error we can use "pass"
    keyword to do nothing indicating some other might replace it later.
    
-> nested functions:

    User-defined function in python can have function defined inside
    another function and its scope is limited that function itself.

    for ex:
        def function1():
            def function2():
        
-> Docstring:

    This can be used inside function to write it as comment
    and mention what the function can do. It's just like a comment.

    for ex:

        def function():
            '''
            What function can do.
            '''


    To print the Docstring. use help(<function-name>) or <fucntion-name>.__doc__
    which is a dunder function.

-> *args && **kwargs:

    *args : It's just like variable argument in C/C++ (var_arg).

    for ex:

        def fun(*args):
            print(args)

    -> args : It stores the number of argument as a tuple.

    **kwargs : It stores argument with keywords. It stores it like a 
                dictionary.

    #Rule: params, *args, default parameters, **kwargs
            name , *args, i = 'hi'          , **kwargs

-> Walrus operator (:=):
 
    for ex:

    if n := len(a) > 0:
        ...do something...

    "n = len(a)" is not allowed in python.
    So, to achieve this we use := operator.

-> Scope in Python:

    Different function have different scope.
    If and other things use global scope.


    Order to check the scope of variable:

    #1 - start with local.
    #2 - Parent local?
    #3 - global
    #4 - built in python functions.

-> global keyword:

    Use this to access global variable in a function scope.

    for ex:

    total = 0

    def count():
        global total
        total += 1
        return 1


-> nonlocal keyword:

    This is the very latest feature of feature

    To access the non-local variable which is not present
    in the local scope of the function but is present in the
    scope of parent function, in case of nested function.

    for ex:

    def outer():
        x = "local"
        def inner():
            nonlocal x
            x = "nonlocal"
            print("inner:", x)

        inner()
        print("outer:", x)

    outer()