Skillzam

Python Language

HTML5 Home

  • Python is an easy to learn, powerful high-level programming language.
  • Has efficient built-in data structures and a simple but effective approach to object-oriented programming.
  • Python is an interpreted language
    1.  (a) can save time during program development
    2.  (b) No compilation & linking is necessary.
  • Python interpreter can be used interactively
    1.  (a) makes it easy to experiment lang features
    2.  (b) to write throw-away programs, or
    3.  (c) to test functions during bottom-up program development.
  • Python Programs are typically much shorter than equivalent C, C++, or Java programs, for reasons:
    1.  (a) the high-level datatypes allow you to express complex operations in a single statement;
    2.  (b) statement grouping is done by indentation instead of beginning and ending brackets;
    3.  (c) no variable or argument declarations are necessary.
  • Python has elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development.
  • History of Python:

    Python was created by Guido van Rossum, and first released on February 20, 1991.

    Guidovan Rossum

    The Python language is named after the BBC show “Monty Python's Flying Circus” and has nothing to do with reptiles.

    “Monty Python

    Python Packages / Modules:

  • Python is an open-source language. The Python company is one of the largest companies and, still, is free.
  • Python is also suitable as an extension language for customizable applications.
  • Python has become an essential tool for many programmers, engineers, researchers, and data scientists across academia and industry, hence Python sometimes described as a "batteries included" language due to its comprehensive library.
  • The appeal of Python is in its simplicity and beauty, as well as the convenience of the large ecosystem of domain-specific tools that have been built on top of it.
  • For example, most of the Python code in scientific computing and data science is built around a group of mature and useful packages:

  • NumPy provides efficient storage and computation for multi-dimensional data arrays.
  • SciPy contains a wide array of numerical tools such as numerical integration and interpolation.
  • Pandas provides a DataFrame object and powerful set of methods to manipulate, filter, group, and transform data.
  • Matplotlib provides a useful interface for creation of publication-quality plots and figures.
  • Scikit-Learn provides a uniform toolkit for applying common machine learning algorithms to data.
  • IPython/Jupyter provides an enhanced terminal and an interactive notebook environment for exploratory analysis.
  • Python language has many uses:

  • used on a server to create web apps.
  • used to connect to database systems.
  • used alongside software to create workflows.
  • used to read & modify files.
  • used to handle big data & perform complex math.
  • used for rapid prototyping, or for production-ready software development.
  • Python: Install & Run

    Python Installation

    Installing Python and the suite of libraries that enable scientific computing is straightforward whether you use Windows, Linux, or MacOS.

    (A). Python 3 by python.org


    In this Python learning notes, we use the syntax of Python 3, which contains language enhancements that are not compatible with the 2.x series of Python.Though Python 3.0 was first released in 2008, adoption has been relatively slow, particularly in the scientific and web development communities. As of December 22, 2022 - Python has the version 3.11.1 (stable release version).

  • Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python Downloads, and may be freely distributed.
  • The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation.
  • (B). Installation using conda


    Another popular way to install Python, if you wish to eventually use the data science tools - is via the cross-platform Anaconda distribution. There are two flavors of the Anaconda distribution:

  • Miniconda gives you Python interpreter itself, along with a command-line tool called conda which operates as a cross-platform package manager geared toward Python packages, similar in spirit to the apt or yum tools that Linux users might be familiar with.
  • Anaconda includes both Python and conda, and additionally bundles a suite of other pre-installed packages geared toward scientific computing.
  • Any of the packages included with Anaconda can also be installed manually on top of Miniconda.

    Run Python Code

    Python is a flexible and interpreted rather than compiled. It can be executed line by line, which allows a way that is not directly possible with compiled languages like C, C++ or Java.

    There are four primary ways you can run Python code:

  • (A). Python interpreter
  • (B). Jupyter notebook
  • (C). Self-contained Scripts
  • (D). IPython interpreter

  • (A). Python interpreter

  • The most basic way to execute Python code is line by line within the Python interpreter.
  • The Python interpreter can be started by installing the Python language and typing python python3 or just py at the Command Prompt application in Windows ( or look for the Terminal on Mac OS X or Unix/Linux systems):
    
    C:\Users\Skillzam> python
    Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    
    
  • With the interpreter running, you can begin to type and execute code snippets. More convenient to try out small snippets of Python code. Here we'll use the interpreter as a simple calculator, performing calculations and assigning values to variables:
    
    >>> 12 + 13
    25
    >>> 21 - 1
    20
    >>> num = 9
    >>> num * 9
    81
    
    

  • (B). Jupyter notebook

  • Project Jupyter is a non-profit, open-source project, born out of the IPython Project in 2014 as it evolved to support web-based interactive data science and scientific computing across all programming languages.
  • Jupyter will always be 100% open-source software, free for all to use and released under the liberal terms of the modified BSD license.
  • Jupyter is developed in the open on GitHub, through the consensus of the Jupyter community.
  • Jupyter Notebook is a document format that allows executable code, formatted text, graphics, and even interactive features to be combined into a single document.
  • Jupyter Notebook is useful both as a development environment, and as a means of sharing work via rich computational and data-driven narratives that mix together code, figures, data, and text.
  • Jupyter notebooks have the file extension .ipynb
    1.     [1]. Installing Jupyter through Anaconda Distribution
    2. Anaconda, the world's most popular open-source Python distribution platform will install Jupyter Notebooks by default.
    3. Once installed, you can launch Jupyter Notebooks from Anaconda Navigator.
    1.     [2]. Installing Jupyter by pip
    2. Project Jupyter's tools are available for installation via the Python Package Index, the leading repository of software created for the Python programming language.
    3. JupyterLaB
      1. (a). Install JupyterLab with pip:
        
        C:\Users\Skillzam> pip install jupyterlab
        
        
      2. (b). Once installed, launch JupyterLab with: jupyter-lab
    4. Jupyter Notebook
      1. (a). Install the classic Jupyter Notebook with:
        
        C:\Users\Skillzam> pip install notebook
          
      2. (b). To run the notebook:
      3. 
        C:\Users\Skillzam> jupyter notebook
          


    (C). Self-contained Scripts

  • For running complicated Python programs, it is more convenient to save code to file, and execute it all at once.
  • By convention, Python scripts are saved in files with a .py extension.
  • For example, let's create a program file called main.py which contains the following:
    
    # Python Program file: main.py
    msg = "Hello World!"
    print(msg)
                  
    
  • To run this file, make sure it is in the current directory and type python <filename> at the command prompt:
    
    C:\Users\Skillzam> python main.py
    Hello World!
    
    

  • (D). IPython interpreter

  • Basic Python interpreter at command prompt lacks many of the features of a full-fledged interactive development environment.
  • An alternative interpreter called IPython (for Interactive Python) is bundled with the Anaconda distribution, and includes a host of convenient enhancements to the basic Python interpreter.
  • It can be started by typing ipython at the command prompt:
  • 
    C:\Users\Skillzam> ipython
    Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)]
    Type 'copyright', 'credits' or 'license' for more information
    IPython 8.7.0 -- An enhanced Interactive Python. Type '?' for help.
    
    In [1]:         
          
    
  • The cosmetic difference between the Python interpreter and the enhanced IPython interpreter lies in the command prompt: Python uses >>> by default, while IPython uses numbered commands like In [1] :
    However, we can still execute code line by line interactively:
    
    In [1]: 55 + 2
    Out[1]: 57
    
    In [2]: myText = "Skillzam - Learn without limits!"
    
    In [3]: print(myText)
    Skillzam - Learn without limits!  
    
    
  • Python language syntax

  • Python was originally developed as a teaching language, but its ease of use and clean syntax have led it to be embraced by beginners and experts alike.
  • The cleanliness of Python's syntax has led some to call it "executable pseudocode", and indeed many developer feel that it is often much easier to read and understand a Python script than to read a similar script written in, say, C.
  • Python is an interpreted programming language, this means that as a developer you write Python (.py) files in a text editor and then put those files into the python interpreter to be executed.
  • Syntax refers to the structure of the language, which is, what constitutes a correctly-formed program.

    Observe the below code:

    
    # Program to add two numbers
    
    # function declaration for adding two numbers
    def addNum(num1, num2):
        total = num1 + num2
        return (total)
    
    value1 = 0; value2 = 0;
    value1 = int(input('Enter the first number: ')) # Input first number
    value2 = int(input('Enter the second number: ')) # Input second number
    
    # Invoking the function addNum() and Printing the Output 
    print(f'The sum of {value1} and {value2} is {addNum(value1, value2)}')            
                
    
    OUTPUT
      Enter the first number: 24
      Enter the second number: 12
      The sum of 24 and 12 is 36
    

    This above script illustrates several of the important aspects of Python syntax.
    Let's walk through it and discuss some of the syntactical features of Python.

    Comments are marked by #

  • The script starts with a comment:
  • 
    # Program to add two numbers                               
    
    
  • Comments in Python are indicated by a pound (or hashtag) sign # and anything on the line following the pound sign is ignored by the interpreter.
  • This means, for example, that you can have stand-alone comments like the one just shown, as well as inline comments that follow a statement. For example:
    
    value1 = int(input('Enter the first number: ')) # Input first number
    
    
  • Python does not have any syntax for multi-line comments, such as the /* ... */ syntax used in C and C++. But, since Python will ignore string literals that are not assigned to a variable, you can add a multiline string (triple quotes) in your code, and place your comment inside it
    
    ''' This is the
        demo of un-assigned
        multi-line string used 
        as comment in Python
    '''
                                        
    
  • End-of-Line terminates a statement

  • The first line in the function declaration is:
    
    total = num1 + num2
                
    
  • This is an assignment operation, where a variable named total is assigned to sum of two numbers num1 + num2
  • Notice that the end of this statement is simply marked by the end of the line. This is in contrast to languages like C and C++, where every statement must end with a semicolon ;
  • In Python, if you'd like a statement to continue to the next line, it is possible to use the "\" marker to indicate this:

    
    sumSingleDigits = 0 + 1 + 2 + 3 + 4 +\
    5 + 6 + 7 + 8 + 9        
    
    

    It is also possible to continue expressions on the next line within parentheses ( ), without using the "\" marker:

    
    sumSingleDigits = (0 + 1 + 2 + 3 + 4 +
    5 + 6 + 7 + 8 + 9)
              
    

    Semicolon can optionally terminate a statement

    Sometimes it can be useful to put multiple statements on a single line. This shows in the example, how the semicolon ; familiar in C Language, can be used optionally in Python to put two statements on a single line.

    
    value1 = 0; value2 = 0;
              
    

    Functionally, this is entirely equivalent to writing

    
    value1 = 0
    value2 = 0
              
    

    Indentation: Whitespace matters

    In the function declaration, the block of code looks:

    
    # function declaration for adding two numbers            
    def addNum(num1, num2):
      total = num1 + num2
      return (total)
              
    

    This demonstrates what is perhaps the most controversial feature of Python's syntax: whitespace is meaningful!

    In programming languages, a block of code is a set of statements that should be treated as a unit. In JavaScript, for example, code blocks are denoted by curly braces { }

    
    // JavaScript code
    for (int i = 0; i < 10; i++) { 
      // curly braces indicate code block
      total = total + i;
    }
              
    

    In Python, code blocks are denoted by indentation. In Python, indented code blocks are always preceded by a colon (:) on the previous line.

    
    # Python code
    for i in range(10):
      # indentation indicates code block
      total = total + i;
    
    

    The use of indentation helps to enforce the uniform, readable style that many find appealing in Python code. But it might be confusing to the uninitiated; for example, the following two snippets will produce different results:

    In the below snippet, print() method is in the indented block, and will be executed only if mangoPrice is less than 500.

    
    if mangoPrice < 500:
      totalCost = mangoPrice * 2
      print(totalCost)
    
    

    In the below snippet, print() method is outside the indented block, and will be executed regardless of the value of mangoPrice.

    
    if mangoPrice < 500:
      totalCost = mangoPrice * 2
    print(totalCost)
    
    

    NOTE that the amount of whitespace used for indenting code blocks is up to the user, as long as it is consistent throughout the script. By convention, most style guides recommend to indent code blocks by four spaces TAB and that is the convention to follow.

    Whitespaces within lines does not matter

    While the mantra of meaningful whitespace holds true for whitespace before lines (which indicate a code block), white space within lines of Python code does not matter

    For example, all three of these expressions are equivalent:

    
    sumNumbers=12+34
    sumNumbers = 12 + 34
    sumNumbers    =    12    +    34
    
    

    Abusing this flexibility can lead to issues with code readibility - in fact, abusing white space is often one of the primary means of intentionally obfuscating code (which some people do for sport).

    Using whitespace effectively can lead to much more readable code, especially in cases where operators follow each other - compare the following two expressions for exponentiating by a negative number:

    
    squareNumber=9**2
    # Check the below with SPACES
    squareNumber = 9 ** 2
                
    

    Observe that second version with spaces much is more easily readable at a single glance. Most Python style guides recommend using a single space around binary operators, and no space around unary operators.

    Parentheses are for grouping or calling

    Two major uses of parentheses. First, they can be used in the typical way to group statements or mathematical operations:

    
    12 * (34 + 56) 
    # 1080
    
    

    Secondly, they can also be used to indicate that a function is being invoked (called).

    In the below snippet, the addNum() is used to call the function with two arguments. The function call is indicated by a pair of opening and closing parentheses, with the arguments to the function contained within:

    
    addNum(value1, value2)
                
    

    Some functions can be called with no arguments at all, in which case the opening and closing parentheses still must be used to indicate a function evaluation. An example of this is the upper() method of lists:

    
    firstString = 'Skillzam'.upper()
    print(firstString)
    
    # SKILLZAM
    
    

    The "()" after upper indicates that the function should be executed, and is required even if no arguments are necessary.

    print() is function NOT a statement anymore

    The print() function is one piece that has changed between Python 2.x and Python 3.x. In Python 2, print behaved as a statement: that is, you could write

    
    # In Python 2.x versions
    >> print "Hello World!"
    Hello World!
    
    

    For various reasons, the language maintainers decided that in Python 3 print() should become a function, so we now write

    
    # In Python 3.x versions
    >>> print("Hello World!")
    Hello World!
                
    

    This is one of the many backward-incompatible constructs between Python 2 and 3.

    Style Guide for Python code

    Python "style guides", which can help teams to write code in a consistent style. The most widely used style guide in Python is known as PEP8 (Python Enhancement Proposals 8). As you begin to write more Python code, it would be useful to read through this!

    Python Variables & Objects

  • The syntax of a programming language refers to structure of the language, that is, what constitutes a legal program.
  • The semantics of a programming language refers to the meaning of a legal program.
  • We will now understand the semantics of keywords identifier variables objects which are the main ways you store, reference, and operate on data within a Python script.

    Keywords

    Every programming language has special reserved words, or keywords, that have specific meanings and restrictions around how they should be used. Python is no different. Python keywords are the fundamental building blocks of any Python program.

  • Python keywords are special reserved words that have specific meanings and purposes which cannot be used as variable names, function names, or any other identifiers.
  • Keywords are always available — you'll never have to import them into your code.
  • Python keywords are different from Python's built-in functions and types. The built-in functions and types are also always available, but they aren't as restrictive as the keywords in their usage.
  • As of Python 3.11.1, there are thirty-five(35) keywords in Python
  • Here is a list of 35 keywords in Python Language:
    and continue finally is raise
    as def for lambda return
    assert del from None True
    async elif global nonlocal try
    await else if not while
    break except import or with
    class False in pass yield

    Identifiers

  • Python Identifier is the name given to identify a variable, function, class, module or other object.
  • That means, whenever we want to give an entity a name, that's called identifier.
  • Sometimes variable and identifier are often misunderstood to be same, but they are not.
  • Variables

  • In Python, variables are not as containers/buckets but as pointers
  • There is no command for declaring a variables in Python.
  • Variables are created the moment you assign a value to the identifier. Variable assignment follows name = object
  • Python variable names themselves have no attached type information
  • Python supports "Dynamic Typing". Dynamically-typed languages are those, where the interpreter assigns variables a type at runtime based on the variable's value at the time.

      Pros of Dynamic Typing
    1.     (a). very easy to work with
    2.     (b). faster development time
      Cons of Dynamic Typing
    1.     (a). may result in unexpected bugs
    2.     (b). need to be aware of type() method
  • In Python everything is an object, which means every entity has some metadata also called attributes and methods
  • Assigning values to Variables

  • Variable assignment follows name = object
  • The assignment operator, denoted by the “=” symbol, is the operator that is used to assign values to variables
  • Single value assigned to one variable

    
    favColor = 'Blue'                                    
                
    

    Multiple values assigned to multiple variables

    Make sure the number of variables matches the number of values, or else you will get an error.

    
    aaplStockPrice, tslaStockPrice, msftStockPrice =  131.86, 123.15, 238.73                          
      
    

    Single value assigned to multiple variables

    
    messiGoals = neymarGoals = ronaldoGoals =  "SAME"                          
      
    

    Naming Variable

  • Must start with a letter or the underscore character
  • Name cannot start with a number
  • Name cannot contain SPACES
  • Name can only contain alpha-numeric characters and underscores (A-z, 0-9, _ )
  • Names are case-sensitive
  • Names cannot use Python keywords / reserved words
  • Names cannot use these special symbols: '  ,  "  <  >  /  \  ?  |  (  )  !  @  #  $  %  ^  &  *  ~  -  +  
  • 
    # Naming Variables & assignment 
    
    price = 120         # using '=' operator for assigning value
    Price = 24          # price & Price are different as Python is case-sensitive
    _interest = 2.25    # '_' is a valid usage in variable name
    QTY = 3             # All-caps is valid usage in variable name                                    
                
    
    
    # Naming Variables : special Symbols not Allowed
    
    $stockPrice = 123    # SyntaxError: invalid syntax                                    
                
    
    
    # Naming Variables : SPACES not Allowed
    
    pin Code = 17011    # SyntaxError: invalid syntax                         
                
    
    
    # Naming Variables : Cannot start with number
    
    1stItemDiscount = 15    # SyntaxError: invalid syntax                  
                
    
    
    # Naming Variables : Cannot use Keywords/Reserved words
    
    lambda = 0.022    # SyntaxError: invalid syntax
                
    

    Variable names with multiple words

  • User defined Variable names with more than one word can be difficult to read.
  • There are several ways you can use to make them more readable:
  • 1. Camel Case

    Variable names where each word, except the first, starts with a capital letter:

    
    firstName = 'Jack'                                    
                
    

    2. Snake Case

    Variable names where each word is separated by an underscore ( _ ) character:

    
    last_name = 'Sparrow'                           
      
    

    3. Pascal Case

    Variable names where each word starts with a capital letter:

    
    MyFavoriteTeam = 'F C Barcelona'
    
    

    Finding variable type

    The built-in type() method/function is used to get the type of an object/variable.

  • int (for integers)
  • float (for decimal numbers)
  • complex ( for complex numbers)
  • str (for strings)
  • bool (for Boolean)
  • NoneType (for None)
  • list (for lists)
  • tuple (for tuples )
  • dict (for dictionaries)
  • set (for sets)
  • 
    intNum = 123
    type(intNum)      
    
    # int  
    
    
    
    decimalNum = 3.142
    type(decimalNum)      
    
    # float
    
    
    
    complexNum = 3 + 4j
    type(complexNum)             
    
    # complex
    
    
    
    CompanyName = "Workzam"
    type(CompanyName)                    
    
    # str
    
    
    
    isGreater = True
    type(isGreater)         
    
    # bool
    
    
                           
    randNum = None
    type(randNum)
    
    # NoneType
    
    
    
    myList = ['s','k','i','l','l','z','a','m']
    type(myList)      
    
    # list 
    
    
    
    tickerPrice = ('aapl', 131.86)
    type(tickerPrice)        
      
    # tuple
    
    
    
    fifaFinale = { "Argentina":4, "France": 2 }
    type(fifaFinale)   
      
    # dict
    
    
    
    teams = {"Arsenal","Barcelona","PSG"}
    type(teams)       
      
    # set
    
    

    Python variables are pointers

    Assigning variables in Python is as easy as putting a variable name to the left of the equals = sign:

    
    # assign 8128 to the variable 'perfectNum'
    perfectNum = 8128
    
    # assign 3.142 to the variable 'valuePI'
    valuePI = 3.142
    
    # assign Skillzam to the variable 'academy'
    academy = 'Skillzam'
    
    # assign True to the variable 'isGreater'
    isGreater = True
    
    # assign None to the variable 'varName'
    varName = None
    
    

    This may seem straightforward, but if you have the wrong mental model of what this operation does, the way Python works may seem confusing. We'll briefly dig into that here.

    In many programming languages, variables are best thought of as containers or buckets into which you put data.

    Variable Bucket

    So in C Language, for example, when you write

    
    // C code
    int perfectNum = 8128;  
    
    

    you are essentially defining a "memory bucket" named perfectNum, and putting the value 8128 into it. In Python, by contrast, variables are best thought of not as containers but as pointers. So in Python, when you write

    
    # Python code            
    perfectNum = 8128     
    
    

    you are essentially defining a pointer named perfectNum that points to some other bucket containing the value 8128.

    Note one consequence of this: because Python variables just point to various objects, there is no need to "declare" the variable, or even require the variable to always point to information of the same type!

    This is the sense in which people say Python is dynamically-typed : variable names can point to objects of any type.

    Dynamically-typed languages are those, where the interpreter assigns variables a type at runtime based on the variable's value at the time.

    So in Python, you can do things like this:

    
    varName = 789           # varName is an integer here
    print('1: int',id(varName))
    varName = 'Workzam'     # varName is a string now
    print('2: str',id(varName))
    varName = [12, 34, 56]  # varName is a list now
    print('3: list',id(varName))   
    
    
    OUTPUT
    
    1: int 2337616203024
    2: str 2337615666096
    3: list 2337616495808
    
    

    While users of statically-typed languages might miss the type-safety that comes with declarations like those found in C,

    
    perfectNum = 8128     
    
    

    this dynamic typing is one of the pieces that makes Python so quick to write and easy to read.

    There is a consequence of "variable as pointer" approach in Python.

    If we have two variable names pointing to the same mutable variable/object, then changing one will change the other as well! For example, let's create and modify a list:

    
    # Python 'list' is created            
    listNameOne = [9, 8, 7]
    listNameTwo = listNameOne     
    
    

    We've created two variables listNameOne and listNameTwo which both point to the same object. Because of this, if we modify the list via one of its names, we'll see that the "other" list will be modified as well:

    
    print(listNameTwo)
    
    # [9, 8, 7]
    
    
    
    listNameOne.append(6)  # append 6 to the list pointed to by listNameOne
    print(listNameTwo)     # listNameTwo's list is modified as well!
    print('1:listNameOne',id(listNameOne))
    print('2:listNameTwo',id(listNameTwo))   
    
    
    OUTPUT
    
    [9, 8, 7, 6]
    1:listNameOne 2337616485568
    2:listNameTwo 2337616485568
    
    

    This behavior might seem confusing if you're wrongly thinking of variables as buckets that contain data. But if you're correctly thinking of variables as pointers to objects, then this behavior makes sense.

    Note also that if we use = to assign another value to listNameOne, this will not affect the value of listNameTwo - assignment is simply a change of what object the variable points to:

    
    listNameOne = 'Some new value'
    print(listNameTwo)  # listNameTwo is unchanged
    
    print('1:listNameOne',id(listNameOne))
    print('2:listNameTwo',id(listNameTwo))  
        
    
    OUTPUT
    
    [9, 8, 7, 6]
    1:listNameOne 2337615565616
    2:listNameTwo 2337616485568  
    
    

    Again, this makes perfect sense if you think of listNameOne and listNameTwo as pointers, and the = operator as an operation that changes what the name points to.

    You might wonder whether this pointer idea makes arithmetic operations in Python difficult to track, but Python is set up so that this is not an issue. Numbers, strings, and other simple types are immutable: you can't change their value – you can only change what values the variables point to. So, for example, it's perfectly safe to do operations like the following:

    
    x_axis = 12
    y_axis = x_axis
    x_axis += 4      # add 4 to x_axis' value, and assign it to x_axis
    print("x-axis value =", x_axis)
    print("y-axis value =", y_axis)  
    
    
    OUTPUT
    
    x-axis value = 16
    y-axis value = 12
    
    

    When we call x_axis += 4, we are not modifying the value of the 12 object pointed to by x_axis; we are rather changing the variable x_axis so that it points to a new integer object with value 16. For this reason, the value of y_axis is not affected by the operation.

    Example to demo variables as pointers

    Before you jump into understanding Python Variables, let us consider these below "Base object types" :

  • PyObject : All object types are extensions of this type. This is a type which contains the information Python needs to treat a pointer to an object as an object . In a normal “release” build, it contains only the object's reference count and a pointer to the corresponding type object. Nothing is actually declared to be a PyObject, but every pointer to a Python object can be cast to a PyObject*. Access to the members must be done by using the macros Py_REFCNT and Py_TYPE.
  • Py_REFCNT (PyObject *o) : Get the reference count of the Python object o.
  • Py_TYPE (PyObject *o) : Get the type of the Python object o.
  • For more details, refer Python common object structures.

    Let us now consider the below examples:

    
    nameOne = 'Skill'
    print(id(nameOne), 'nameOne: '+ nameOne)    
    
    
    OUTPUT
    
    2337616224048 nameOne: Skill
    
    
    Variable Bucket
    
    nameOne = nameOne + 'zam'
    print(id(nameOne), 'nameOne: '+ nameOne)  
    
    
    OUTPUT
    
    2337615651312 nameOne: Skillzam
    
    
    Variable Bucket
    
    nameTwo = nameOne
    print(id(nameTwo), 'nameTwo: '+ nameTwo)
                    
    
    OUTPUT
    
    2337615651312 nameTwo: Skillzam
    
    
    Variable Bucket
    
    nameOne = 'Workzam'
    print(id(nameOne), 'nameOne: '+ nameOne)
                
    
    OUTPUT
    
    2337615672880 nameOne: Workzam
    
    
    Variable Bucket

    Everything is an Object in Python

    Python is an object-oriented programming language, and in Python everything is an object.

    Earlier we saw that, variables are simply pointers, and the variable names themselves have no attached type information. This leads some to claim erroneously that Python is a type-free language. But this is not the case!

    Consider the following:

    
    stockPrice = 112
    type(stockPrice)    
    
    # int
    
    
    
    inr = 'Indian Rupee'
    type(inr)  
    
    # str
    
    
    
    dollarUS = 82.82  # USD exchange rate in INR
    type(dollarUS)  
    
    # float
    
    

    isinstance() function returns True, if the specified object is of the specified type, otherwise False

    
    isinstance(stockPrice, object)     
    # True
    
    
    
    isinstance(inr, object)   
    # True
    
    
    
    isinstance(dollarUS, object)
    # True
    
    

    Python has types; however, the types are linked not to the variable names but to the objects themselves.

    In object-oriented programming languages like Python, an object is an entity that contains data along with associated metadata and/or functionality. In Python everything is an object, which means every entity has some metadata also called attributes and associated functionality also called methods.

    These attributes and methods are accessed via the dot syntax.

    For example, before we saw that lists have an append method, which adds an item to the list, and is accessed via the dot "." syntax:

    
    # Single Digit Prime Numbers
    primeNumbers = [2, 3, 5]
    primeNumbers.append(7)
    print(primeNumbers)
    isinstance(primeNumbers, object)   
    
    
    OUTPUT
    
    [2, 3, 5, 7]
    True
    
    

    While it might be expected for compound objects like lists to have attributes and methods, what is sometimes unexpected is that in Python even simple types have attached attributes and methods.

    For example, numerical types have a real and imag attribute that returns the real and imaginary part of the value, if viewed as a complex number:

    
    complexNumber = 9.8
    print(complexNumber.real, "+", complexNumber.imag, 'i')
    isinstance(complexNumber, object)  
    
    
    OUTPUT
    
    9.8 + 0.0 i
    
    

    Methods are like attributes, except they are functions that you can call using opening and closing parentheses.

    For example, floating point numbers have a method called is_integer() that checks whether the value is an integer:

    
    floatNumber = 33.33
    print(floatNumber.is_integer())
    isinstance(floatNumber.is_integer(), object)       
    
    
    OUTPUT
    
    False
    True
    
    

    When we say that everything in Python is an object, we really mean that everything is an object – even the attributes and methods of objects are themselves objects with their own type information:

    
    type(intNumber.is_integer())     
    
    # bool
    
    

    Python Operators

    Now we'll dig into the semantics of the various operators included in Python language.

    Listed below are the major Operator categories:

  • Arithmetic Operators
  • Bitwise Operators
  • Assignment Operators
  • Comparison Operators
  • Boolean Operators
  • Identity & Membership Operators
  • 1. Arithmetic Operators

    Python implements seven basic binary arithmetic operators, two of which can double as unary operators. They are summarized in the following table:

    Listed below are the links to the Examples
    Operator Name Description
    a + b Addition Sum of a and b
    a - b Subtraction Difference of a and b
    a * b Multiplication Product of a and b
    a / b True division Quotient of a and b
    a // b Floor division Quotient of a and b, removing fractional parts
    a % b Modulus Integer Remainder after division of a by b
    a ** b Exponentiation a raised to the power of b
    -a Unary Negation The negative of a
    +a Unary plus a unchanged (rarely used)

    These operators can be used and combined in intuitive ways, using standard parentheses ( ) to group operations. For example:

    
    # addition, subtraction, multiplication
    (12 + 34) * (12.3 - 4)     
    
    # 381.8
    
    

    Floor division is true division with fractional parts truncated:

    
    # True division
    print(22 / 7)    
    
    # 3.142857142857143
    
    
    
    # Floor division
    print(22 // 7)      
    
    # 3
    
    

    The floor division operator was added in Python 3.

    Finally, an eighth arithmetic operator that was added in Python 3.5:

    a @ b operator, which is meant to indicate the matrix product of a and b, for use in various linear algebra packages.

    
    # demonstrate a @ b operator; matrix product of a and b
    
    import numpy as np
    
    matrixA = np.matrix('1 2; 3 4')
    print(f"matrix A  = \n {matrixA}")
    
    matrixB = np.matrix('5 6; 7 8')
    print(f"matrix B  = \n {matrixB}")
    
    prodMatrix = matrixA @ matrixB    # the matrix product of matrixA and matrixB
    print(f"Product of matrixA and matrixB  = \n {prodMatrix}")
                    
    
    OUTPUT
    
    matrix A  = 
     [[1 2]
     [3 4]]
    matrix B  = 
     [[5 6]
     [7 8]]
    Product of matrixA and matrixB  = 
     [[19 22]
     [43 50]]
    
    

    2. Bitwise Operators

    Python includes operators to perform bitwise logical operations on integers.

    Bitwise operators are much less commonly used than the standard arithmetic operations.

    The six bitwise operators are summarized in the following table:

    Listed below are the links to the Examples
    Operator Name Description
    a & b Bitwise AND Bits defined in both a and b
    a | b Bitwise OR Bits defined in a or b or both
    a ^ b Bitwise XOR Bits defined in a or b but not both
    a << b Bit shift left Shift bits of a left by b units
    a >> b Bit shift right Shift bits of a right by b units
    ~a Bitwise NOT Bitwise negation of a

    These bitwise operators only make sense in terms of the binary representation of numbers, which you can see using the built-in bin( ) method:

    
    # bin() function returns the binary version of a specified integer
    bin(10)        
    
    # '0b1010'
    
    

    The result is prefixed with '0b', which indicates a binary representation. The rest of the digits indicate that the number 10 is expressed as the sum 1*23 + 0*22 + 1*21 + 0*20 = 10 .

    Similarly, we can write number 9 in binary as below:

    
    bin(9)
    
    # '0b1001'
    
    

    Now, using bitwise OR we can find the number which combines the bits of 9 and 10:

    
    9 | 10
    
    #   00001001 (9 in Binary)
    # | 00001010 (10 in Binary)
    #___________
    #   00001011 (11 in Binary)
    
    
    OUTPUT
     
    11
    
    
    
    bin(9 | 10)
    
    #   00001001 (9 in Binary)
    # | 00001010 (10 in Binary)
    #___________
    #   00001011 (11 in Binary)   
    
    
    OUTPUT
     
    '0b1011'
    
    

    Bitwise operators are not as immediately useful as the standard arithmetic operators, but it's helpful to see them at least once to understand what class of operation they perform.

    In particular, users from other languages are sometimes tempted to use XOR (i.e., a ^ b) when they really mean exponentiation (i.e., a ** b).

    3. Assignment Operators

    Already seen that variables can be assigned with the = operator, and the values stored for later use.

    
    assignNum = 66
    print(assignNum)      
    
    # 66
    
    

    We can use these variables in expressions with any of the operators mentioned earlier.

    For example, to add 6 to assignNum we write:

    
    assignNum + 6
    
    # 72
    
    

    We might want to update the variable assignNum with this new value; in this case, we could combine the addition and the assignment and write assignNum = assignNum + 6. Because this type of combined operation and assignment is so common, Python includes built-in update operators for all of the arithmetic operations:

    
    assignNum += 6  # equivalent to assignNum = assignNum + 6
    print(assignNum)      
    
    # 72
    
    

    There is an augmented assignment operator corresponding to each of the binary operators listed earlier; in brief, they are:

    Operator Description Same as
    a += b Add and equal operator a = a + b
    a -= b Subtract and equal operator a = a - b
    a *= b Asterisk and equal operator a = a * b
    a /= b Divide and equal operator a = a / b
    a //= b Double divide and equal operator a = a // b
    a **= b Exponent and equal operator a = a ** b
    a %= b Modulus and equal operator a = a % b
    a &= b Bitwise AND Assignment Operator a = a & b
    a|= b Bitwise OR Assignment Operator a = a | b
    a ^= b Bitwise XOR Assignment Operator a = a ^ b
    a <<= b Bitwise LEFT shift assignment operator a = a << b
    a >>= b Bitwise RIGHT shift assignment operator a = a >> b

    Each one is equivalent to the corresponding operation followed by assignment: that is, for any operator "■" the expression a ■= b is equivalent to a = a ■ b with a slight catch.

    For mutable objects like lists, arrays, or DataFrames, these augmented assignment operations are actually subtly different than their more verbose counterparts: they modify the contents of the original object rather than creating a new object to store the result.

    
    # Demo the Add and Assignment Operators 
    num1 = 6  
    num2 = 2  
    num1 += num2    # num1 = num1 + num2
    print( "Value of num1 = ", num1)  
            
    
    OUTPUT
    
    Value of num1 =  8 
    
    
    
    # Demo the Substract and Assignment Operators 
    num1 = 6  
    num2 = 2  
    num1 -= num2    # num1 = num1 - num2
    print( "Value of num1 = ", num1)              
                
    
    OUTPUT
     
    Value of num1 =  4
    
    
    
    # Demo the Multiple and Assignment Operators 
    num1 = 6  
    num2 = 2  
    num1 *= num2    # num1 = num1 * num2
    print( "Value of num1 = ", num1)            
                
    
    OUTPUT
     
    Value of num1 =  12
      
    
    
    # Demo the Divide and Assignment Operators 
    num1 = 22  
    num2 = 7  
    num1 /= num2    # num1 = num1 / num2
    print( "Value of num1 = ", num1)       
                
    
    OUTPUT
     
    Value of num1 =  3.142857142857143
      
    
    
    # Demo the Double Divide and Assignment Operators 
    num1 = 22  
    num2 = 7  
    num1 //= num2    # num1 = num1 // num2
    print( "Value of num1 = ", num1)  
                
    
    OUTPUT
     
    Value of num1 =  3
      
    
    
    # Demo the Exponent and Assignment Operators 
    num1 = 3  
    num2 = 2  
    num1 **= num2    # num1 = num1 ** num2
    print( "Value of num1 = ", num1)       
                
    
    OUTPUT
     
    Value of num1 =  9
      
    
    
    # Demo the Modulus  and Assignment Operators 
    num1 = 7  
    num2 = 4  
    num1 %= num2    # num1 = num1 % num2
    print( "Value of num1 = ", num1)        
                
    
    OUTPUT
     
    Value of num1 =  3
      
    
    
    # Demo the Bitwise AND Assignment Operator 
    num1 = 6  
    num2 = 13  
    num1 &= num2    # num1 = num1 & num2
    
    #   0110  (6 in binary)  
    # & 1101  (13 in binary)  
    #  ________  
    #   0100 = 4 (In decimal)  
    
    print( "Value of num1 = ", num1)  
      
    
    OUTPUT
     
    Value of num1 =  4
      
    
    
    # Demo the Bitwise OR Assignment Operator
    num1 = 6  
    num2 = 13  
    num1 |= num2    # num1 = num1 | num2
    
    #   0110  (6 in binary)  
    # | 1101  (13 in binary)  
    #  ________  
    #   0100 = 4 (In decimal)   
    
    print( "Value of num1 = ", num1)  
                
    
    OUTPUT
     
    Value of num1 =  15
      
    
    
    # Demo the Bitwise XOR Assignment Operator
    num1 = 6  
    num2 = 13  
    num1 ^= num2    # num1 = num1 ^ num2
    
    #   0110  (6 in binary)  
    # | 1101  (13 in binary)  
    #  ________  
    #   0100 = 4 (In decimal)   
    # ^ 1011 = 11 (In decimal) [Negation of OR is XOR]
    
    print( "Value of num1 = ", num1)  
          
    
    OUTPUT
    
    Value of num1 =  11
      
    
    
    # Demo the Bitwise LEFT shift assignment operator
    num1 = 6  
    num2 = 2 
    num1 <<= num2    # num1 = num1 << num2
    
    #    00000110  (6 in binary) [ LEFT shift by 2] 
    #    00011000  (24 in binary)  
    
    print( "Value of num1 = ", num1)  
          
                
    
    OUTPUT
    
    Value of num1 =  24
      
    
    
    # Demo the Bitwise RIGHT shift assignment operator
    num1 = 19  
    num2 = 1 
    num1 >>= num2    # num1 = num1 >> num2
    
    #    00010011  (19 in binary) [ RIGHT shift by 1] 
    #    00001001  (9 in binary)  
    
    print( "Value of num1 = ", num1)  
                
    
    OUTPUT
     
    Value of num1 =  9
      
    

    4. Comparison Operators

    Python implements standard comparison operators, which return Boolean values True and False.

    The comparison operations are listed in the following table:

    Operation Description
    a == b a equal to b
    a != b a not equal to b
    a < b a less than b
    a > b a greater than b
    a <= b a less than or equal to b
    a >= b a greater than or equal to b

    Comparison operators can be combined with the arithmetic and bitwise operators to express a virtually limitless range of tests for the numbers.

    For example, we can check if a number is odd by checking that the modulus with 2 returns 1:

    
    # 13 is odd
    13 % 2 == 1      
    
    # True
    
    
    
    # 24 is even
    24 % 2 == 0
    
    # True
    
    

    We can string-together multiple comparisons to check more complicated relationships:

    
    # check if givenNum is between 9 and 19
    givenNum = 18
    9 < givenNum < 19      
    
    # True
    
    

    5. Boolean Operators

    When working with Boolean values, Python provides operators to combine the values using the standard concepts of "and", "or", and "not".

    Predictably, these operators are expressed using the words and , or , not :

    
    testNum = 11
    (testNum < 15) and (testNum > 10)   
    
    # True
    
    
    
    (testNum > 9) or (testNum % 2 == 0)
    
    # True
    
    
    
    not (testNum < 7)
    
    # True
    
    

    Boolean algebra aficionados might notice that the XOR operator is not included; this can of course be constructed in several ways from a compound statement of the other operators.

    Otherwise, a clever trick you can use for XOR of Boolean values is the following:

    
    # (testNum > 1) xor (testNum < 10)
    (testNum > 1) != (testNum < 10)
                
    # True            
    
    

    These sorts of Boolean operations will become extremely useful when we begin discussing control flow statements such as conditionals and loops

    6. Identity & Membership Operators

    Like and , or , not , Python also contains prose-like operators to check for identity and membership. They are the following:

    Operator Description
    a is b True, if a and b are identical objects
    a is not b True, if a and b are not identical objects
    a in b True, if a is a member of b
    a not in b True, if a is not a member of b

    (a). Identity Operators: is and is not


    The identity operators, "is" and "is not" check for object identity.

    Object identity is different than equality, as we can see here:

    
    oddNumOne = [3, 5, 7]
    print(id(oddNumOne))
    oddNumTwo = [3, 5, 7]
    print(id(oddNumTwo))
    
    
    OUTPUT
      
    2280219806976
    2280219680128
    
    
    
    oddNumOne == oddNumTwo
    
    # True
    
    
    
    oddNumOne is oddNumTwo  
    
    # False
      
    
    
    oddNumOne is not oddNumTwo    
    
    # True
        
    

    What do identical objects look like? Here is an example:

    
    evenNumOne = [2, 4, 6]
    evenNumTwo = evenNumOne
    print(evenNumOne is evenNumTwo)
    print(id(evenNumOne))
    print(id(evenNumTwo))
    
    
    OUTPUT
    
    True
    2280198889280
    2280198889280
    
    

    The difference between the two cases here is that in the first, oddNumOne and oddNumTwo point to different objects, while in the second they point to the same object.

    As we know, Python variables are pointers. The "is" operator checks whether the two variables are pointing to the same container (object), rather than referring to what the container contains.

    With this in mind, in most cases that a beginner is tempted to use "is" what they really mean is ==.

    (b). Membership operators: in and not in


    Membership operators check for membership within compound objects.

    For example:

    
    'Messi' in ["Messi", "Ronaldo", "Neymar"]
    
    # True 
    
    
    
    'Messi' not in ["Messi", "Ronaldo", "Neymar"]
    
    # False 
    
    

    Python Built-in Types

    All Python objects have type information attached. There are two broad categories of built-in types in Python :

    1. 1. Scalar or Simple Types
    2. 2. Compound Types

    1. Built-in Scalar (simple) types in Python:

  • Scalar type is the most basic data type that holds only a single atomic value at a time. Using scalars, more compound data types can be constructed
  • Scalar type is something that has a finite set of possible values, following some scale, i.e. each value can be compared to any other value as either equal, greater or less.
  • Numeric values (integer, floating-point and complex ) are the obvious examples, while discrete/enumerated values can also be considered scalar.
  • Boolean is a scalar with 2 discrete possible values i.e True or False
  • NoneType with only one value i.e None
  • String with charaters/text is scalar as well.
  • Python's simple scalar types
    Type Mutable ? Example Description
    int Immutable x = 96 integers (i.e. whole numbers)
    float Immutable x = 3.14 floating-point numbers (i.e. real numbers)
    complex Immutable x = 2 + 3j Complex numbers (i.e. real and imaginary part)
    bool Immutable x = True Boolean: True/False values
    str Immutable x = 'Skillzam' String: characters or text
    NoneType Immutable x = None Special object indicating null

    2. Built-in Compound types in Python:

  • As seen already Built-in Scalar types: int, float, complex, bool, str, NoneType.
  • Python also has several built-in compound types, which act as containers for other types.
  • Python's Compund types
    Type Mutable ? Example Description
    list Mutable x = ["Messi", 35, 'Forward'] Ordered sequence of Object
    tuple Immutable x = (2022, "Skills", 89.99) Ordered sequence of Objects
    dict Mutable x = {"Argentina":4, "France": 2} Unordered (key,value) mapping
    set Mutable x = {"Arsenal","Barcelona","PSG"} Unordered collection of unique values

    As you can see, round ( ) square [ ] curly { } brackets have distinct meanings when it comes to the type of collection/sequence produced.

    Numbers

  • Number are built-in scalar (simple ) types in Python.
  • Numeric values are integers, floating-point numbers and complex numbers.
  • Three numeric types in Python :
    1. 1. int
    2. 2. float
    3. 3. complex
  • Integers have unlimited precision.
  • Floating point numbers are usually implemented using double in C
  • Complex numbers have a real and imaginary part, which are each a floating point number.
  • Numbers are created by numeric literals or as the result of built-in functions and operators.
  • Numeric literals containing a decimal point or an exponent sign yield floating point numbers.
  • A comparison between numbers of different types behaves as though the exact values of those numbers were being compared.
  • The constructors int(), float(), and complex() can be used to produce numbers of a specific type.
  • Numeric type (scalar):
    Type Mutable ? Example Description
    int Immutable x = 96 integers (i.e. whole numbers)
    float Immutable x = 3.14 floating-point numbers (i.e. real numbers)
    complex Immutable x = 2 + 3j Complex numbers (i.e. real and imaginary part)

    1. Integer Numbers

  • The most basic numerical type is the integer.
  • An integer is a whole number (not a fractional number) that can be positive, negative, or zero.
  • int() constructor method can be used to create a integer type.
  • Consider some examples:

    
    # Integers includes negation whole numbers
    
    intNum = -51
    type(intNum)
    
    
    OUTPUT
    
    int
    
    
    
    # zero is an integer as well
    
    type(0)
    
    
    OUTPUT
    
    int
    
    
    
    # int() constructor method to create integer variable
    
    year = int(1947)
    print(type(year))
    
    
    OUTPUT
    
    <class 'int'>
    
    

  • Python integers are actually quite a bit more sophisticated than integers in languages like C. C integers are fixed-precision, and usually overflow at some value (often near 231 or 263 , depending on your system).
  • Python integers are variable-precision, so you can do computations that would overflow in other languages:
  • 
    # Python integers are variable-precision
    
    2 ** 199
    
    
    OUTPUT
    
    803469022129495137770981046170581301261101496891396417650688
    
    

  • Python integers by default, the division operation will up-casts to floating-point type.
  • 
    # Division will up-casts to floating-point type
    
    22 / 7
    
    
    OUTPUT
    
    3.142857142857143
    
    

  • Note that this upcasting is a feature of Python 3; in Python 2, like in many statically-typed languages such as C, integer division truncates any decimal and always returns an integer:
  • 
    # Python 2 behavior
    
    >>> 22 / 7
    3
    
    

  • To recover this behavior in Python 3, you can use the floor-division operator //
  • 
    # Python 3 behavior
    
    >>> 22 // 7
    3
    
    

    2. Floating-Point Numbers

  • Floating-point type are used to represent non-integer fractional numbers.
  • A floating point number, is a positive or negative whole number with a decimal point ( . ).
  • Floating-point number can be defined either in standard decimal notation, or in exponential notation.
  • Consider some examples:

    
    # decimal & exponential notation
    
    floatNumOne = 0.000000009
    floatNumTwo = 9E-9
    print(floatNumOne == floatNumTwo)
    
    
    OUTPUT
    
    True
    
    

  • NOTE: In the exponential notation, the e or E can be read "...times ten to the...", so that 9.8e7 is interpreted as 9.8 * 107 (as seen in below example).
  • 
    # exponential notation
    
    fNumOne = 98000000.00
    fNumTwo = 9.8e7
    print(fNumOne == fNumTwo)
    
    
    OUTPUT
    
    True
    
    

  • An integer can be explicitly converted to a float with the float() constructor.
  • A float can be converted to a integer with the int() constructor
  • 
    >>> float(-55)
    -55.0
    >>> int(9.8e7)
    98000000
    
    

    Floating-point precision:

  • Precision of Floating point arithmetic is limited, which can cause equality tests to be unstable. For example:
  • 
    >>> 0.1 + 0.2 == 0.3
    False
    
    

    Why is this, the case ?

  • It turns out that it is not a behavior unique to Python, but is due to the fixed-precision format of binary floating-point storage used by most, if not all, scientific computing platforms.
  • All programming languages using floating-point numbers store them in a fixed number of bits, and this leads some numbers to be represented only approximately.
  • We can see this by printing the three values to high precision, in the below example:
  • 
    print("0.1 = {0:.17f}".format(0.1))
    print("0.2 = {0:.17f}".format(0.2))
    print("0.3 = {0:.17f}".format(0.3))
    
    
    OUTPUT
    
    0.1 = 0.10000000000000001
    0.2 = 0.20000000000000001
    0.3 = 0.29999999999999999
    
    

    EXPLANATION:

    We're accustomed to thinking of numbers in decimal (base-10) notation, so that each fraction must be expressed as a sum of powers of 10:

    1/8 = 0.125 = 1 * 10-1 + 2 * 10-2 + 5 * 10-3

    In the familiar base-10 representation, we represent this in the familiar decimal expression: 0.125

    Computers usually store values in binary notation, so that each number is expressed as a sum of powers of 2:

    1/8 = 0 * 2-1 + 0 * 2-2 + 1 * 2-3

    In a base-2 representation, we can write this 0.0012 where the subscript 2 indicates binary notation. The value 0.125 = 0.0012 happens to be one number which both binary and decimal notation can represent in a finite number of digits.

    In the familiar base-10 representation of numbers, you are probably familiar with numbers that can't be expressed in a finite number of digits. For example, dividing 1 by 3 gives, in standard decimal notation:

    1/3 = 0.333333333⋯

    The 3s go on forever: that is, to truly represent this quotient, the number of required digits is infinite!

    Similarly, there are numbers for which binary representations require an infinite number of digits. For example:

    1/10 = (0.00011001100110011⋯)2

    Just as decimal notation requires an infinite number of digits to perfectly represent 1/3 , binary notation requires an infinite number of digits to represent 1/10 .

    Python internally truncates these representations at 52 bits beyond the first nonzero bit on most systems.

    This rounding error for floating-point values is a necessary evil of working with floating-point numbers. The best way to deal with it is to always keep in mind that floating-point arithmetic is approximate, and never rely on exact equality tests with floating-point values.

    3. Complex Numbers

  • Complex numbers are the numbers that are expressed in the form of A + Bj where, A B are real numbers and j is an imaginary number called “iota”.
  • Complex numbers are numbers with real and imaginary (floating-point) parts.
  • complex() constructor method returns a complex number, when real and imaginary parts are provided, or it converts a string or number to a complex number.
  • 
    >>> complex(9, 6)
    (9+6j)
    
    

  • Alternatively, we can use the "j" suffix in expressions to indicate the imaginary part:
  • 
    >>> 3 + 4j
    (3+4j)
    
    

  • Complex numbers have a variety of interesting attributes and methods
  • 
    # Complex Numbers
    
    complexNum = 3 + 4j
    complexReal = complexNum.real    # real part of Complex number         
    complexImag = complexNum.imag    # imaginary part of Complex number
    complexConjugate = complexNum.conjugate()  # conjugate of Complex number
    complexAbs = abs(complexNum)     # magnitude of Complex number 
    
    print("Real part of Complex Number :", complexReal)
    print("Imaginary part of Complex Number :", complexImag)
    print("Conjugate of Complex Number :", complexConjugate)
    print("Magnitude of Complex Number :", complexAbs)
                
    
    OUTPUT
    
    Real part of Complex Number : 3.0
    Imaginary part of Complex Number : 4.0
    Conjugate of Complex Number : (3-4j)
    Magnitude of Complex Number : 5.0
    
    

  • conjugate of a complex number is another complex number which has the same real part as the original complex number and the imaginary part has the same magnitude but opposite sign.
  • If we multiply a complex number with its conjugate, we get a real number.
  • Magnitude (or absolute value) of a complex number is the number's distance from the origin in the complex plane.
  • Magnitude of a complex number is obtained by multiplying it by its complex conjugate and then taking the square root of the result.
  • Magnitude = sqrt( complexNumber.real ** 2 + complexNumber.imag ** 2 )
  • Strings

  • In Python, Strings are sequences of one or more characters put in a single quote(') double-quote(") or triple quote(''' or """).
  • Whenever you create a string by surrounding text with quotation marks, the string is called a string literal.
  • Quotes surrounding a string are called delimiters because they tell Python where a string begins and where it ends.
  • Textual data in Python is handled with str objects, or strings. Strings are immutable sequences of Unicode code points.
  • String literals that are part of a single expression and have only whitespace between them will be implicitly converted to a single string literal. That is, ("string " "literal") == "string literal".
  • String literals are written in a variety of ways:
    1. (a). Single quotes: 'allows embedded "double" quotes'
    2. (b). Double quotes: "allows embedded 'single' quotes"
    3. (c). Triple quoted: '''Three single quotes''', """Three double quotes"""

  • Strings created with Single quotes:

    
    # String in Single Quotes
    
    academy = 'Skillzam'          
    print(academy)
                
    
    OUTPUT
    
    Skillzam
    
    

    Strings created with Double quotes:

    
    # String in Double Quotes 
    
    tagLine = "Learn without limits!"    
    print(tagLine)
      
    
    OUTPUT
    
    Learn without limits!
    
    

    Strings created with Tripple quotes:

  • Spanning strings over multiple lines can be done using python's triple quotes.
  • It can also be used for long multiple comments in code.
  • Special characters like TABs, verbatim or NEWLINEs can also be used within the triple quotes.
  • As the name suggests its syntax consists of three consecutive single or double-quotes.
  • 
    # Multi-line comments
    
    ''' This is the
        Python
        multi-line 
        comment!
    '''
    
    # String in tripple Quotes 
    
    multiLine = """
    A CURE platform: 
                    - Cross-Skill 
                    - Up-Skill
                    - Re-Skill 
                    - Expert-Skill
                """
    
    print(multiLine)
      
    
    OUTPUT
    
    A CURE platform: 
                    - Cross-Skill 
                    - Up-Skill
                    - Re-Skill 
                    - Expert-Skill
    
    

  • Strings may also be created from other objects using the str constructor.
  • Python str() function is used to create a string version of the object that would be passed as argument to it.
  • 
    # string variable using str() method
    
    techHiring = str('www.' + 'workzam'  + '.com')   # '+' is used for concatenation
    print(techHiring)
    
    
    OUTPUT
    
    www.workzam.com
    
    

    String Properties

    Strings have three important properties:

  • Strings contain individual letters or symbols called characters.
  • Strings have a length, defined as the number of characters the string contains.
  • Characters in a string appear in a sequence, which means that each character has a numbered position in the string.
  • 
    # String contains characters/symbols (any Unicode Points)
    
    strVar01 = "$killzam 2023"
    strVar02 = "ಕನ್ನಡ"
    print("type of strVar01 = ", type(strVar01))
    print("type of strVar02 = ", type(strVar02))
    
    
    OUTPUT
    
    type of strVar01 =  <class 'str'>
    type of strVar02 =  <class 'str'>
      
    
    
    # Length of a given string using len() String method
    # len() returns number of characters contained in a string including spaces
    
    strVar03 = ' Python '    
    print("length of strVar03 = ", len(strVar03))
                
    
    OUTPUT
    
    length of strVar03 =  8
      
    

    String concatenation, Indexing, and Slicing

    Three basic string operations:

  • Concatenation: which joins two strings together
  • Indexing: which gets a single character from a string
  • Slicing: which gets several characters from a string at once
  • String Concatenation : You can combine, or concatenate, two strings using the + operator.

    
    # String Concatenation 
    
    'Skill' + 'zam'
            
    
    OUTPUT
    
    Skillzam
    
    
    
    # String Concatenation 
    
    fname = "Elon"
    lname = 'Musk'
    fullname = fname + ' ' + lname
    print(fullname)
            
    
    OUTPUT
    
    Elon Musk
    
    
    
    # Concatenation & Multiplication of string
    
    'buz' + ('z' * 6)
            
    
    OUTPUT
    
    'buzzzzzzz'
    
    

    String Indexing :

  • Each character in a string has a numbered position called an index
  • Index counting always starts at zero. To get the character at the beginning of a string, you need to access the character at position [0]
  • You can access the character at the nth position by putting the number n between two square brackets [ ] immediately after the string.
  • 
    # String Indexing 
    
    rhyme = "Twinkle, Twinkle, Little Star"
    rhyme[4]    # returns the character at index position 4
    
    
    OUTPUT
    
    'k'
    
    
    
    # Reverse Indexing
    
    river = "Kaveri"
    river[-5]                  
    
    
    OUTPUT
    
    'a'
    
    
    
    # Access an index beyond end of string, then Python throws an IndexError
    
    rhyme = "Twinkle, Twinkle, Little Star"
    try:
        rhyme[100]         
    except:
        print("IndexError: string index out of range")              
    
    
    OUTPUT
    
    IndexError: string index out of range
    
    

    String Slicing :

  • You can extract a portion of a string, called a substring, by inserting a colon : between two index numbers set inside square brackets [ ]
  • This substring is called Slice
  • Use Syntax : string[start : stop : step]
  • 
    # String Slicing 
    
    city = "Mumbai"
    city[0:3]       # returns first three characters in string "Mumbai"
    
    
    OUTPUT
    
    'Mum'
    
    

  • It's important to note that, unlike with string indexing, Python won't raise an IndexError when you try to slice between boundaries that fall outside the beginning or ending boundaries of a string
  • 
    # Boundaries that fall outside the beginning or ending
    
    city = "Mumbai"
    city[:10]        # return the entire string
    
    
    OUTPUT
    
    'Mumbai'
    
    

  • when you try to get a slice in which the entire range is out of bounds, Instead of raising an error, Python returns the empty string ("").
  • 
    #  Entire range is out of bounds
    
    city = "Mumbai"
    city[6:10]        # returns the empty string ''
    
    
    OUTPUT
    
    ''
    
    
    
    # Adding 'step' to the string slicing
    
    stadium = "Maracanã"
    stadium[1::2]     # start at position index 1, to end of string in step sizes of 2
    
    
    OUTPUT
    
    'aaaã'
    
    

  • To reverse a string, we simply create a slice that starts with the length of the string, and ends at index 0. The slice statement means start at string length, end at position 0, move with the step -1 (or one step backward)
  • 
    # Reverse a string
    
    subject = "Data Science"
    subject[::-1]       # same as subject[12::-1] 
    
    
    OUTPUT
    
    'ecneicS ataD'
    
    
    
    # Slicing everything : Copying the entry string
    
    singer = "Michael Jackson"
    singer[:]         # Copy everything
    
    
    OUTPUT
    
    'Michael Jackson'
    
    

    Escape Characters

  • To insert characters that are illegal in a string, use an escape character.
  • An escape character is a backslash \ followed by the character you want to insert.
  • Escape Character usuage
    Code Description
    \' Single Quote
    \" Double Quote
    \\ Backslash
    \n New Line
    \b Backspace
    \r Carriage Return
    \t Tab
    
    # Double quotes inside a string, which is surrounded by double quotes
    
    sentence = "Messi became the "World Champion" in the year 2022!"
    print(sentence)
    
    
    OUTPUT
    
    SyntaxError: invalid syntax
    
    
    
    # Using escape character \" to avoid the invalid Syntax error
    
    sentence = "Messi became the \"World Champion\" in the year 2022!"
    print(sentence)          
    
    
    OUTPUT
    
    Messi became the "World Champion" in the year 2022!
    
    
    
    # Using newline escape character \n
    
    newline = "This is to demonstrate the usuage of \n new line escape charater in Python."
    print(newline)         
    
    
    OUTPUT
    
    This is to demonstrate the usuage of 
     new line escape charater in Python.
     
    

    String Interpolation

    String Interpolation is the process of substituting values of variables into placeholders in a string.

    For instance, if you have a template for specifying user-ID like "Welcome to Skillzam! Your user ID is {uid}.", you would like to replace the placeholder for user ID with an actual value. This process is called string interpolation.

    Python supports multiple ways to format text strings and these includes % Modulo, str.format(), f-strings and string.Template.

    % Modulo

  • % - Formatting is a feature provided by Python which can be accessed with a % operator. This is similar to printf style function in C.
  • It should be noted that two methods %s and %r convert any python object to a string using two separate methods: str() and repr(). We will learn more about these functions later on in the course, but you should note that %r and repr() deliver the string representation of the object, including quotation marks and any escape characters.
  • String formatting syntax changes slightly, if we want to make multiple substitutions in a single string, and as the % operator only takes one argument, we need to wrap the right-hand side in a tuple as shown in the example below:
  • 
    # Code to demonstrate % Module string interpolation
    
    name = 'Michael Phelps'
    medalNum = 28
    
    # for single substitution
    print("% s is called 'Flying Fish'!" % name)
    
    # for single and multiple substitutions use () mandatory
    print("%s is the most decorated Olympian of all time, with a total of %s medals." %(name, medalNum))
    
    
    OUTPUT
    
    Michael Phelps is called 'Flying Fish'!
    Michael Phelps is the most decorated Olympian of all time, with a total of 28 medals.
      
    

    Padding and Precision of Floating Point Numbers: Floating point numbers use the format %5.2f Here, 5 would be the minimum number of characters the string should contain; these may be padded with whitespace if the entire number does not have this many digits. Next to this, .2f stands for how many numbers to show past the decimal point. Let's see some examples:

    
    # Padding and Precision of Floating Point Numbers
    
    print('1. Floating point number: %5.2f' %(23.456))
    print('2. Floating point number: %1.0f' %(23.456))
    print('3. Floating point number: %1.5f' %(23.456))
    print('4. Floating point number: %10.2f' %(23.456))
    print('5. Floating point number: %25.2f' %(23.456))
    
    
    OUTPUT
    
    1. Floating point number: 23.46
    2. Floating point number: 23
    3. Floating point number: 23.45600
    4. Floating point number:      23.46
    5. Floating point number:                     23.46
    
    

    str.format()

  • In this string formatting we use format() function on a string object and curly braces { }, the string object in format() function is substituted in place of curly braces { }.
  • We can use the format() function to do simple positional formatting, just like % formatting.
  • We can also use the variable name inside the curly braces { }. This will allow us to use the parameters of format functions in any order we want.
  • 
    # Code to demonstrate str.format string interpolation
    
    name = 'Challenger Deep'
    location = 'Mariana Trench'
    txt = "The {} in the {}, is the deepest part of the ocean."
    
    print(txt.format(name, location))
    
    
    OUTPUT
    
    The Challenger Deep in the Mariana Trench, is the deepest part of the ocean.
    
    
    
    # Code to demonstrate str.format string interpolation
    # use the variable name inside the curly braces {}
    
    championName = 'Garry Kasparov'
    championAge = 22
    txt = "{nam} became the youngest ever undisputed World Chess Champion at {age}."
    
    print(txt.format(age=championAge, nam=championName))
                
    
    OUTPUT
    
    Garry Kasparov became the youngest ever undisputed World Chess Champion at 22.
    
    

    f-string

  • Python 3.6 added new string interpolation method called literal string interpolation and introduced a new literal prefix f
  • It provides access to embedded Python expressions inside string constants.
  • We can embed arbitrary Python expressions we can even do inline arithmetic with it.
  • 
    # Code to demonstrate f-string string interpolation
    
    founderName = 'Brendan Eich'
    langName = 'JavaScript'
    txt = f"{founderName} is the creater of {langName} programming language."
    
    print(txt)
                
    
    OUTPUT
    
    Brendan Eich is the creater of JavaScript programming language.
    
    
    
    # f-strings to calculate some arithmetic operations
    # Solve Quadratic Equation: x**2 - 8x + 15 = 0
    
    a = 1
    b = -8
    c = 15
    root1 = f"Value of root1 = {(-b + ((b**2 - 4*a*c)**0.5)) / 2*a}"
    root2 = f"Value of root2 = {(-b - ((b**2 - 4*a*c)**0.5)) / 2*a}"
    
    print("The roots of Quadratic Equation are :")
    print(root1)
    print(root2)
    
    
    OUTPUT
    
    The roots of Quadratic Equation are :
    Value of root1 = 5.0
    Value of root2 = 3.0
      
    

    Template string

  • Template Strings is simpler and less powerful mechanism of string interpolation. We need to import Template class from Python's built-in string module to use it.
  • The format uses placeholder names formed by $ with valid Python identifiers. Surrounding the placeholder with curly braces { } allows it to be followed by more alphanumeric letters with no intervening spaces.
  • 
    # Template string to demo string interpolation
    
    from string import Template
    
    name = 'Peregrine Falcon'
    livingType = 'bird'
    
    # create a template which pass two variable
    txt = Template('The $value1 is the fastest $value2 in the world.')
    
    # Pass parameters into the template string
    print(txt.substitute(value2=livingType, value1=name))
                
    
    OUTPUT
    
    The Peregrine Falcon is the fastest bird in the world.
    
    

    String Methods

  • Objects in Python usually have built-in methods. These methods are functions inside the object, that can perform actions or commands on the object itself.
  • We call methods with a period and then method name. Methods are in the form: object.method(parameters)
  • Here, parameters are extra arguments we can pass into the method.
  • Python has many extremely useful string functions/methods; here are a few of them:
  • 
    # Make string upper-case
    
    academy = "Skillzam - Learn without limits!"
    academy.upper()
    
    
    OUTPUT
    
    'SKILLZAM - LEARN WITHOUT LIMITS!'
    
    
    
    # Make string lower-case 
    
    academy = "Skillzam - Learn without limits!"
    academy.lower()
    
    
    OUTPUT
    
    'skillzam - learn without limits!'
    
    
    
    # Make string Capitalize
    
    academy = "Skillzam - Learn without limits!"
    academy.capitalize()
    
    
    OUTPUT
    
    'Skillzam - learn without limits!'
    
    
    
    # Removes any whitespace from the beginning or the end
    
    academy = " Skillzam - Learn without limits! "
    academy.strip()
    
    
    OUTPUT
    
    'Skillzam - Learn without limits!'
    
    
    
    # replace() method replaces a string with another string
    
    academy = "Skillzam - Learn without limits!"
    academy.replace('-','=>')
    
    
    OUTPUT
    
    'Skillzam => Learn without limits!'
    
    
    
    # Split a string by blank space and returns list
    
    academy = "Skillzam - Learn without limits!"
    academy.split()
    
    
    OUTPUT
    
    ['Skillzam', '-', 'Learn', 'without', 'limits!']
    
    
    
    # Split by a specific element and returns list (doesn't include the element that was split on)
    
    academy = "Skillzam - Learn without limits!"
    academy.split('-')
    
    
    OUTPUT
    
    ['Skillzam ', ' Learn without limits!']
    
    
    
    # Converts string into lower-case and return string
    
    academy = "Skillzam - Learn without limits!"
    academy.casefold()
    
    
    OUTPUT
    
    'skillzam - learn without limits!'
    
    
    
    # center align the string, using a specified character & return string
    
    academy = "Skillzam - Learn without limits!"
    academy.center(50,'*')
    
    
    OUTPUT
    
    '*********Skillzam - Learn without limits!*********'
    
    
    
    # count() method
    
    txt = "count() string method returns the number of times the specified value occurs in the string"
    txt.count('the')
    
    
    OUTPUT
    
    3
    
    
    
    # find() & index() method returns the position of string, where it was found
    
    vibgyor = "violet indigo blue green yellow orange red"
    positionBlue = vibgyor.find('blue')         # find() method
    positionWhite = vibgyor.find('white')       # find() method
    poistionRed = vibgyor.index('red')          # index() method
    try:
        poistionCyan = vibgyor.index('cyan')    # index() method
    except:
        print("ValueError: substring not found")  
    
    print("Index position where 'blue' was found is ", positionBlue)
    print("Index position where 'white' was found is ", positionWhite)
    print("Index position where 'red' was found is ", poistionRed)
      
    
    OUTPUT
    
    ValueError: substring not found
    Index position where 'blue' was found is  14
    Index position where 'white' was found is  -1
    Index position where 'red' was found is  39  
    
    

    Booleans

  • Booleans represent one of two values: True or False.
  • When you compare two values, the expression is evaluated and Python returns the Boolean answer
  • Booleans can also be constructed using the bool() object constructor: values of any other type can be converted to Boolean via predictable rules.

    For example, any numeric type is False if equal to zero, and True otherwise:

  • 
    # Truth value of an integer
    bool(2017)    
    
    
    OUTPUT
    
    True
    
    
    
    # Truth value of zero      
    bool(0)    
    
    
    OUTPUT
    
    False
    
    
    
    # Truth value of a float number
    bool(2.728281) 
    
    
    OUTPUT
    
    True
    
    
    
    # Truth value of None
    bool(None)       
    
    
    OUTPUT
    
    False
    
    

  • For strings, bool() is False for empty strings and True otherwise:
  • 
    # Truth value of an empty string
    bool("")       
    
    
    OUTPUT
    
    False
    
    
    
    # Truth value of string
    bool("Workzam")       
    
    
    OUTPUT
    
    True
    
    

  • For empty sequences and empty collections the Boolean representation is False and for any other values, it is True.
  • 
    # Truth value of a non-empty list
    bool([2, 4, 6])          
    
    
    OUTPUT
    
    True
    
    
    
    # Truth value of an empty list
    bool([])         
    
    
    OUTPUT
    
    False
    
    
    
    # Truth value of an empty dictionary
    bool({}) 
    
    
    OUTPUT
    
    False
    
    
    
    # Truth value of an non-empty dictionary
    bool({"Argentina":4, "France": 2})  
    
    
    OUTPUT
    
    True
    
    
    
    # Truth value of an empty set()
    bool(set())
    
    
    OUTPUT
    
    False
    
    
    
    # Truth value of a range(0)
    bool(range(0))
    
    
    OUTPUT
    
    False
    
    

    Any object can be tested for "truth value", for use in an if or while condition or as operand of the Boolean operations below.

    By default, an object is considered True unless its class defines either a __bool__() method that returns False or a __len__() method that returns zero, when called with the object.

    Here are most of the built-in objects considered False :

  • constants defined to be false: None and False
  • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • empty sequences and collections: "", (), [ ], { }, set(), range(0)
  • NoneType

  • Python includes a special type, the NoneType which has only a single possible value i.e None
  • None keyword is used to define a null value, or no value at all.
  • None is not the same as 0, False, or an empty string. None is a data type of its own (NoneType) and only None can be None.
  • For Example:

    
    studentCount = None
    type(studentCount)
    
    
    OUTPUT
    
    NoneType
    
    

  • You'll see None used in many places, but perhaps most commonly it is used as the default return value of a function.
  • For example, the print() function in Python 3 does not return anything, but we can still catch its value.
  • Likewise, any function in Python with no return value is, in reality, returning None
  • 
    # print() method returns None
    
    return_value = print('Old is Gold')
    print(return_value)
    
    
    OUTPUT
    
    None
    
    

    Lists

  • Lists are the most general version of a sequence in Python, typically used to store collections of homogeneous items.
  • Unlike strings, Lists are mutable , meaning the elements inside a list can be changed.
  • Lists are the basic ordered data collection type.
  • Lists can be defined with comma-separated values between square brackets [ ]
  • Lists draw parallels with arrays in other languages. Lists in Python however, tend to be more flexible than arrays in other languages for a two good reasons:
    1. (1). Lists have no fixed size, meaning we don't have to specify how big a list will be.
    2. (2). Lists have no fixed type constraint.
  • For example:

    
    # List of English Vowels 
    
    vowels = ['a', 'e', 'i', 'o', 'u']
    print(vowels)
    
    
    OUTPUT
    
    ['a', 'e', 'i', 'o', 'u']
    
    
    
    # List of whole numbers in a Fibonacci series 
    
    fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    print(fibo)
    
    
    OUTPUT
    
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    
    

    Lists may be constructed in several ways:

  • Using a pair of square brackets to denote the empty list: [ ]
  • Using square brackets, separating items with commas: [a], [a, b, c]
  • Using a list comprehension: [x for x in iterable]
  • Using the type constructor: list() or list(iterable)
  • The constructor builds a list whose items are the same and in the same order as iterable's items.
    iterable may be either a sequence, a container that supports iteration, or an iterator object.
    If iterable is already a list, a copy is made and returned, similar to iterable[:].

    For example, list('abc') returns ['a', 'b', 'c'] and list( (1, 2, 3) ) returns [1, 2, 3].
    If no argument is given, the constructor creates a new empty list, [ ].

    
    # Creating list literals using square brackets
    
    listOne = ['A', 1]
    print(listOne)
    
    
    OUTPUT
    
    ['A', 1]
    
    
    
    # Creating list using built-in list() constructor method
    
    listTwo = list("WORKZAM")      # Creating list from string argument
    listThree = list((8, 1, 2, 8)) # Creating list from tuple argument
    listFour = list(range(11, 21)) # Creating list from range() argument
    listFive = list([1, 3, 5, 7])  # Creating list from list argument
    print(listTwo)
    print(listThree)
    print(listFour)
    print(listFive)
                
    
    OUTPUT
    
    ['W', 'O', 'R', 'K', 'Z', 'A', 'M']
    [8, 1, 2, 8]
    [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
    [1, 3, 5, 7]
    
    
    
    # Creating list using list comprehension
    # list of single digit even number
    
    listSix = [item for item in range(1, 10) if (item % 2 == 0)]
    print(listSix)
    
    
    OUTPUT
    
    [2, 4, 6, 8]
    
    

    Working with lists :

  • Determine if a specified item is present in a list use the in keyword.
  • 
    # Determine if item exists in list
    
    shoppingList = ["tea", "coffee", "milk", "eggs", "honey"]
    if "eggs" in shoppingList:
        print("EGGS are present in the shopping list.")
    
    
    OUTPUT
    
    EGGS are present in the shopping list.
    
    

  • Modify items in a list.
  • 
    # Modify item in a list
    
    shoppingList = ["tea", "coffee", "milk", "eggs", "honey"]
    shoppingList[4] = "bread"    # Change the item from honey to bread
    print(shoppingList)
    
    
    OUTPUT
    
    ['tea', 'coffee', 'milk', 'eggs', 'bread']
    
    

  • Remove items in a list using the keyword del
  • 
    # using "del" remove item from list 
    
    shoppingList = ["tea", "coffee", "milk", "eggs", "honey"]
    del shoppingList[-1]    # Remove the last item "honey" in the list
    print(shoppingList)
    
    
    OUTPUT
    
    ['tea', 'coffee', 'milk', 'eggs']
    
    

    List indexing & slicing

  • List items are ordered, changeable, and allow duplicate values.
  • List items are indexed , the first item has index [0], the second item has index [1] etc.
  • Python provides access to elements in compound types through indexing for single elements , and slicing for multiple. Python uses zero-based indexing.
  • Elements at the end of the list can be accessed with negative numbers (reverse Indexing), starting from -1
  • Both indexing and slicing can be used to set elements as well as access them
  • We can also use + to concatenate lists, just like we did for strings.
  • We can also use the * for a duplication method similar to strings
  • Conside the below list example for understanding:

    
    # list example
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[0]     # access first element by indexing
    
    
    OUTPUT
    
    'S'
    
    
    
    # Reverse Indexing of list element
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[-1]     # access last element by reverse indexing
                
    
    OUTPUT
    
    'M'
    
    

    You can visualize indexing & reverse-indexing this way:


    Here values in the list are represented by capital letters in the squares; list indices are represented by number above and below.

    In this case, exampleList[1] returns K , because that is the value at index 1.


    Indexing is a means of fetching a single value from the list, slicing is a means of accessing multiple values in sub-lists. It uses a colon : to indicate the start point (inclusive) and end point (non-inclusive) of the sub-array.


    list Indexing slicing

    For example, to get the first five elements of the list, we can write:

    
    # Slicing list Example
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[0:5]     # same as exampleList[:5]
    
    
    OUTPUT
    
    ['S', 'K', 'I', 'L', 'L']
    
    

    Similarly, if we leave out the last index, it defaults to the length of the list. Thus, the last three elements can be accessed as follows:

    
    # Access last three elements in list
                
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[-3:]     # same as exampleList[-3:len(exampleList)]     
    
    
    OUTPUT
    
    ['Z', 'A', 'M']
    
    

    Finally, it is possible to specify a third integer that represents the step size ; for example, to select every second element of the list, we can write:

    
    # specify step size in list slicing
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[::2]    # same as exampleList[0:len(exampleList):2]
    
    
    OUTPUT
    
    ['S', 'I', 'L', 'A']
    
    

    Reversing the list :

    
    # Reversing the list
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[::-1]
    
    
    OUTPUT
    
    ['M', 'A', 'Z', 'L', 'L', 'I', 'K', 'S']
    
    

    Examples on List Indexing & Slicing :

    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[0] = '$'
    print(exampleList)
    
    
    OUTPUT
    
    ['$', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    
    
    
    exampleList = ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    exampleList[:5] = 'WORK'
    print(exampleList)
    
    
    OUTPUT
    
    ['W', 'O', 'R', 'K', 'Z', 'A', 'M']
    
    
    
    exampleList = ['W', 'O', 'R', 'K', 'Z', 'A', 'M']
    exampleList[:4] = ['S', 'K', 'I', 'L', 'L']
    print(exampleList)
    
    
    OUTPUT
    
    ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
    
    

    Use + to concatenate lists :

    
    # use '+' to concatenate lists 
    exampleList + [1, 2, 3]
    
    
    OUTPUT
    
    ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M', 1, 2, 3]
    
    

    Use * to duplicate lists :

    
    # Make the list double
    exampleList * 2
    
    
    OUTPUT
    
    ['S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M', 'S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M']
      
    

    List Nesting

    Python data structures support nesting. This means we can have data structures within data structures.
    For example: A list inside a list.

    
    # Matrix like structure using lists
    
    row1 = [1, 2, 3]
    row2 = [4, 5, 6]
    row3 = [7, 8, 9]
    
    # Nesting of lists within a list 
    matrixOne = [row1, row2, row3]
    print(matrixOne)
    
    
    OUTPUT
    
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    
    
    # Access first list item in matrix list
    
    matrixOne[0]
    
    
    OUTPUT
    
    [1, 2, 3]
    
    
    
    # access first element of first list item in matrix list
    
    matrixOne[0][0]
    
    
    OUTPUT
    
    1
    
    

    List methods

    Lists have a lot of useful built-in attributes and methods. Let us now consider them one by one:

  • append() method is used to add an item to the end of the list.
  • 
    # append() adds an item to end of the list
    
    capitalCities = ["New Delhi", "New York", "London", "Istanbul"]
    capitalCities.append("Tokyo")
    print(capitalCities)
              
    
    OUTPUT
    
    ['New Delhi', 'New York', 'London', 'Istanbul', 'Tokyo']
    
    

  • insert() method inserts an item at the specified index.
  • 
    # insert() adds an item at specified index
    
    capitalCities = ["New Delhi", "New York", "London", "Istanbul"]
    capitalCities.insert(0,"Tokyo")
    print(capitalCities)
    
    
    OUTPUT
    
    ['Tokyo', 'New Delhi', 'New York', 'London', 'Istanbul']
    
    

  • extend() method is used to append elements from another list to the current list.
  • 
    # extend() appends elements from one list to another
    
    healthyFood = ['Avocado', 'Kiwi', 'Moringa']
    veggies = ['Spinach', 'Kale', 'Collard']
    healthyFood.extend(veggies)
    print(healthyFood)
    
    
    OUTPUT
    
    ['Avocado', 'Kiwi', 'Moringa', 'Spinach', 'Kale', 'Collard']
    
    

  • extend() method can append not only lists but also any iterable object (tuples, sets, dictionaries etc..).
  • 
    # extend() appends elements from one list to another iterables
    
    numbers = [0, 1, 3, 5, 7, 9]
    evenOdd = (2, 4, 6, 8)       # Tuple Object
    numbers.extend(evenOdd)
    print(numbers)
    
    
    OUTPUT
    
    [0, 1, 3, 5, 7, 9, 2, 4, 6, 8]
    
    

  • remove() method removes the specified item.
  • 
    # remove() method removes element from list
    
    shoppingList = ["tea", "coffee", "milk", "eggs", "honey"]
    shoppingList.remove('honey')
    print(shoppingList)
    
    
    OUTPUT
    
    ['tea', 'coffee', 'milk', 'eggs']
    
    

  • pop() method removes the specified index element.
  • 
    # pop() method removes the specified index element
    
    fishes = ['Catfish', 'Bass', 'Carp', 'Tuna', 'Salmon']
    fishes.pop(3)     # 'Tuna' is at the index position 3
    print(fishes)
    
    
    OUTPUT
    
    ['Catfish', 'Bass', 'Carp', 'Salmon']
    
    

  • clear() method empties the list.
  • 
    # clear() method empties the list
    
    ranNum = [12, 34 ,76, 98, 11]
    ranNum.clear()
    print(ranNum)
    
    
    OUTPUT
    
    []
    
    

  • sort() method that will sort the list alphanumerically, ascending, by default.
  • 
    # sort() ascending will sort list alphanumerically
    
    colors = ['red', 'green', 'blue', 'orange', 'cyan']
    colors.sort()
    print(colors)
    
    
    OUTPUT
    
    ['blue', 'cyan', 'green', 'orange', 'red']
    
    

  • sort() descending using keyword argument reverse = True
  • 
    # sort() descending using 'reverse' argument
    
    num = [-12, 3, 45, 8877, 222]
    num.sort(reverse=True)
    print(num)
    
    
    OUTPUT
    
    [8877, 222, 45, 3, -12]
    
    

  • sort() customization using the keyword argument key = function.
  • 
    # sort() customization using 'key = function' argument
    
    names = ['Raj', 'ali', 'LEO', 'eve', 'Max']
    names.sort(key = str.lower)
    print(names)
    
    
    OUTPUT
    
    ['ali', 'eve', 'LEO', 'Max', 'Raj']
    
    

  • reverse() method reverses the current sorting order of the elements.
  • 
    # reverse() method reverses current sorting order
    
    names = ['ali', 'eve', 'LEO', 'Max', 'Raj']
    names.reverse()
    print(names)
    
    
    OUTPUT
    
    ['Raj', 'Max', 'LEO', 'eve', 'ali']
    
    

  • copy() method will make a copy of the list.
  • 
    # copy() method will make a copy of the list
    
    symbols = ['INR', 'USD', 'EUR', 'JPY', 'CNY']
    curSym = symbols.copy()
    print(curSym)
    
    
    OUTPUT
    
    ['INR', 'USD', 'EUR', 'JPY', 'CNY']
    
    

    Tuples

  • Tuples are built-in compound types in Python.
  • Tuples are ordered, immutable (= unchangeable) sequences, typically used to store collections of data.
  • Tuples can be defined with comma-separated values between round brackets ( ).
  • Tuples allows duplicate values.
  • Tuple items are indexed, the first item has index [0] the second item has index [1] etc.
  • Length of the Tuple can be found using built-in len() method.
  • For Example:

    
    # Tuple of temperature readings
    
    tempCel = (12.5, 22, 34.5, 21, 11, 33)
    print(tempCel)
                            
    
    OUTPUT
    
    (12.5, 22, 34.5, 21, 11, 33)
    
    
    
    # Tuple of tuples: stockPrices as on Jan-2023
    
    stockPrices = (('AAPL', 135.73), ('TSLA', 130.92), ('MSFT', 238.65))
    print(stockPrices)
                            
    
    OUTPUT
    
    (('AAPL', 135.73), ('TSLA', 130.92), ('MSFT', 238.65))
    
    

    Tuples may be constructed in a number of ways:

  • Using a pair of parentheses to denote the empty tuple: ( )
  • Using a trailing comma for a singleton tuple: a, or (a,)
  • Separating items with commas: a, b, c or (a, b, c)
  • Using the tuple() built-in: tuple() or tuple(iterable)
  • 
    # Creating singleton tuple 
    
    tupleOne = 23,      # or can also be done (23,)
    print(tupleOne)
                    
    
    OUTPUT
    
    (23,)
    
    
    
    # Creating tuple using constructor method
    
    tupleTwo = tuple("WORKZAM")      # Creating tuple from string argument
    tupleThree = tuple((8, 1, 2, 8)) # Creating tuple from tuple argument
    tupleFour = tuple(range(11, 21)) # Creating tuple from range() argument
    tupleFive = tuple([1, 3, 5, 7])  # Creating tuple from list argument
    print(tupleTwo)
    print(tupleThree)
    print(tupleFour)
    print(tupleFive)
                
    
    OUTPUT
    
    ('W', 'O', 'R', 'K', 'Z', 'A', 'M')
    (8, 1, 2, 8)
    (11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
    (1, 3, 5, 7)
    
    

    Working with Tuples:

  • Tuples can also be defined without any brackets at all.
  • 
    # Creating tuples without any brackets 
    
    numTuple = 12, 34, 56, 78, 90
    print(numTuple)
    
    
    OUTPUT
    
    (12, 34, 56, 78, 90)
    
    

  • Tuples have length.
  • 
    # Length of a Tuple using len() method
    
    numTuple = (12, 34, 56, 78, 90)
    len(numTuple)   
    
    
    OUTPUT
    
    5
    
    

  • Individual elements can be accessed using square-bracket indexing.
  • 
    # Accessing individual elements of a Tuple
    
    numTuple = (12, 34, 56, 78, 90)
    numTuple[4]
                  
    
    OUTPUT
    
    90
    
    

  • Accessing an index that doesn't exist in a Tuple will throw - IndexError: tuple index out of range.
  • 
    # IndexError: tuple index out of range
    
    numTuple = (12, 34, 56, 78, 90)
    numTuple[5]     # index 5 doesnot exists
        
    
    OUTPUT
    
    IndexError: tuple index out of range
    
    

  • Once Tuples are created, their size and contents cannot be changed i.e Tuples are immutable..
  • 
    # Tuples are immutable
    
    numTuple = (12, 34, 56, 78, 90)
    numTuple[0] = 24
    
    
    OUTPUT
    
    TypeError: 'tuple' object does not support item assignment
    
    

    Tuple indexing & slicing

  • Tuple items are ordered, unchangeable, and allow duplicate values.
  • Tuple items are indexed , the first item has index [0], the second item has index [1] etc.
  • Python provides access to elements in compound types through indexing for single elements , and slicing for multiple. Python uses zero-based indexing.
  • Elements at the end of the tuple can be accessed with negative numbers (reverse Indexing), starting from -1
  • Both indexing and slicing can be used to access elements of the Tuple.
  • We can also use + to concatenate tuples, just like we did for strings.
  • We can also use the * for a duplication method similar to strings
  • Conside the below tuple example for understanding:

    
    # tuple example
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[0]     # access first element by indexing
    
    
    OUTPUT
    
    'S'
    
    
    
    # Reverse Indexing of tuple element
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[-1]     # access last element by reverse indexing
              
    
    OUTPUT
    
    'M'
    
    

    Indexing is a means of fetching a single value from the tuple, slicing is a means of accessing multiple values in sub-tuples. It uses a colon : to indicate the start point (inclusive) and end point (non-inclusive) of the sub-array.


    For example, to get the first five elements of the tuple, we can write:

    
    # Slicing tuple Example
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[0:5]     # same as exampleTuple[:5]
    
    
    OUTPUT
    
    ('S', 'K', 'I', 'L', 'L')
    
    

    Similarly, if we leave out the last index, it defaults to the length of the tuple. Thus, the last three elements can be accessed as follows:

    
    # Access last three elements in tuple
              
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[-3:]     # same as exampleTuple[-3:len(exampleTuple)]     
    
    
    OUTPUT
    
    ('Z', 'A', 'M')
    
    
    

    Finally, it is possible to specify a third integer that represents the step size ; for example, to select every second element of the tuple, we can write:

    
    # specify step size in tuple slicing
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[::2]    # same as exampleTuple[0:len(exampleTuple):2]
    
    
    OUTPUT
    
    ('S', 'I', 'L', 'A')
    
    

    Reversing the tuple :

    
    # Reversing the tuple
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[::-1]
    
    
    OUTPUT
    
    ('M', 'A', 'Z', 'L', 'L', 'I', 'K', 'S')
    
    

    Changing elements of a Tuple using Indexing & Slicing :

    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple[:5] = 'WORK'
    print(exampleTuple)
    
    
    OUTPUT
    
    TypeError: 'tuple' object does not support item assignment
    
    

    Use + to concatenate tuples :

    
    # use '+' to concatenate tuples 
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple + (1, 2, 3)
    
    
    OUTPUT
    
    ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M', 1, 2, 3)
    
    

    Use * to duplicate tuples :

    
    # Make the tuple double
    
    exampleTuple = ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    exampleTuple * 2
    
    
    OUTPUT
    
    ('S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M', 'S', 'K', 'I', 'L', 'L', 'Z', 'A', 'M')
    
    

    Tuple Methods

    Tuples have built-in methods, but not as many as lists do. Here are index() and count().

  • index() method finds the first occurrence of the specified value. This method raises an exception, if the value is not found.
  • 
    # index() returns index of a specified value
    
    colorCode = ('#ff4040', '#008080', '#fdbe02', '#c5aac8')
    selectColor = colorCode.index('#fdbe02')
    print(selectColor)
              
    
    OUTPUT
    
    2
    
    

  • count() method returns the number of times a specified value appears in the tuple.
  • 
    # count() returns number of times a value appears
    
    ranNum = (12, 34, 65, 12, 67, 88, 12, 36)
    selectNum = ranNum.count(12)
    print(selectNum)
    
    
    OUTPUT
    
    3
    
    

    Why another built-in type ?

    Data integrity / immutability:


    To be honest, tuples are not used as often as lists in programming, but are used when immutability is necessary.
    If in your program you are passing around an object and need to make sure it does not get changed, then a tuple becomes your solution.
    It provides a convenient source of data integrity.

    Handle multiple return values:


    A particularly common case is in a functions that have multiple return values.
    For example, the as_integer_ratio() method of floating-point objects returns a numerator and a denominator; this dual return value comes in the form of a tuple:

    
    floatNum = 0.125
    rtnVal = floatNum.as_integer_ratio()
    print(rtnVal)    # print a tuple o/p
    
    
    OUTPUT
    
    (1, 8)
    
    

    These multiple return values can be individually assigned as follows:

    
    floatNum = 0.125
    numerator, denominator = floatNum.as_integer_ratio()
    print(f'Numerator is {numerator} and Denominator is {denominator}')
    
    
    OUTPUT
    
    Numerator is 1 and Denominator is 8
    
    

    Range

    range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and stops before a specified number.

    Ranges implement all of the common sequence operations except concatenation and repetition (due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern).

    Syntax: range(start, stop[, step])

  • start : The value of the start parameter (or 0 if the parameter was not supplied)
  • stop : The value of the stop parameter
  • step : The value of the step parameter (or 1 if the parameter was not supplied)
  • The advantage of the range type over a regular list or tuple is that a range object will always take the same (small) amount of memory, no matter the size of the range it represents (as it only stores the start, stop and step values, calculating individual items and subranges as needed).

    
    # range() type example
    
    num = range(6)
    for n in num:
        print(n, end=' ')
    
    
    OUTPUT
    
    0 1 2 3 4 5 
    
    
    
    # range() type with two arguments
    
    num = range(1,6)
    for n in num:
        print(n, end=' ')
    
    
    OUTPUT
    
    1 2 3 4 5 
    
    
    
    # range() type with three arguments
    
    num = range(2,10,2)
    for n in num:
        print(n, end=' ')
    
    
    OUTPUT
    
    2 4 6 8 
    
    

    Dictionaries

  • Dictionary is a built-in compound types in Python.
  • Dictionaries are ordered, mutable (= changeable), key-value pair mapping objects, typically used to store collections of data. As of Python version 3.7, dictionaries are ordered. In Python 3.6 and earlier, dictionaries are unordered.
  • Dictionary items can be defined within curly brackets and have a comma-separated key-value pairs.
  • The values in dictionary items can be of any data type.
  • Dictionaries does not allow duplicate items.
  • Dictionary items are presented in key:value pairs, and can be referred to by using the key name.
  • Length of the Dictionary is to determine how many items it has and can be found using built-in len() method.
  • For Examples:

    
    # Dictionary Example
    
    numbers = {'Keyone':1, 'Keytwo':2, 'Keythree':3}
    print(numbers)
    
    
    OUTPUT
    
    {'Keyone': 1, 'Keytwo': 2, 'Keythree': 3}
    
    
    
    # Length of a Dictionary (Year-Make-Model of vehicle)
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"
    }
    len(vehicle)
    
    
    OUTPUT
      
    3
    
    
    
    # Values can be of any dataType in a Dictionary
    
    students = {
        "fName": "Jasmine",    
        "lName": "Dsouza",     
        "gender": "Female",    # value is string DataType
        "age": 20,             # value is integer DataType  
        "isGraduate": True,    # value is boolean DataType
        "cgpa": 8.4,           # value is float DataType 
        "favSub": ["Physics","Computers","History"] # value is list DataType
    }
    print(students)
    
    
    OUTPUT
    
    {'fName': 'Jasmine', 'lName': 'Dsouza', 'gender': 'Female', 'age': 20, 'isGraduate': True, 'cgpa': 8.4, 'favSub': ['Physics', 'Computers', 'History']}
    
    

    Dictionaries can be created by several means:

  • Use a comma-separated list of key: value pairs within braces: {'ravi': 4098, 'abdul': 4127} or {4098: 'ravi', 4127: 'abdul'}
  • Use a dict comprehension: {}, {x: x ** 2 for x in range(10)}
  • Use the type constructor: dict(), dict([('BTC', 100), ('ETH', 200)]), dict(BTC=100, ETH=200)
  • 
    # Creating dictionary various ways
    
    dictOne = {'apples':123, 'oranges':456}    # using key:value pairs
    dictTwo = {x: x ** 2 for x in range(6)}    # dict comprehension
    dictThree = dict({'Sat':23, 'Sun':22})     # dict type constructor
    dictFour = dict([('BTC', 1), ('ETH', 2)])  # dict type constructor
    dictFive = dict(AAPL=137.87, MSFT=240.22)  # dict type constructor
    
    print(dictOne)
    print(dictTwo)
    print(dictThree)
    print(dictFour)
    print(dictFive)
                
    
    OUTPUT
    
    {'apples': 123, 'oranges': 456}
    {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
    {'Sat': 23, 'Sun': 22}
    {'BTC': 1, 'ETH': 2}
    {'AAPL': 137.87, 'MSFT': 240.22}
    
    

    Working with Dictionary:

  • Access the items of a dictionary by referring to its key name, inside square brackets.
  • 
    # Accessing the items of a dictionary
    
    students = {
        "fName": "Jasmine",    
        "lName": "Dsouza",     
        "gender": "Female",     
        "age": 20,                
        "isGraduate": True,     
        "cgpa": 8.4,            
        "favSub": ["Physics","Computers","History"]  
    }
    
    firstname = students['fName']
    favSubject = students['favSub'][0]
    print(f'{firstname} loves {favSubject}!')
    
    
    OUTPUT
    
    Jasmine loves Physics!
      
    

  • get() method is also used to access the items of a dictionary.
  • 
    # get() method to access the items of a Dictionary 
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"
    }
    vehicle.get('make')
    
    
    OUTPUT
    
    'Mahindra'
    
    

    Dictionary view objects :

  • The objects returned by dict.keys(), dict.values() and dict.items() are view objects.
  • They provide a dynamic view on the dictionary's entries, which means that when the dictionary changes, the view reflects these changes.
  • Dictionary views can be iterated over to yield their respective data, and support membership tests.
  • keys() method will return a list of all the keys in the dictionary. The list of the keys is a view of the dictionary, meaning that any changes done to the dictionary will be reflected in the keys list.
  • values() method will return a list of all the values in the dictionary. The list of the values is a view of the dictionary, meaning that any changes done to the dictionary will be reflected in the values list.
  • items() method will return each item in a dictionary, as tuples in a list. The returned list is a view of the items of the dictionary, meaning that any changes done to the dictionary will be reflected in the items list.
  • 
    # Dictionary view objects dict.keys(), dict.values(), dict.items()
    
    playersBio = {
        'name': "Leo Messi",
        'team': "Paris Saint-Germain",
        'position': "Forward",
        'height': 170,
        'weight': 159,
        'birthdate': "24/6/1987",
        'age': 35,
        'nationality': "Argentina",
        'careerHistory': ["Barcelona","PSG","Argentina"], 
        'isRetired': False
    }
    getKeys = playersBio.keys()
    getValues = playersBio.values()
    getItems = playersBio.items()
    
    print(getKeys)
    print('\n')
    print(getValues)
    print('\n')
    print(getItems)           
    
    
    OUTPUT
    dict_keys(['name', 'team', 'position', 'height', 'weight', 'birthdate', 'age', 'nationality', 'careerHistory', 'isRetired'])
    
    
    dict_values(['Leo Messi', 'Paris Saint-Germain', 'Forward', 170, 159, '24/6/1987', 35, 'Argentina', ['Barcelona', 'PSG', 'Argentina'], False])
    
    
    dict_items([('name', 'Leo Messi'), ('team', 'Paris Saint-Germain'), ('position', 'Forward'), ('height', 170), ('weight', 159), ('birthdate', '24/6/1987'), ('age', 35), ('nationality', 'Argentina'), ('careerHistory', ['Barcelona', 'PSG', 'Argentina']), ('isRetired', False)])
    
    

  • in Keyword : is used to determine if a specified key is present in a dictionary.
  • 
    # Check if a key exists 'in' the dictionary 
    
    users = {
        "fname": 'Elon',
        "lname": 'Musk',
        "email": 'elon.musk@example.com'
    }
    isEmailExists = 'email' in users
    print(isEmailExists)
                
    
    OUTPUT
    
    True
    
    

  • Change the value of a specific item by referring to its key name.
  • 
    # Change the value of a specific item in a dictionary 
    
    users = {
        "fname": 'Elon',
        "lname": 'Musk',
        "email": 'elon.musk@.com'
    }
    users['email'] = 'elon.musk@example.com'
    print(users)
    
    
    OUTPUT
    
    {'fname': 'Elon', 'lname': 'Musk', 'email': 'elon.musk@example.com'}
    
    

  • Adding an item to the dictionary is done by using a new index key and assigning a value to it.
  • 
    # Adding new item (key:value) in a dictionary 
    
    users = {
        "fname": 'Elon',
        "lname": 'Musk',
        "email": 'elon.musk@.com'
    }
    users['country'] = 'USA'
    print(users)
    
    
    OUTPUT
    
    {'fname': 'Elon', 'lname': 'Musk', 'email': 'elon.musk@.com', 'country': 'USA'}
    
    

  • del keyword removes the item with the specified key name.
  • 
    # Deleting an item (key:value) in a dictionary 
    
    users = {
        "fname": 'Elon',
        "lname": 'Musk',
        "email": 'elon.musk@example.com',
        "country": 'USA'
    }
    del users['email'] 
    print(users)
    
    
    OUTPUT
    
    {'fname': 'Elon', 'lname': 'Musk', 'country': 'USA'}
    
    

    Nested Dictionaries

    Python data structures support nesting. This means we can have data structures within data structures.
    For example: A dictionary containing dictionary.

    
    # Nested Dictionary : Example 1
    
    team = {
        "player1": {
            "name": 'Leo Messi',
            "position": 'Forward'
        },
    
        "player2": {
            "name": 'Andres Iniesta',
            "position": 'Midfield'
        },
    
        "player3": {
            "name": 'Xavi Hernandez',
            "position": 'Midfield'
        }
    }
    
    team['player1']['name']
    
    
    OUTPUT
    
    'Leo Messi'
    
    
    
    # Nested Dictionary : Example 2
    
    player1 = {
        "name": 'Leo Messi',
        "position": 'Forward'
    },
    
    player2 = {
        "name": 'Andres Iniesta',
        "position": 'Midfield'
    },
    
    player3 = {
        "name": 'Xavi Hernandez',
        "position": 'Midfield'
    }
    
    team = {
        "player1" : player1,
        "player2" : player2,
        "player3" : player3
    }
    
    team['player1']
    
    
    OUTPUT
    
    ({'name': 'Leo Messi', 'position': 'Forward'},)
    
    

    Dictionary Methods

  • copy() method returns a copy of the specified dictionary.
  • 
    # copy() returns a shallow copy of dictionary
    
    student = {
        "fName": "Jasmine",    
        "lName": "Dsouza",     
        "gender": "Female",     
        "age": 20,                
        "isGraduate": True,     
        "cgpa": 8.4,            
        "favSub": ["Physics","Computers","History"]  
    }
    
    newStudent = student.copy()
    print(newStudent)
                
    
    OUTPUT
    
    {'fName': 'Jasmine', 'lName': 'Dsouza', 'gender': 'Female', 'age': 20, 'isGraduate': True, 'cgpa': 8.4, 'favSub': ['Physics', 'Computers', 'History']}
    
    

  • fromkeys() method returns a dictionary with the specified keys and value.
  • 
    # fromkeys() returns dictionary with keys/value specified
    
    allKeys = ('name','gender','age','country')
    allValues = ('Rahul','Male',25,'India')
    
    dictOne = dict.fromkeys(allKeys, allValues)
    dictTwo = dict.fromkeys(allKeys)           # default all values to 'None'
    dictThree = dict.fromkeys(allKeys, 'xyz')  # default all values to 'xyz'
    
    print(dictOne)
    print(dictTwo)
    print(dictThree)
    
    
    OUTPUT
    
    {'name': ('Rahul', 'Male', 25, 'India'), 'gender': ('Rahul', 'Male', 25, 'India'), 'age': ('Rahul', 'Male', 25, 'India'), 'country': ('Rahul', 'Male', 25, 'India')}
    {'name': None, 'gender': None, 'age': None, 'country': None}
    {'name': 'xyz', 'gender': 'xyz', 'age': 'xyz', 'country': 'xyz'}
    
    

  • get() method returns the value of the item with the specified key.
  • 
    # get() returns the value of the key specified
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"
    }
    vehicle.get('make')
    
    
    OUTPUT
    
    'Mahindra'
    
    

  • pop() method removes the specified item from the dictionary. The value of the removed item is the return value of the pop() method.
  • 
    # pop() removes the item from the dictionary
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"    
    }
    vehicle.pop('year')
    print(vehicle)
    
    
    OUTPUT
    
    {'make': 'Mahindra', 'model': 'XUV700'}
    
    

  • popitem() method removes the item that was last inserted into the dictionary.
  • 
    # popitem() removes the last inserted item  
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"    
    }
    vehicle.popitem()
    print(vehicle)
    
    
    OUTPUT
    
    {'year': 2021, 'make': 'Mahindra'}
    
    

  • clear() method removes all items from the dictionary.
  • 
    # clear() removes the all items from the dictionary  
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"    
    }
    vehicle.clear()
    print(vehicle)
    
    
    OUTPUT
    
    {}
    
    

  • setdefault() method returns the value of the item with the specified key. If the key does not exist, insert the key, with the specified value.
  • 
    # setdefault() returns value of the item 
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"    
    }
    vehicle.setdefault('country', 'India')
    print(vehicle)
    
    
    OUTPUT
    
    {'year': 2021, 'make': 'Mahindra', 'model': 'XUV700', 'country': 'India'}
    
    

  • update() method inserts the specified items to the dictionary. The specified items can be a dictionary, or an iterable object with key value pairs.
  • 
    # update() inserts the specified item
    
    vehicle = {
        "year": 2021,
        "make": 'Mahindra',
        "model": "XUV700"    
    }
    vehicle.update({"color": "Black"})
    print(vehicle)
    
    
    OUTPUT
    
    {'year': 2021, 'make': 'Mahindra', 'model': 'XUV700', 'color': 'Black'}
    
    
    
    # update() inserts items from iterable
    
    cryptoCurrency = {}
    cryptoCurrency.update([('BTC', 1), ('ETH', 2), ('ADA', 3), ('SOL', 4)])
    print(cryptoCurrency)
    
    
    OUTPUT
    
    {'BTC': 1, 'ETH': 2, 'ADA': 3, 'SOL': 4}
    
    

    Sets

  • Set is a built-in compound types in Python.
  • Sets are unordered, mutable collections of unique objects, typically used to store collections of data.
  • Set items can be defined within curly brackets {} and have items with comma-separated.
  • Sets does not allow duplicate values.
  • Length of the set is to determine how many items a set has, using built-in len() method.
  • They are defined much like lists and tuples, except they use the curly brackets of dictionaries.
  • Common uses of sets include membership testing, removing duplicates from a sequence, and computing mathematical operations such as intersection, union, difference, and symmetric difference.
  • 
    # Set Examples
    primes = {2, 3, 5, 7}
    odds = {1, 3, 5, 7, 9}
    
    # set can have values of different datatypes
    mixedSet = {"Skillzam", 12, True, 33.33}   
    
    
    
    # Duplicate set values will be ignored
    
    # 'apple' will be ignored
    fruits = {'apple', 'bananas', 'oranges', 'apple'}
    print(fruits)
    
    
    OUTPUT
    
    {'apple', 'oranges', 'bananas'}
    
    
    
    # len() will find the number of items in a set
    
    fruits = {'apple', 'bananas', 'oranges'}
    len(fruits)
    
    
    OUTPUT
    
    3
    
    

    Sets can be created by several means:

  • Use a comma-separated list of elements within braces: {'male', 'female'}
  • Use a set comprehension: {c for c in 'abracadabra' if c not in 'abc'}
  • Use the type constructor: set() , set('workzam') , set(['red', 'green', 'blue'])
  • 
    # Creating sets various ways
    
    setOne = {'apples', 'oranges', 'kiwi'}    # using curly braces
    setTwo = {c for c in 'abracadabra' if c not in 'abc'} # set comprehension
    setThree = set(('Mon', 'Tue', 'Wed', 'Thu', 'Fri'))   # set type constructor
    setFour = set([('BTC', 1), ('ETH', 2), ('ADA', 3)])   # set type constructor
    setFive = set("workzam")                              # set type constructor
    
    print(setOne)
    print(setTwo)
    print(setThree)
    print(setFour)
    print(setFive)
    
    
    OUTPUT
      
    {'apples', 'kiwi', 'oranges'}
    {'d', 'r'}
    {'Thu', 'Tue', 'Fri', 'Wed', 'Mon'}
    {('ADA', 3), ('ETH', 2), ('BTC', 1)}
    {'w', 'z', 'o', 'a', 'm', 'k', 'r'}
    
    

    Working with Sets

  • Once a set is created, you cannot change/modify its items, but you can add or remove items.
  • You cannot access items in a set by referring to an index or a key.But you can loop through the set items using a for loop.
  • 
    # Accessing each item in the set
    
    cryptoCurrency = set([('BTC', 1), ('ETH', 2), ('ADA', 3), ('SOL', 4)])
    print("Set is defined as ", cryptoCurrency)
    for crypto in cryptoCurrency:
        print(crypto)
      
    
    OUTPUT
    
    Set is defined as  {('ADA', 3), ('SOL', 4), ('ETH', 2), ('BTC', 1)}
    ('ADA', 3)
    ('SOL', 4)
    ('ETH', 2)
    ('BTC', 1)
    
    

  • Check if a specified value is present in a set, by using the in keyword.
  • 
    # Check if a specified item is present in a set
    
    workingDays = {'Mon', 'Tue', 'Wed', 'Thu', 'Fri'}
    isItemExists = 'Sun' in workingDays
    print('Sunday exits in the set? ', isItemExists)
    
    
    OUTPUT
    
    Sunday exits in the set?  False
    
    

  • del keyword will delete the set completely.
  • 
    # del will delete the set completely
    
    fruits = {'apples', 'oranges', 'kiwi'}
    del fruits
    print(fruits)
    
    
    OUTPUT
    
    NameError: name 'fruits' is not defined
    
    

    Mathematics of sets

    Sets can be used to peform Mathematical operations like the union, intersection, difference, symmetric difference, and others.

    Python's sets have all of these operations built-in, via methods or operators.

    For each, we'll show the two equivalent methods:

    
    # union: items appearing in either
    
    primes = {2, 3, 5, 7}
    odds = {1, 3, 5, 7, 9}
    primes | odds      # with an operator
    primes.union(odds) # equivalently with a method
    
    
    OUTPUT
    
    {1, 2, 3, 5, 7, 9}
    
    
    
    # intersection: items appearing in both
    
    primes = {2, 3, 5, 7}
    odds = {1, 3, 5, 7, 9}
    primes & odds             # with an operator
    primes.intersection(odds) # equivalently with a method
    
    
    OUTPUT
    
    {3, 5, 7}
    
    
    
    # difference: items in primes but not in odds
    
    primes = {2, 3, 5, 7}
    odds = {1, 3, 5, 7, 9}
    primes - odds           # with an operator
    primes.difference(odds) # equivalently with a method
    
    
    OUTPUT
    
    {2}
    
    
    
    # symmetric difference: items appearing in only one set
    
    primes = {2, 3, 5, 7}
    odds = {1, 3, 5, 7, 9}
    primes ^ odds                     # with an operator
    primes.symmetric_difference(odds) # equivalently with a method
    
    
    OUTPUT
    
    {1, 2, 9}
    
    

    Set Methods

  • add() method adds an element to the set. If the element already exists, the add() method does not add the element.
  • 
    # add() will add new item to the set
    
    positions = {'forward', 'midfield', 'defender'}
    positions.add('goalkeeper')
    print(positions)
    
    
    OUTPUT
    
    {'midfield', 'defender', 'goalkeeper', 'forward'}
    
    

  • clear() method removes all elements in a set.
  • 
    # clear() removes all elements in a set
    
    positions = {'forward', 'midfield', 'defender', 'goalkeeper'}
    positions.clear()
    print(positions)
    
    
    OUTPUT
    
    set()
    
    

  • copy() method copies the set.
  • 
    # copy() method copies the set.
    
    ranNum = {12, 65, 11, 56, 9}
    newNum = ranNum.copy()
    print(newNum)
    
    
    OUTPUT
    
    {65, 56, 9, 11, 12}
    
    

  • remove() method removes the specified element from the set. This method will raise an error if the specified item does not exist.
  • 
    # remove() will remove specified element from set
    
    workingDays = {'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sun'}
    workingDays.remove('Sun')
    print(workingDays)
    
    
    OUTPUT
    
    {'Wed', 'Mon', 'Thu', 'Tue', 'Fri'}
    
    

  • discard() method removes the specified element from the set. This method will NOT raise an error, if the specified item does not exist.
  • 
    # discard() will remove specified element from set
    
    workingDays = {'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sun'}
    workingDays.discard('Sun')
    print(workingDays)
    
    
    OUTPUT
    
    {'Wed', 'Mon', 'Thu', 'Tue', 'Fri'}
    
    

  • pop() method removes a random item from the set. This method returns the removed item.
  • 
    # pop() will remove random item from the set
    
    userDetails = {'Jasmine', 'Dsouza', 'Female', 20, True, 8.4, 'Physics'}
    userDetails.pop()
    print(userDetails)
    
    
    OUTPUT
    
    {True, 'Jasmine', 20, 8.4, 'Physics', 'Female'}
    
    

  • update() method updates the current set, by adding items from another set (or any other iterable). If an item is present in both sets, only one appearance of this item will be present in the updated set.
  • 
    # update() will update the current set, by adding items from another set
    
    setOne = {'Python', 'C++', 'Java'}
    setTwo = {'Python', 'JavaScript', 'SQL'}
    
    setOne.update(setTwo)
    print(setOne)
    
    
    OUTPUT
    
    {'C++', 'JavaScript', 'Python', 'SQL', 'Java'}
    
    

  • issubset() method returns True if all items in the set exists in the specified set, otherwise it retuns False.
  • 
    # issubset() returns True if all items in set exists in specified set
    
    primes = {3, 5, 7}
    odds = {3, 5, 7, 9}
    
    primes.issubset(odds)
    
    
    OUTPUT
    
    True
    
    

  • issuperset() method returns True if all items in the specified set exists in the original set, otherwise it retuns False.
  • 
    # issuperset() returns True if all items in specified set exists in original set
    
    primes = {3, 5, 7}
    odds = {3, 5, 7, 9}
    
    odds.issuperset(primes)
    
    
    OUTPUT
      
    True
    
    

    frozensets

    frozenset() function returns an immutable / unchangeable frozenset object (which is like a set object, only unchangeable).

    Return a new set or frozenset object whose elements are taken from iterable and are unique (does not allow duplicate elements).

    frozensets can be created using type constructor: frozenset() , frozenset('foobar') , frozenset(['a', 'b', 'foo'])

    You cannot access items in a frozenset by referring to an index or a key.

    You can loop through the frozenset items using a for loop, or ask if a specified value is present in a set, by using the in keyword.


    
    # Creating frozenset() using iterables
    
    frozensetOne = frozenset({'Kiwi','Figs','Papaya'})   # using Set
    frozensetTwo = frozenset(['Carrot','Beet','Onion'])  # using List
    frozensetThree = frozenset("WORKZAM")                # using String
    frozensetFour = frozenset((12, 34, 67, 99, 45, 51))  # using Tuples
    frozensetFive = frozenset({'BTC':1,'ETH':2,'ADA':3}) # using Dictionary
    frozensetSix = frozenset(range(10))                  # using Range
    
    print(frozensetOne)
    print(frozensetTwo)
    print(frozensetThree)
    print(frozensetFour)
    print(frozensetFive)
    print(frozensetSix)
    
    
    OUTPUT
    
    frozenset({'Figs', 'Kiwi', 'Papaya'})
    frozenset({'Onion', 'Beet', 'Carrot'})
    frozenset({'O', 'K', 'Z', 'M', 'R', 'A', 'W'})
    frozenset({34, 99, 67, 12, 45, 51})
    frozenset({'ETH', 'ADA', 'BTC'})
    frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
    
    
    
    # Loop through the frozenset using 'for'
    
    colors = frozenset({"red", "green", "blue"})
    for color in colors:
        print(color)
    
    
    OUTPUT
    
    red
    blue
    green
     
    
    
    # Check if an element exists 'in' the frozenset
    
    colors = frozenset({"red", "green", "blue"})
    isColorExists = 'red' in colors
    print(isColorExists)
    
    
    OUTPUT
    
    True
      
    

    Control Flow in Python

  • Control flow is where the rubber really meets the road in programming. Without it, a program is simply a list of statements that are sequentially executed.
  • With control flow, you can execute certain code blocks conditionally and/or repeatedly.
  • A programming language uses control statements to control the flow of execution of a program based on certain conditions. These are used to cause the flow of execution to advance and branch based on changes to the state of a program.
  • Control flow statements in Python can be put into three broad categories:
    1. [1]. Decision Making statements ( if, elif, else )
    2. [2]. Loop statements ( for...in, while )
    3. [3]. Jump statements ( break, continue, pass )
  • Decision making statements

  • Decision making statements are sometimes also known as "Conditional statements" often referred to if-else statements, allow the programmer to execute certain pieces of code depending on some Boolean condition.
  • Decision making statements in Python are:
    1. [1]. if statement
    2. [2]. elif statement
    3. [3]. else statement
  • Decision-making statements evaluate the Boolean expression and control the program flow depending upon the result of the condition provided.
  • Python adopts the if and else often used in other languages; its more unique keyword is elif, a contraction of else if. In these conditional clauses, elif and else blocks are optional; additionally, you can optinally include as few or as many elif statements as you would like.
  • Simple if statement :

  • if statement is the most simple decision-making statement.
  • An "if statement" is written by using the if keyword.
  • It is used to decide whether a certain statement or block of statements will be executed or not i.e if a certain condition is true then a block of statement is executed otherwise not.
  • The condition after evaluation will be either True or False
  • Python relies on indentation (whitespace at the beginning of a line) to define scope in the code. Other programming languages often use curly-brackets for this purpose.
  • Note especially the use of colons : after the if condition and whitespace in the next-line to denote blocks of code.
  • Example of Simple if statement:

    
    # Simple 'if' statement
    
    num1 = 24
    num2 = 12
    
    if num1 > num2:         # if condition is True, hence "if" block will be executed
        print(f'num1({num1}) is greater than num2({num2})')
        
    if num2 > num1:         # if condition is False, hence "if" block will NOT be executed
        print(f'num2({num2}) is greater than num1({num1})')
                    
                    
    
    OUTPUT
    
    num1(24) is greater than num2(12)
    
    
    
    # IndentationError: expected an indented block
    
    num1 = 15
    num2 = 10
    
    if num1 >= num2:               
    print(f'num1({num1}) is greater than num2({num2})')   # Error, as no whitespace is left to denote block of code
                
                    
    
    OUTPUT
    
    IndentationError: expected an indented block
    
    

    if...else statement :

  • The if statement alone tells us that if a condition is True it will execute a block of statements and if the condition is False it won't.
  • But what if we want to do something else if the condition is False. Here comes the else statement.
  • We can use the else statement with if statement to execute a block of code when the condition is False.
  • 
    # 'if...else' statement
    
    num1 = 36
    num2 = 48
    
    if num1 > num2:        # if condition is False, hence "if" block will NOT be executed
        print(f'num1({num1}) is greater than num2({num2})')
    else:                  # else block will be executed
        print(f'num1({num1}) is lesser than num2({num2})')
                
    
    OUTPUT
    
    num1(36) is lesser than num2(48)
    
    

    Nested if statement :

  • A nested if is an if statement that is the target of another if or else.
  • Nested if statements mean an if statement inside an if statement.
  • Yes, Python allows us to nest if statements within if statements. i.e, we can place an if statement inside another if statement.
  • 
    # Nested "if/else" statement       
    
    ranNum = 28
    
    if (ranNum == 28 or ranNum <= 30):  # if condition is True, hence "if" block will be executed
        if ranNum < 30:                 # if condition is True, hence nested "if" block will be executed
            print('ranNum is smaller than 30')
        if ranNum < 15:                 # if condition is False, hence nested "if" block will NOT be executed
            print('ranNum is smaller than 15')
    else:                               # never executes else block
        print('ranNum is larger than 30')
            
    
    OUTPUT
    
    ranNum is smaller than 30
    
    

    if-elif-else ladder statement :

  • The if statements are executed from the top down.
  • As soon as one of the conditions controlling the if is True, the statement associated with that if is executed, and the rest of the ladder is bypassed.
  • If none of the conditions is True, then the final else statement will be executed.
  • 
    # if-elif-else ladder statement
    
    givenNum = 100
    
    if givenNum == 25:            # if condition is False, hence "if" block will NOT be executed
        print('givenNum is 25')
    elif givenNum == 50:          # if condition is False, hence "elif" block will NOT be executed
        print('givenNum is 50')    
    elif givenNum == 75:          # if condition is False, hence "elif" block will NOT be executed
        print('givenNum is 75')
    elif givenNum == 100:         # if condition is True, hence "elif" block will be executed 
        print('givenNum is 100')
    else:                         # never executes else block     
        print('givenNum is INVALID')
                
    
    OUTPUT
    
    givenNum is 100
    
    

    Shorthand if statement :

  • If you have only one statement to execute, you can put it on the same line as the if statement.
  • 
    # Shorthand "if" statement
    
    weightOne = 225 
    weightTwo = 125
    
    if weightOne > weightTwo: print("weightOne is heavier")
                    
    
    OUTPUT
    
    weightOne is heavier
    
    

    Shorthand if...else statement :

  • If you have only one statement to execute, one for if, and one for else, you can put it all on the same line.
  • This technique is known as Ternary Operators, or Conditional Expressions.
  • 
    # Shorthand "if...else" statement
    
    num1 = 144 
    num2 = 169
    
    print("num1 is largest") if num1 > num2 else print("=") if num1 == num2 else print("num2 is largest")
                            
    
    OUTPUT
    
    num2 is largest
      
    

    Loops in Python

  • Loops are basically a simple set of instructions that gets repeated until a condition is met.
    1. In python, we have two main tools to use for looping
    2. [1]. for Loops
    3. [2]. while Loops
  • In Python; we prefer to think of else in loop as a nobreak statement: that is, the else block is executed only if the loop ends naturally, without encountering a break statement.
  • for Loop

  • for loop is used for sequential traversal i.e. it is used for iterating over an iterable like string, tuple, list, set, range(), forzenset() or dictionary.
  • In Python, there is "no" C / Java style for loop, i.e., for (i=0; i<n; i++). In Python, for loops only implements the collection-based iteration.
  • The indented statements inside the for loops are executed once for each item in an iterable.
  • Notice the simplicity of the for loop: we specify the variable we want to use, the sequence we want to loop over, and use the in operator to link them together in an intuitive and readable way.
  • The object to the right of the in keyword, can be any Python iterator. An iterator can be thought of as a generalized sequence.
  • The for loop does not require an indexing variable to set beforehand.
  • The else keyword in a for loop specifies a block of code to be executed when the loop is finished.
  • The else block will NOT be executed if the for loop is stopped by a break statement.
  • A nested for loop is a for loop inside a for loop. The "inner loop" will be executed one time for each iteration of the "outer loop".
  • 
    # Using "for" loop to iterate list object
    
    primes = [2, 3, 5, 7, 11, 13]
    for prime in primes:
        print(prime, end=" ")    # print all on same line
    else:
        print("\nList finished!")
        
    
    OUTPUT
    
    2 3 5 7 11 13 
    List finished!
      
    
    
    # Nested "for" loops
    
    colors = ("red", "green")
    fruits = ["apples", "grapes"]
    for color in colors:
        for fruit in fruits:
            print(f"{fruit} are {color}.")
                    
    
    OUTPUT
    
    apples are red.
    grapes are red.
    apples are green.
    grapes are green.
    
    

    while Loop

  • while loop is used to execute a block of statements repeatedly until a given condition is satisfied.
  • When the condition becomes False, the line immediately after the loop in the program is executed.
  • while loop falls under the category of indefinite iteration. Indefinite iteration means that the number of times the loop is executed isn't specified explicitly in advance.
  • Statements represent all the statements indented by the same number of character spaces after a programming construct are considered to be part of a single block of code
  • Python uses indentation as its method of grouping statements.
  • When a while loop is executed, expression is first evaluated in a Boolean context and if it is True, the loop body is executed. Then the expression is checked again, if it is still True then the body is executed again and this continues until the expression becomes False.
  • With the else statement we can run a block of code once when the condition no longer is True
  • The else block will NOT be executed if the while loop is stopped by a break statement.
  • A nested while loop is a while loop inside a while loop.
  • 
    # Using "while" loop to print numbers less than 10
    
    i = 0
    while (i < 10):
        print(i, end=' ')
        i+=1     # remember to increment i, or else loop will continue forever
    else:
        print("\n'i' is no longer less than 10")
    
    
    OUTPUT
    
    0 1 2 3 4 5 6 7 8 9 
    'i' is no longer less than 10
        
    
    
    # Nested "while" loop
    
    i=1
    while i<=6:
        j=1
        while j<=i:
            print(j,end=" ")
            j+=1
        print()
        i+=1
    
    
    OUTPUT
    
    1 
    1 2 
    1 2 3 
    1 2 3 4 
    1 2 3 4 5 
    1 2 3 4 5 6 
    
    
    
    # Using nested "while" loop to display multiplication table
    
    i = 1
    while i <= 10:
        j = 1
        while j <= 10:
            print(f"{(i*j) : 4.0f}", end=" ")
            j += 1
        i += 1
        print("")
                    
    
    OUTPUT
    
      1    2    3    4    5    6    7    8    9   10 
      2    4    6    8   10   12   14   16   18   20 
      3    6    9   12   15   18   21   24   27   30 
      4    8   12   16   20   24   28   32   36   40 
      5   10   15   20   25   30   35   40   45   50 
      6   12   18   24   30   36   42   48   54   60 
      7   14   21   28   35   42   49   56   63   70 
      8   16   24   32   40   48   56   64   72   80 
      9   18   27   36   45   54   63   72   81   90 
     10   20   30   40   50   60   70   80   90  100 
    
    

    Jump statements : break, continue, pass

  • Jump statements are the loop control statements that change the execution from its normal sequence.
  • break statement: The break statement breaks-out of the loop entirely.
    With the break statement we can stop the for loop before it has looped through all the items.
    With the break statement we can stop the while loop even if the condition is True.
  • continue statement: The continue statement skips the remainder of the current loop, and goes to the next iteration.
    With the continue statement we can stop the current iteration of the for or while loop, and continue with the next.
  • pass statement: In Python, pass is a null statement. The interpreter does not ignore a pass statement, but nothing happens and the statement results into no operation.
    The pass statement is useful when you don't write the implementation of a function but you want to implement it in the future.
  • Example of using break statement for a less trivial task. This loop will fill a list with all Fibonacci numbers up to a certain value:

    
    # "break" statement for printing Fibonacci numbers 
    
    a, b = 0, 1
    maxNum = 100
    listFibo = []
    
    while True:
        listFibo.append(a)
        (a, b) = (b, a + b)
        if a > maxNum:
            break
            
    print(listFibo)
    
    
    OUTPUT
    
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    
    

    Example of using continue to print a string of odd numbers. In this case, the result could be accomplished just as well with an if...else statement, but sometimes the continue statement can be a more convenient way to express the idea you have in mind:

    
    # "continue" statement for printing ODD numbers
    
    for num in range(20):
        # if the remainder of num / 2 is 0, skip the rest of the loop
        if num % 2 == 0:
            continue
        print(num, end=' ')
    
    
    OUTPUT
    
    1 3 5 7 9 11 13 15 17 19 
    
    

    Example of using passto reserve for future use.

    
    # "pass" statement to reserve for future use
    
    listRange = list(range(2,10,2))  # [ 2, 4, 6, 8]
    for evenNum in listRange:
        if evenNum == 6:
            pass    # reserve for future use
        print(evenNum, end=' ')
    
    
    OUTPUT
    
    2 4 6 8 
      
    

    Functions in Python

  • A function is a block of organized, reusable code that is used to perform a single, related action. Functions provide better modularity for your application and a high degree of code reusing.
  • One way to organize our Python code and to make it more readable and reusable is to factor-out useful pieces into reusable function.
  • The function will allow you to call the same block of code without having to write it multiple times. This in turn will allow you to create more complex Python scripts.
  • Here we'll cover two ways of creating functions:

  • the def statement, useful for any type of function, and
  • the lambda statement, useful for creating short anonymous functions.
  • Function definition & invoking

  • The keyword def introduces a function definition.
  • It must be followed by the function name and the parenthesized list of formal parameters.
  • The statements that form the body of the function start at the next line, and must be indented.
  • The first statement of the function body can optionally be a string literal; this string literal is the function's documentation string, or docstring.
  • To call a function, use the function name followed by parenthesis.
  • 
    # Using "def" for functions 
    
    # function definition or declaration
    def createFullName():
        '''The function createFullName() will create
        fullname using fname and lname variable.'''
        fname = "Guido"
        lname = "van Rossum"
        fullname = fname + " " + lname
        print(fullname)
    
    # function invoking / calling
    createFullName()
                
    
    
    OUTPUT
    
    Guido van Rossum
    
    
    
    # Accessing the docstring using __doc__ method 
    
    def createFullName():
        '''The function createFullName() will create
    fullname using fname and lname variable.'''
        fname = "Guido"
        lname = "van Rossum"
        fullname = fname + " " + lname
        print(fullname)
    
    # Accessing Docstrings
    print(createFullName.__doc__)
                
    
    OUTPUT
    
    The function createFullName() will create
    fullname using fname and lname variable.
    
    

    Function Arguments & Parameters

  • Information / data can be passed into functions as arguments.
  • An argument is the value that is sent to the function when it is called/invoked.
  • A parameter is the variable listed inside the parentheses in the function definition.
  • Arguments/Parameters are specified after the function name, inside the parentheses. You can add as many arguments/parameters as you want, just separate them with a comma.
  • 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.
  • To let a function return a value, use the return statement
  • What is the difference between return and print ?

    The return keyword allows you to actually save the result of the output of a function as a variable.
    The print() function simply displays the output to you, but doesn't save it for future use. print() doesn't return any value, as it returns None

    
    # function Parameters & arguments
    
    def createFullName(fname, lname):    # 2 Parameters used in function declaration
        '''The function createFullName() will create
    fullname using fname and lname variable.'''
        fullname = fname + " " + lname
        return fullname                  # function return a value
    
    firstName = "Guido"
    lastName = "van Rossum"
    # function invoking / calling
    createFullName(firstName, lastName)  # 2 Arguments used in function invoking            
    
    
    OUTPUT
    
    'Guido van Rossum'
    
    

    Default parameter value

  • The most useful form is to specify a default value for one or more parameter.
  • Defaulting parameter values will creates a function that can be called with fewer arguments than it is defined to allow.
  • If we call the function without argument, it uses the default value.
  • The below example function can be called/invoked in several ways:
    1. [a]. giving only the mandatory argument: ask_ok('Enter the city name : ')
    2. [b]. giving one of the optional arguments: ask_ok('Enter the city name : ', 2)
    3. [c]. or even giving all arguments: ask_ok('Enter the city name : ', 2, 'Just asked to enter city name!')
  • 
    # Default function Parameter values
    
    def playerClub(club = "no one"):
        print(f"I play for {club}.")
    
    playerClub("Barcelona")
    playerClub()           # default parameter is set 
    playerClub("Al-Nassr")
      
    
    OUTPUT
    
    I play for Barcelona.
    I play for no one.
    I play for Al-Nassr.
    
    
    
    # Default function Parameter values
    
    def ask_ok(place, retries=3, reminder='Please try again!'):
        while True:
            city = input(place)
            if city in ('Bengaluru', 'Hyderabad', 'Chennai', 'Kolkata', 'Mumbai', 'Delhi'):
                return True
            if city in ('Pune', 'Mysore', 'Lucknow', 'Surat', 'Indore', 'Jaipur'):
                return False
            retries = retries - 1
            if retries < 0:
                raise ValueError('invalid user response')
            print(reminder)
            
    # function invoking using only mandatory argument        
    ask_ok('Enter the city name : ')
    
    
    OUTPUT
    
    Enter the city name : Delhi
    True
    
    

    Flexible/Arbitrary Arguments : *args & **kwargs

  • Sometimes you might wish to write a function in which you don't initially know how many arguments the user will pass.
  • In this case, you can use the special form *args and **kwargs to catch all arguments that are passed.
  • Here it is not the names args and kwargs that are important, but the * characters preceding them.
  • args and kwargs are just the variable names often used by convention, short for arguments and keyword arguments.
  • The operative difference is the asterisk characters: a single * before a variable means "expand this as a sequence", while a double ** before a variable means "expand this as a dictionary key = value".
  • 
    # Arbitrary Arguments *args
    
    def getMonth(*month):
        print(f"The sixth month is {month[5]}")
        
    getMonth('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')    
    
    
    OUTPUT
    
    The sixth month is Jun
    
    
    
    # Arbitrary Keyword Arguments **kwargs
    
    def getMonth(**month):
        print(f"The month Jun has the value {month['Jun']}")
        
    getMonth(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6)
    
    
    OUTPUT
    
    The month Jun has the value 6
    
    
    
    # Arbitrary Arguments *args and Arbitrary Kword Arguments **kwargs
    
    def catch_all(*args, **kwargs):
        print("args =", args)
        print("kwargs = ", kwargs)
        
    catch_all(11, 12, 23, 343, 45, 45, a=22, b=33, c=44, d=22, e=55)
    
    
    OUTPUT
    
    args = (11, 12, 23, 343, 45, 45)
    kwargs =  {'a': 22, 'b': 33, 'c': 44, 'd': 22, 'e': 55}
    
    

    Recursion Function

  • A Recursion function is defined as a function that calls itself.
  • Recursion 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 the below example, factorial() is a function that we have defined to call itself ("recurse").
  • Suppose we want to find the factorial of 5, then it will goes as below:

    0! = 1
    1! = 1 x 0! = 1 x 1 = 1
    2! = 2 x 1! = 2 x 1 = 2
    3! = 3 x 2! = 3 x 2 = 6
    4! = 4 x 3! = 4 x 6 = 24
    5! = 5 x 4! = 5 x 24 = 120

    
    # Function recursion example
    
    def factorial(num):
        if num == 1:
            return 1
        else:
            result = num * factorial(num-1)
            return result
    
    randNum = 5
    funcRtn = factorial(num)
    print(f"The factorial of {randNum} is {funcRtn}")
    
    
    OUTPUT
    
    The factorial of 5 is 120
    
    

    lambda Expressions

  • Small anonymous functions can be created with the lambda keyword.
  • Lambda functions can be used wherever function objects are required.
  • lambda function can take any number of arguments, but can only have one expression.
  • Like nested function definitions, lambda functions can reference variables from the containing scope.
  • The power of lambda is better shown when you use them as an anonymous function inside another function.
  • SYNTAX:
    lambda arguments : expression
  • 
    # lambda Expressions Example
    
    addNum = lambda num1, num2: num1 + num2
    addNum(12, 13)
    
    
    OUTPUT
    
    25
    
    
    
    # Using lambda expression to return a function.
    
    def lambdaRtn(num):
        return lambda x: x + num
    
    incrementor = lambdaRtn(100)
    incrementor(1)
    
    
    OUTPUT
    
    101
    
    
    
    # Pass a lambda function as an argument 
    
    pairs = [('one', 1), ('two', 2), ('three', 3), ('four', 4)]
    pairs.sort(key = lambda pair: pair[0])    # argument to sort() method
    print(pairs)
    
    
    OUTPUT
    
    [('four', 4), ('one', 1), ('three', 3), ('two', 2)]
    
    
    
    # Pass a lambda function as an argument
    
    nums = [1, 2, 3, 4, 5, 6]
    newListOne = list(map(lambda m: m ** 2, nums))
    newListTwo = list(filter(lambda n: n % 2 == 0,nums))
    print(newListOne)
    print(newListTwo)
                
    
    OUTPUT
    
    [1, 4, 9, 16, 25, 36]
    [2, 4, 6]
    
    

    Python Iterators

  • An iterator is an object that contains a countable number of values.
  • An iterator is an object that can be iterated upon, meaning that you can traverse through all the values.
  • Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__() .
  • iter() function is used to return an iterator for the object. The iter() is used to create an object that will iterate one element at a time.
  • To prevent the iteration to go on forever, we can use the StopIteration statement.
  • Iterator vs Iterable: Lists, tuples, dictionaries and sets are all iterable objects. They are iterable containers which you can get an iterator from. All these objects have a iter() method which is used to get an iterator.
  • In Python 3, range() is not a list, but is an iterator.


  • Consider the below example of container object list which is be looped over using a for statement.

  • Behind the scenes, the for statement calls iter() on the container object.
  • The function returns an iterator object that defines the method __next__() which accesses elements in the container one at a time.
  • When there are no more elements, __next__() raises a StopIteration exception which tells the for loop to terminate.
  • You can call the __next__() method using the next() built-in function; this example shows how it all works.
  • 
    # Behind the scenes, the 'for' statement calls iter() on container object - list
    
    primes = [2, 3, 5, 7]
    for num in primes:
        print(num)
                    
    
    OUTPUT
    
    2
    3
    5
    7
    
    

    Iterating over lists

    From the above example of "primes" list, the familiar "for num in primes" syntax allows us to repeat some operation for each value in the list. The fact that the syntax of the code is so close to its English description for [each] value in [the] list is just one of the syntactic choices that makes Python such an intuitive language to learn and use.

    But the face-value behavior is not what's really happening. When you write something like "for num in primes", the Python interpreter checks whether it has an iterator interface, which you can check yourself with the built-in iter() method:

    
    # iterator object created by iter() method
    
    primes = [2, 3, 5, 7]
    iter(primes)
    
    
    OUTPUT
    
    <list_iterator at 0x25a49513c10>
    
    

    It is this iterator object that provides the functionality required by the for loop. The iter object is a container that gives you access to the next object for as long as it's valid, which can be seen with the built-in method next().

    What is the purpose of this level of indirection? Well, it turns out this is incredibly useful, because it allows Python to treat things as lists that are not actually lists.

    
    # Using iter() & next() method 
    
    primes = [2, 3, 5, 7]
    num = iter(primes)
    print(next(num))
    print(next(num))
    print(next(num))
    print(next(num))            
    
    
    OUTPUT
    
    2
    3
    5
    7
    
    

    range() : Indirect iteration

  • The most common example of indirect iteration is the range() function, which returns not a list, but a special range() object
  • range() like a list, exposes an iterator.
  • The benefit of the iterator indirection is that the full list is never explicitly created.
  • In the below example, If range were to actually create that list of one trillion values, it would occupy tens of terabytes of machine memory: a waste, given the fact that we're ignoring all but the first 10 values.
  • 
    # range() method for Indirect iteration
    
    N = 10 ** 12
    for i in range(N):
        if i >= 10: break
        print(i, end=', ')
    
    
    OUTPUT
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
    
    

    Useful Iterators : enumerate, zip, map, filter

    Here we'll cover some of the more useful iterators in the Python language:

    enumerate()

  • enumerate() function takes a collection (e.g. list, tuple etc) and returns it as an enumerate object.
  • enumerate() function adds a counter as the key of the enumerate object.
  • SYNTAX:
    enumerate( iterable, start = 0 )

    where, "iterable" must be a sequence, an iterator, or some other object which supports iteration.
  • The __next__() method of the iterator returned by enumerate() returns a tuple containing a count (from start which defaults to 0) and the values obtained from iterating over iterable.
  • Often you need to iterate not only the values in a list, but also keep track of the index.
  • 
    # enumerate() on tuple
    
    perfectNum = ( 6, 28, 496, 8128 )
    for i, num in enumerate(perfectNum):
        print(i, num)
    
    
    OUTPUT
    
    0 6
    1 28
    2 496
    3 8128
    
    
    
    # enumerate() on list with "start" argument
    
    seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    for i, season in enumerate(seasons, start = 1):
        print(i, season)    
    
    
    OUTPUT
    
    1 Spring
    2 Summer
    3 Fall
    4 Winter
    
    

    zip()

  • zip() method is used to iterate multiple iterables in parallel (simultaneously), producing tuples with an item from each one.
  • More formally: zip() returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument iterables.
  • Another way to think of zip() is that it turns rows into columns, and columns into rows. This is similar to transposing a matrix.
  • SYNTAX:
    zip(iterator1, iterator2,...)
  • One thing to consider is that the iterables passed to zip() could have different lengths; sometimes by design, and sometimes because of a bug in the code that prepared these iterables.
    1. [1]. By default, zip() stops when the shortest iterable is exhausted. It will ignore the remaining items in the longer iterables, cutting off the result to the length of the shortest iterable.
    2. [2]. Shorter iterables can be padded with a constant value to make all the iterables have the same length. This is done by itertools.zip_longest().
  • 
    # zip() method on multiple lists
    
    numbers = list(range(1, 4))
    shoppingList = ['Tea', 'Milk', 'Honey']
    
    for item in zip(numbers, shoppingList):
        print(item)
    
    
    OUTPUT
     
    (1, 'Tea')
    (2, 'Milk')
    (3, 'Honey')
    
    
    
    # zip() stops when shortest iterable (tuple) is exhausted
    
    L = (2, 4, 6, 8, 10)    # shortest will determine length of zip
    C = (1, 2, 3, 4, 5, 6, 7, 8, 9)
    R = (3, 6, 9, 12, 15, 18)
    for lval, cval, rval in zip(L, C, R):
        print((lval, cval, rval))
                    
    
    OUTPUT
    
    (2, 1, 3)
    (4, 2, 6)
    (6, 3, 9)
    (8, 4, 12)
    (10, 5, 15)
    
    
    
    # Transpose rows & columns using zip() method
    
    matrix = [ [11, 12, 13, 14],
                [15, 16, 17, 18],
                [19, 20, 21, 22] ]
    
    zipList = list(zip(*matrix))
    print(zipList)  
    
    
    OUTPUT
    
    [(11, 15, 19), (12, 16, 20), (13, 17, 21), (14, 18, 22)]
    
    
    
    # Padding with a default value for shorter iterables
    
    from itertools import zip_longest
      
    numbers = [1, 2, 3, 4, 5]
    shoppingList = ['Tea', 'Milk', 'Honey']
    
    newList = list(zip_longest(numbers, shoppingList))
    print(newList)
                
    
    OUTPUT
    
    [(1, 'Tea'), (2, 'Milk'), (3, 'Honey'), (4, None), (5, None)]
    
    

    map()

  • map() method return an iterator that applies function to every item of iterable, yielding the results.
  • If additional iterables arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel.
  • With multiple iterables, the iterator stops when the shortest iterable is exhausted.
  • SYNTAX:
    map( function, iterable, *iterables )
  • 
    # Using map(), find square of first 10 numbers
    
    squares = lambda x: x ** 2
    for val in map(squares, range(1,11) ):
        print(val, end=' ')
    
    
    OUTPUT
    
    1 4 9 16 25 36 49 64 81 100 
    
    
    
    # Passing Multiple Iterators to map() method
    
    num1 = list(range(1,6))          # equal to [1,2,3,4,5]
    num2 = [10, 20, 30, 40, 50]
    incrementor = lambda x , y: x + y
    
    result = map(incrementor, num1, num2 )
    
    #convert the map into a list, for readability:
    newList = list(result)          
    print(newList)            
    
    
    OUTPUT
      
    [11, 22, 33, 44, 55]
    
    
    
    # map() can listify the list of strings individually
    
    exampleList = ['skill', 'zam', 'work']
    result = list(map(list, exampleList))
    print(result)            
    
    
    OUTPUT
    
    [['s', 'k', 'i', 'l', 'l'], ['z', 'a', 'm'], ['w', 'o', 'r', 'k']]
    
    

    filter()

  • filter() method is used to construct an iterator from those elements of iterable for which function returns True.
  • SYNTAX:
    filter( function, iterable )
  • "iterable" may be either a sequence, a container which supports iteration, or an iterator.
  • If function is None, the identity function is assumed, that is, all elements of iterable that are False are removed.
  • 
    # Using filter(), find single digit even numbers
    
    isEven = lambda x: x % 2 == 0
    for num in filter(isEven, range(1, 10)):
        print(num, end=' ')            
    
    
    OUTPUT
    
    2 4 6 8 
    
    
    
    # Using filter(), find the words that do not have vowels
    
    inputWords = ['myths', 'tea', 'milk', 'gym', 'honey', 'spy']
    
    def containsVowels(word):
        wordList = list(word)
        if 'a' in wordList:
            return False
        elif 'e' in wordList:
            return False
        elif 'i' in wordList:
            return False
        elif 'o' in wordList:
            return False
        elif 'u' in wordList:
            return False
        else:
            return True
    
    vowelWords = filter(containsVowels, inputWords)
    result = list(vowelWords)
    print(result)            
    
    
    OUTPUT
    
    ['myths', 'gym', 'spy']
    
    

    Iterators as function arguments

  • *args and **kwargs can be used to pass sequences and dictionaries to functions. It turns out that the *args syntax works not just with sequences, but with any iterator.
  • 
    # *args as the argument to print() method
    
    print( *range(10) )
    print(*map(lambda x: x ** 2, range(10)))
    
    
    OUTPUT
    
    0 1 2 3 4 5 6 7 8 9
    0 1 4 9 16 25 36 49 64 81
      
    
    
    # zip and unzip
    
    # using the zip() method
    L1 = (1, 2, 3, 4)
    L2 = ('a', 'b', 'c', 'd')
    z = zip(L1, L2)
    print(*z)
    
    # similar to unzip
    z = zip(L1, L2)
    new_L1, new_L2 = zip(*z)
    print(new_L1, new_L2) 
    
    
    OUTPUT
    
    (1, 'a') (2, 'b') (3, 'c') (4, 'd')
    (1, 2, 3, 4) ('a', 'b', 'c', 'd')
    
    

    List Comprehensions

  • List comprehensions are simply a way to compress a list building for loop into a single short, readable line.
  • List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
  • SYNTAX:
    newlist = [ expression for item in iterable if condition == True ]

    "expression" is the current item in the iteration, but it is also the outcome, which you can manipulate before it ends up like a list item in the new list,
    "item" is a variable name,
    "iterable" is any iterable Python object (like list, tuple, set, dict, str, range() etc),
    "condition" is like a filter that only accepts the items that valuate to True
  • Note that line break within the list comprehension before the ''for'' expression: this is valid in Python, and is often a nice way to break-up long list comprehensions for greater readibility.
  • Examples of list comprehension: Create a list of squares.

    
    # Square number list using list comprehension
    
    squares = [x**2 for x in range(10)]
    print(squares)            
    
    
    OUTPUT
    
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    
    
    # Even number list using list comprehension
    
    evenNum = [item for item in range(1, 10) if (item % 2 == 0)]
    print(evenNum)
    
    
    OUTPUT
    
    [2, 4, 6, 8]
    
    

    Note how the order of the for and if statements is the same in below mentioned both of snippets.

    
    # list comprehension using mulitple "for" Iterations
    
    tupleList = [(x, y) 
                 for x in [1,2,3] 
                 for y in [1,3,4] 
                 if x != y]
    print("\nCreating list using 'list comprehension'")
    print(tupleList)
    
    
    # Below code will create the same new-list as above
    combs = []
    for x in [1,2,3]:
        for y in [1,3,4]:
            if x != y:
                combs.append((x, y))
    print("\nCreating list using 'for' loop")
    print(combs)
                
    
    OUTPUT
    
    Creating list using 'list comprehension'
    [(1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4)]
    
    Creating list using 'for' loop
    [(1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4)]
    
    
    
    # Using list comprehension : 
    # Find all the tweet-tags that starts with symbol ‘@’ in a given tweet list.
    
    tweet_list = [
                    ["@elonmusk : Comedy is now legal on @twitter"],
                    ["@BarackObama : No one is born hating another person because of the color of his skin or his background or his religion"],
                    ["@andymilonakis : Congratulations to the Astronauts that left Earth today. Good choice."]
                  ]
    
    tweetTags = [ word 
                  for tweet in tweet_list 
                  for word in ''.join(tweet).split(' ') 
                  if '@' in word ]
    
    print(tweetTags)
    
    
    OUTPUT
    
    ['@elonmusk', '@twitter', '@BarackObama', '@andymilonakis']
    
    
    
    # flatten a list using a list comprehension with two 'for'
    
    nestedList = [['A','B','C'], ['D','E','F'], ['G','H','I']]
    flatList = [letter 
                for element in nestedList 
                for letter in element]
    print(flatList)
    
    
    OUTPUT
    
    ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
    
    
    
    # Transpose rows & columns using list comprehension 
    
    matrix = [ [11, 12, 13, 14],
                [15, 16, 17, 18],
                [19, 20, 21, 22] ]
    
    transposeMatrix = [[row[i] for row in matrix] for i in range(4)]
    print("Transpose rows & columns using list comprehension")
    print(transposeMatrix)
    
    
    # Transpose rows & columns using zip() method
    zipList = list(zip(*matrix))
    print("\nTranspose rows & columns using zip() method")
    print(zipList)            
    
    
    OUTPUT
    
    Transpose rows & columns using list comprehension
    [[11, 15, 19], [12, 16, 20], [13, 17, 21], [14, 18, 22]]
    
    Transpose rows & columns using zip() method
    [(11, 15, 19), (12, 16, 20), (13, 17, 21), (14, 18, 22)]
    
    

    Once you understand the dynamics of list comprehensions, it's straightforward to move on to other types of comprehensions. The syntax is largely the same; the only difference is the type of bracket you use.
    For example, with curly braces you can create a set with a set comprehension and add a colon : to create a dict comprehension:

    
    # Set & Dictionary comprehensions
    
    # add a curly braces {} to create a set comprehension
    squareSet = { n**2 for n in range(10) }
    print(type(squareSet), squareSet)
    
    # add a colon (:) to create a dict comprehension
    squareDict = { n:n**2 for n in range(10) }
    print(type(squareDict), squareDict)
    
    
    OUTPUT
     
     {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
     {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
        
    

    Generators & Generator Expressions

    Generator Expressions

  • In Python, a generator expression is a concise way to create a generator object.
  • It is similar to a list comprehension, but instead of creating a list, it creates a generator object that can be iterated over to produce the values in the generator.
  • SYNTAX:
    generatorExpressions = ( expression for item in iterable if condition == True )

    "expression" is the current item in the iteration, but it is also the outcome, which you can manipulate before it ends up like an item in the new generator Expressions.
    "item" is a variable name,
    "iterable" is any iterable Python object (like list, tuple, set, dict, str, range() etc),
    "condition" is like a filter that only accepts the items that valuate to True

  • Difference between List comprehensions & Generator Expressions

  • List comprehensions use square brackets, while generator expressions use parentheses ( ) .
  • A list is a collection of values, while a generator is a recipe for producing values.
  • The difference is that a generator expression does not actually compute the values until they are needed. This not only leads to memory efficiency, but to computational efficiency as well. This also means that while the size of a list is limited by available memory, the size of a generator expression is unlimited.
  • A list can be iterated multiple times; a generator expression is single-use i.e. its used-up after one iteration. This can be very useful because it means iteration can be stopped and started.
  • Examples of Generator Expressions:

    
    # Generator Expressions
    
    (n ** 2 for n in range(10))
    
    
    OUTPUT
    
    <generator object  at 0x0000025A4974B9E0>
    
    

    Notice that printing the generator expression does not print the contents; one way to print the contents of a generator expression is to pass it to the list constructor:

    
    # Generator Expressions passed to list constructor
    
    genExp = (n ** 2 for n in range(10))
    list(genExp)            
    
    
    OUTPUT
    
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    
    
    # Generator Expressions is single-use
    
    genExp = (n**2 for n in range(10))
    
    for num in genExp:
        print(num, end=' ')
        if num > 20: break
    
    print("\nDoing something in between...")
    
    for num in genExp:
        print(num, end=' ')
    
    newList = list(genExp)
    print("\n\nGenerator Expressions is empty : ", newList)
    
    
    OUTPUT
    
    0 1 4 9 16 25 
    Doing something in between...
    36 49 64 81 
    
    Generator Expressions is empty :  []
    
    

    Generator Functions: Using yield

  • A generator-function is defined like a normal function, but whenever it needs to generate a value, it does so with the yield keyword rather than return.
  • If the body of a def contains yield, the function automatically becomes a generator function.
  • Generator functions return a generator object.
  • Generator objects are used either by calling the next() method on the generator object or using the generator object in a for...in loop.
  • When the generator function is terminated, StopIteration is called automatically on further calls.

  • yield vs return

  • The yield statement is responsible for controlling the flow of the generator function. It pauses the function execution by saving all states and yielded to the caller. Later it resumes execution when a successive function is called. We can use the multiple yield statement in the generator function.
  • The return statement returns a value and terminates the whole function and only one return statement can be used in the function.
  • 
    # Generator Function using 'yield'
    
    genExp = (n ** 2 for n in range(10))
    print(*genExp)
    
    def gen():
        for n in range(10):
            yield n ** 2
    
    genFunc = gen()
    print(*genFunc)            
    
    
    OUTPUT
    
    0 1 4 9 16 25 36 49 64 81
    0 1 4 9 16 25 36 49 64 81
      
    
    
    # Generator Function to generate prime numbers
    
    def gen_primes(N):
        """Generate primes up to N"""
        primes = set()
        for n in range(2, N):
            if all(n % p > 0 for p in primes):
                primes.add(n)
                yield n
    
    print(*gen_primes(30))
                
    
    OUTPUT
    
    2 3 5 7 11 13 17 19 23 29
    
    

    Object Oriented Programming

  • Object-oriented programming (OOP) is a computer programming model that organizes software design around data, or objects, rather than functions and logic. An object can be defined as a data field that has unique attributes and behavior.
  • Almost everything in Python is an object. Python is an object oriented programming language.
  • An object-oriented paradigm is to design the program using classes and objects. The object is related to real-word entities such as book, customer, fruit, etc.
  • The organization of an object-oriented program also makes the method beneficial to collaborative development, where projects are divided into groups. Additional benefits of OOP include code reusability, scalability and efficiency.
  • The first step in OOP is to collect all of the objects a programmer wants to manipulate and identify how they relate to each other -- an exercise known as data modeling
  • Structure of OOP

    The structure, or building blocks, of object-oriented programming include the following:

  • Classes are user-defined data types that act as the blueprint for individual objects, attributes and methods. Classes provide a means of bundling data and functionality together.
  • Objects are instances of a class created with specifically defined data. Objects can correspond to real-world objects or an abstract entity. When class is defined initially, the description is the only object that is defined.
  • Methods are functions that are defined inside a class that describe the behaviors of an object. Each method contained in class definitions starts with a reference to an instance object. Additionally, the subroutines contained in an object are called instance methods. Programmers use methods for reusability or keeping functionality encapsulated inside one object at a time.
  • Attributes are defined in the class template and represent the state of an object. Objects will have data stored in the attributes field. Class attributes belong to the class itself.
  • Principles of OOP

    Object-oriented programming is based on the following principles:

  • Inheritance : Classes can reuse code from other classes. Relationships and subclasses between objects can be assigned, enabling developers to reuse common logic while still maintaining a unique hierarchy. This property of OOP forces a more thorough data analysis, reduces development time and ensures a higher level of accuracy.
  • Polymorphism : Objects are designed to share behaviors and they can take on more than one form. The program will determine which meaning or usage is necessary for each execution of that object from a parent class, reducing the need to duplicate code. A child class is then created, which extends the functionality of the parent class. Polymorphism allows different types of objects to pass through the same interface.
  • ncapsulation : This principle states that all important information is contained inside an object and only select information is exposed. The implementation and state of each object are privately held inside a defined class. Other objects do not have access to this class or the authority to make changes. They are only able to call a list of public functions or methods. This characteristic of data hiding provides greater program security and avoids unintended data corruption.
  • Abstraction : Objects only reveal internal mechanisms that are relevant for the use of other objects, hiding any unnecessary implementation code. The derived class can have its functionality extended. This concept can help developers more easily make additional changes or additions over time.
  • Benefits of OOP

  • Modularity : Encapsulation enables objects to be self-contained, making troubleshooting and collaborative development easier.
  • Reusability : Code can be reused through inheritance, meaning a team does not have to write the same code multiple times.
  • Productivity : Programmers can construct new programs quicker through the use of multiple libraries and reusable code.
  • Easily upgradable and scalable : Programmers can implement system functionalities independently.
  • Interface descriptions : Descriptions of external systems are simple, due to message passing techniques that are used for objects communication.
  • Security : Using encapsulation and abstraction, complex code is hidden, software maintenance is easier and internet protocols are protected.
  • Flexibility : Polymorphism enables a single function to adapt to the class it is placed in. Different objects can also pass through the same interface.
  • Difference between Procedural & Object Oriented Programming

    Procedural Programming vs Object Oriented Programming
    Parameter Procedural Programming Object Oriented Programming
    Definition This programming language makes use of a step by step approach for breaking down a task into a collection of routines (or subroutines) and variables by following a sequence of instructions. It carries out each step systematically in order so that a computer easily gets to understand what to do. This programming language uses objects and classes for creating models based on the real-world environment. This model makes it very easy for a user to modify as well as maintain the existing code while new objects get created by inheriting the characteristics of the present ones.
    Security Procedural Programming does not offer any method of hiding data. Thus, it is less secure when compared to Object Oriented Programming. Hiding data is possible with Object Oriented Programming due to the abstraction. Thus, it is more secure than the Procedural Programming.
    Method The main program gets divided into minute parts on the basis of the functions. It then treats them as separate programs for smaller programs individually. It involves the concept of classes and objects. Hence, it divides the program into minute chunks known as objects. These are actually instances of classes.
    Division of Program Procedural Programming divides the program into small programs and refers to them as functions. Object Oriented Programming divides the program into small parts and refers to them as objects.
    Movement of Data Available data is capable of moving freely within the system from one function to another. The objects are capable of moving and communicating with each other through the member functions.
    Approach The Procedural Programming follows a Top-Down approach. The Object Oriented Programming follows a Bottom-Up approach.
    Importance This programming model does not give importance to data. It prioritizes the functions along with the sequence of actions that needs to follow. This programming model gives importance to the data rather than functions or procedures. It is because it works on the basis of the real world.
    Orientation It is Structure/Procedure oriented. It is Object Oriented.
    Basis The main focus in Procedural Programming is on how to do the task, meaning, on the structure or procedure of the program. The main focus in Object Oriented Programming is on data security. Hence, it only permits objects to access the class entities.
    Type of Division It divides any large program into small units called functions. It divides the entire program into small units called objects.
    Inheritance It does not provide any inheritance. It achieves inheritance in three modes- protected, private, and public.
    Virtual Classes There is no concept of virtual classes. The concept of virtual functions appears at the time of inheritance.
    Overloading The case of overloading isn’t possible in the case of Procedural Programming. Overloading is possible in the form of operator overloading and function overloading in the case of Object Oriented Programming.
    Reusability of Code No feature of reusing codes is present in Procedural Programming. Object Oriented Programming offers the feature to reuse any existing codes in it by utilizing a feature known as inheritance.
    Most Important Attribute It prioritizes function over data. It prioritizes data over function.
    Modes of Access The Procedural Programming offers no specific accessing mode for accessing functions or attributes in a program. The Object Oriented Programming offers three accessing modes- protected, private, and public. These, then, serve as a share to access functions of attributes.
    Size of Problems It is not very suitable for solving any big or complex problems. It is suitable for solving any big or complex problems.
    Addition of New Function and Data It is not very easy to add new functions and data in the Procedural Programming. It is very easy to add new functions and data in the Object Oriented Programming.
    Access to Data In the Procedural Programming, most of the functions use global data for sharing. They can access freely from one function to another in any given system. In the Object Oriented Programming, the present data cannot easily move easily from one function to another. One can keep it private or even public. Thus, a user can control the data access.
    Data Sharing It shares the global data among the functions present in the program. It shares data among the objects through its member functions.
    Data Hiding No proper way is available for hiding the data. Thus, the data remains insecure. It can hide data in three modes- protected, private, and public. It increases the overall data security.
    Basis of World The Procedural Programming follows an unreal world. The Object Oriented programming follows the real world.
    Friend Classes or Friend Functions It doesn’t involve any concept of friend function. Any class or function is capable of becoming a friend of any other class that contains the keyword “friend.”

    Note – The keyword “friend” only works for C++.

    Examples Some common examples of Procedural Programming are C, Fortran, VB, Pascal etc The examples of Object Oriented Programming languages are Python, JavaScript, Java, C++ etc

    Class Definition : Creating class

  • Class definitions, like function definitions (def statements) must be executed before they have any effect.
  • To create a class in Python, use the keyword class
  • When a class definition is entered, a new namespace is created, and used as the local scope — thus, all assignments to local variables go into this new namespace.
  • As a best practice, class names always start with capital letters.
  • 
    # Class Definition using 'class' keyword
    
    class Customer:
        msg = 'New Customer Created'            
    
    

    Creating Class Object

  • An Object is an instance of a Class. A class is like a blueprint while an instance is a copy of the class with actual values.
  • Class objects support two kinds of operations: attribute references and instantiation.
  • An object consists of :

  • State : It is represented by the attributes of an object. It also reflects the properties of an object.
  • Behaviour : It is represented by the methods of an object. It also reflects the response of an object to other objects.
  • Identity : It gives a unique name to an object and enables one object to interact with other objects.
  • Declaring Objects : When an object of a class is created, the class is said to be instantiated. All the instances share the attributes and the behavior of the class. But the values of those attributes, i.e. the state are unique for each object. A single class may have any number of instances.

    
    # Create "class" & "object" 
    
    # Class Definition
    class Customer:
        msg = 'New Customer Created'
        
    # Instantiation of Customer class : Object 'custObj' created
    custObj = Customer()
    
    # Attribute references using dot (.)
    custObj.msg            
    
    
    OUTPUT
    
    'New Customer Created'
    
    

    Constructor Function : __init__()

  • All classes have a function called __init__() which is always executed when the class is being initiated.
  • The __init__() method is similar to constructors in C++ and Java.
  • Constructors are generally used for instantiating an object. The task of constructors is to initialize(assign values) to the data members of the class when an object of the class is created. In Python the __init__() method is called the constructor and is always called when an object is created.
  • Types of constructors :

  • default constructor : The default constructor is a simple constructor which doesn't accept any arguments. Its definition has only one argument which is a reference to the instance being constructed.
  • parameterized constructor : constructor with parameters is known as parameterized constructor. The parameterized constructor takes its first argument as a reference to the instance being constructed known as self and the rest of the arguments are provided by the programmer.
  • self Parameter :

  • The self parameter is a reference to the current instance of the class and is used to access variables that belongs to the class.
  • It does not have to be named self , you can call it whatever you like, but it has to be the first parameter of any function in the class
  • If we have a method that takes no arguments, we still have one argument (self
  • This is similar to this pointer in C++ and this reference in Java.
  • 
    # default Constructor Function __init__() method
    
    class Customer(object):
    
        # default constructor with no arguments
        def __init__(self):        
            self.msg = 'New Customer Created'
    
    custObj = Customer()
    custObj.msg
    
    
    OUTPUT
    
    'New Customer Created'
    
    
    
    # Parameterized Constructor Function __init__() method
    
    class Customer(object):
        
        def __init__(self, name, balance=0.0):
            # Return a Customer object whose fullname is *name* and starting balance is *balance*.
            self.fullname = name
            self.balance = balance
    
    custObj = Customer('Rohit Shetty', 24000)
    custObj.fullname
    
    
    OUTPUT
    
    'Rohit Shetty'
    
    

    Destructors Function : __del__()

  • Destructors are called when an object gets destroyed.
  • In Python, destructors are not needed as much as in C++ because Python has a garbage collector that handles memory management automatically.
  • __del__() method is a known as a destructor method in Python. It is called when all references to the object have been deleted i.e when an object is garbage collected.
  • 
    # Destructors Function __del__() method
    
    class Customer(object):
        
        # Initializing
        def __init__(self, name, balance=0.0):
            # Return a Customer object whose fullname is *name* and starting balance is *balance*.
            self.fullname = name
            self.balance = balance
            
        # Deleting (Calling destructor)
        def __del__(self):
            print('Destructor called, Customer deleted.')    
    
    custObj = Customer('Rohit Shetty', 24000)
    print(custObj.fullname)
    del custObj            
    
    
    OUTPUT
    
    Rohit Shetty
    Destructor called, Customer deleted.
    
    

    __str__() Function

  • __str__() function controls what should be returned when the class object is represented as a string.
  • If the __str__() function is not set, the string representation of the object is returned.
  • 
    # string representation of an object without __str__() method
    
    class Customer(object):
        
        # Initializing
        def __init__(self, name, balance=0.0):
            # Return a Customer object whose fullname is *name* and starting balance is *balance*.
            self.fullname = name
            self.balance = balance
            
    custObj = Customer('Rohit Shetty', 24000)
    print(custObj)            
    
    
    OUTPUT
    
    <__main__.Customer object at 0x0000020934C9E8E0>
    
    
    
    # string representation of an object with __str__() method
    
    class Customer(object):
        
        # Initializing
        def __init__(self, name, balance=0.0):
            # Return a Customer object whose fullname is *name* and starting balance is *balance*.
            self.fullname = name
            self.balance = balance
    
        # String representation of an object    
        def __str__(self):
            return f"{self.fullname}\'s account balance is Rs.{self.balance}/-"
            
    custObj = Customer('Rohit Shetty', 24000)
    print(custObj)            
    
    
    OUTPUT
    
    Rohit Shetty's account balance is Rs.24000/-
    
    

    Object Methods

  • Objects can also contain methods. Methods in objects are functions that belong to the object.
  • They are used to perform operations with the attributes of our objects.
  • Methods are a key concept of the OOP paradigm. They are essential to dividing responsibilities in programming, especially in large applications.
  • You can basically think of methods as functions acting on an Object that take the Object itself into account through its self argument.
  • 
    # Object Methods
    
    class Customer():
        
        # Initializing
        def __init__(self, name, balance=0.0):
            '''Return a Customer object whose fullname is *name* and starting balance is *balance*'''
            self.fullname = name
            self.balance = balance
            
        # String representation of an object    
        def __str__(self):
            return f"{self.fullname}\'s account balance is Rs.{self.balance}/-"
        
        def withdraw(self, amount):
            '''Return the balance remaining after withdrawing *amount*'''
            if amount > self.balance:
                raise RuntimeError('Amount greater than available balance.')
            self.balance -= amount
            return self.balance
    
        def deposit(self, amount):
            '''Return the balance remaining after depositing *amount*'''
            self.balance += amount
            return self.balance
        
        # Deleting (Calling destructor)
        def __del__(self):
            print('Destructor called, Customer deleted.')    
    
            
    custObj = Customer("Rohit Shetty", 31000)
    print(custObj)
    print(custObj.withdraw(1000))
    print(custObj.deposit(5000))            
    
    
    OUTPUT
     
    Rohit Shetty's account balance is Rs.31000/-
    30000
    35000
    
    

    Inheritance

  • Inheritance is a way to form new classes using classes that have already been defined.
  • The newly formed classes are called derived classes, the classes that we derive from are called base classes.
  • Important benefits of inheritance are code reuse and reduction of complexity of a program.
  • The derived classes (descendants) override or extend the functionality of base classes (ancestors).
  • To create a class that inherits the functionality from another class, send the parent class as a parameter when creating the child class.
  • Let's see an example by incorporating our previous work on the Midfielder class:

    In this example, we have two classes: Player and Midfielder. The Player is the base class, the Midfielder is the derived class.

    The derived class inherits the functionality of the base class.
    It is shown by the play() method.

    The derived class modifies existing behavior of the base class.
    shown by the whoAmI() method.

    Finally, the derived class extends the functionality of the base class, by defining a new skill() method.

    
    # Class Inheritance Example
    
    class Player:
        def __init__(self):
            print("Player created")
    
        def whoAmI(self):
            print("Football Player")
    
        def play(self):
            print("Playing Football")
    
    
    class Midfielder(Player):
        def __init__(self):
            Player.__init__(self)
            print("Midfielder created")
    
        def whoAmI(self):
            print("Football Midfielder")
    
        def skill(self):
            print("Assisting the forward")
            
    playerObj = Midfielder()
    print("--------------------")
    playerObj.whoAmI()
    playerObj.play()
    playerObj.skill()
    
    
    OUTPUT
    
    Player created
    Midfielder created
    --------------------
    Football Midfielder
    Playing Football
    Assisting the forward
    
    

    Polymorphism

  • Polymorph literally means "which takes various forms"
  • We've learned that while functions can take in different arguments, methods belong to the objects they act on.
  • In Python, polymorphism refers to the way in which different object classes can share the same method name, and those methods can be called from the same place even though a variety of different objects might be passed in.
  • The best way to explain this is by example:
  • Here we have a Midfielder class and a Defender class, and each has a skill() method. When called, each object's skill() method returns a result unique to the object.

    
    # Class Polymorphism Example
    
    class Player:
        def __init__(self):
            print("Player created")
    
        def whoAmI(self):
            print("Football Player")
    
        def play(self):
            print("Playing Football")
    
    class Midfielder(Player):
        def __init__(self):
            Player.__init__(self)
            print("Midfielder created")
    
        def whoAmI(self):
            print("Football Midfielder")
    
        def skill(self):
            print("Assisting the forward")
            
            
    class Defender(Player):
        def __init__(self):
            Player.__init__(self)
            print("Defender created")
    
        def whoAmI(self):
            print("Football Defender")
    
        def skill(self):
            print("Defending the goal-post")
            
        
    player1 = Midfielder()
    player2 = Defender()
    print("------------------------")
    player1.skill()
    player2.skill()
    
    
    OUTPUT
    
    Player created
    Midfielder created
    Player created
    Defender created
    ------------------------
    Assisting the forward
    Defending the goal-post
    
    

    Special Methods

  • Classes in Python can implement certain operations with special method names.
  • These methods are not actually called directly but by Python specific language syntax.
  • special methods are defined by their use of underscores. They allow us to use Python specific functions on objects created through our class.
  • Listed below are some special methods:

  • __init__() : All classes have a function called __init__(), which is always executed when the class is being initiated. Use the __init__() function to assign values to object properties, or other operations that are necessary to do when the object is being created.
  • __str__() : function controls what should be returned, when the class object is represented as a string. If the __str__() function is not set, the string representation of the object is returned.
  • __len__() : it is basically used to implement the len() function in Python because whenever we call the len() function then internally __len__() magic method is called. It finally returns an integer value that is greater than or equal to zero as it represents the length of the object for which it is called.
  • __del__() : a destructor method which is called as soon as all references of the object are deleted i.e when an object is garbage collected.
  • 
    # Special Methods
    
    class Book:
        def __init__(self, title, author, pages):
            print("Book is created")
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return "Title: %s, author: %s, pages: %s" %(self.title, self.author, self.pages)
    
        def __len__(self):
            return self.pages
    
        def __del__(self):
            print("Book is destroyed")
            
    book = Book("Python Notes", "Skillzam", 248)
    
    #Special Methods
    print(book)
    print(len(book))
    del book
    
    
    OUTPUT
    
    Book is created
    Title: Python Notes, author: Skillzam, pages: 248
    248
    Book is destroyed
    
    

    Modules & Packages

  • A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended.
  • A collection of related modules is Package.
  • Python Modules

    One feature of Python that makes it useful for a wide range of tasks is the fact that it comes "batteries included" - that is, the Python standard library contains useful tools for a wide range of tasks.

    On top of this, there is a broad ecosystem of third-party tools and packages that offer more specialized functionality.

    If you quit from the Python interpreter and enter it again, the definitions you have made (functions and variables) are lost.

    Therefore, if you want to write a somewhat longer program, you are better off using a text editor to prepare the input for the interpreter and running it with that file as input instead. This is known as creating a script.

  • Python has a way to put definitions in a file and use them in a script or in an interactive instance of the interpreter. Such a file is called a module; definitions from a module can be imported into other modules or into the main module.
  • A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended.
  • Within a module, the module's name (as a string) is available as the value of the global variable __name__
  • dir() method: is a built-in function to list all the function names (or variable names) in a module.
  • For Example, create a file called skillzam.py in the current directory with the following contents:

    
    # Module "skillzam.py" contains basic maths operations:
    
    def add(num1=0, num2=0):
        '''Add two numbers'''        
        return(num1+num2)
    
    def sub(num1=0, num2=0):
        '''Substract two numbers'''        
        return(num1-num2)
    
    def mul(num1=0, num2=0):
        '''Multiply two numbers'''        
        return(num1*num2)
    
    def div(num1=0, num2=0):
        '''Divide two numbers'''        
        return(num1/num2)
    
    def fib(n):
        '''Generate Fibonacci series'''   
        result = []
        a, b = 0, 1
        while a < n:
            result.append(a)
            a, b = b, a+b
        return result
    
    

    import statement: Loading Modules

    For loading built-in and third-party modules, Python provides the import statement.

    There are a few ways to use the import statement. Here they are :

  • Explicit module import
  • Explicit module import by alias
  • Explicit import of module contents
  • Implicit import of module contents


  • [1]. Explicit module import

  • Explicit import of a module preserves the module's content in a namespace.
  • A namespace is a collection of currently defined symbolic names along with information about the object that each name references.
  • You can think of a namespace as a dictionary in which the keys are the object names and the values are the objects themselves.
  • The namespace is then used to refer to its contents with a . between them.
  • For example, here we'll import the built-in math module and compute the cosine of pi:
  • 
    # Explicit import of built-in math module
    
    import math
    
    area = math.pi * 10**2
    print(f"Area of circle with radius(10 units) is {area} sq.units")
    
    
    OUTPUT
    
    Area of circle with radius(10 units) is 314.1592653589793 sq.units
    
    
    
    # Explicit import of used-defined skillzam module
    
    import skillzam
    
    res1 = skillzam.add(12,12)
    res2 = skillzam.sub(12,12)
    print(f"Addition of 12 and 12 is  {res1}")
    print(f"Subtraction of 12 from 12 is {res2}")            
    
    
    OUTPUT
    
    Addition of 12 and 12 is  24
    Subtraction of 12 from 12 is 0
      
    

    [2]. Explicit module import by alias

  • For longer module names, it's not convenient to use the full module name each time you access some element.
  • For this reason, we'll commonly use the "import ... as ..." pattern to create a shorter alias for the namespace.
  • For example, the NumPy (Numerical Python) package, a popular third-party package useful for data science, is by convention imported under the alias np:
  • 
    # Explicit built-in module "Numpy" import by alias ('np')
    
    import numpy as np
    
    arr = np.array([1, 2, 3, 4, 5])
    print(arr)
    
    
    OUTPUT
    
    [1 2 3 4 5]
    
    
    
    # Explicit used-defined module "skillzam" import by alias ('sz')
    
    import skillzam as sz
    
    res3 = sz.mul(12,12)
    res4 = sz.div(12,12)
    print(f"Multiplication of 12 and 12 is {res3}")
    print(f"Division of 12 by 12 is {res4}")            
    
    
    OUTPUT
    
    Multiplication of 12 and 12 is 144
    Division of 12 by 12 is 1.0
      
    

    [3]. Explicit import of module contents

  • Sometimes rather than importing the module namespace, you would just like to import a few particular items from the module.
  • This can be done with the "from ... import ..." pattern.
  • For example, we can import just the cos & sin functions and the pi constant from the math module:
  • 
    # Explicit import of built-in module math's contents 
    # cos() & sin() methods and pi constant
    
    from math import sin, cos, pi
    
    sin(pi)**2 + cos(pi)**2            
    
    
    OUTPUT
    
    1.0
    
    
    
    # Explicit import of used-defined module skillzam's contents 
    # add(), sub() & fib() methods 
    
    from skillzam import add, sub, fib
    
    res1 = add(12,12)
    res2 = sub(12,12)
    res5 = fib(100)
    
    print(f"Addition of 12 and 12 is  {res1}")
    print(f"Subtraction of 12 from 12 is {res2}")
    print(f"Fibonacci series numbers within 100 are :\n", res5)
    
    
    OUTPUT
     
    Addition of 12 and 12 is  24
    Subtraction of 12 from 12 is 0
    Fibonacci series numbers within 100 are :
      [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
        
    

    [4]. Implicit import of module contents

  • Finally, it is sometimes useful to import the entirety of the module contents into the local namespace.
  • This can be done with the "from ... import *" pattern:
  • This pattern should be used sparingly, if at all. The problem is that such imports can sometimes overwrite function names that you do not intend to overwrite, and the implicitness of the statement makes it difficult to determine what has changed.
  • 
    # Implicit import of built-in module math's contents
    
    from math import *
    
    squareRoot81 = sqrt(81)
    piCeil = ceil(3.142)
    eulerNumFloor = floor(2.7182)
    
    print(f"squareRoot81 = {squareRoot81}")
    print(f"piCeil = {piCeil}")
    print(f"eulerNumFloor = {eulerNumFloor}")            
    
    
    OUTPUT
    
    squareRoot81 = 9.0
    piCeil = 4
    eulerNumFloor = 2
      
    
    
    # Implicit import of user-defined module "skillzam" contents
    
    from skillzam import *
    
    fibSeries = fib(50)
    print(f"Fibonacci series numbers within 50 are :\n", fibSeries)
    
    
    OUTPUT
    
    Fibonacci series numbers within 50 are :
     [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    
    

    Packages

  • A collection of related modules is Package.
  • Packages are a way of structuring Python's module namespace by using “dotted module names”.
  • For example, the module name A.B designates a submodule named B in a package named A.
  • __init__.py files are required to make Python treat directories containing the file as packages.
  • __init__.py can just be an empty file, but it can also execute initialization code for the package or set the __all__ variable
  • 
    # Using built-in package library "numpy" to import module random
    # Accessing the method rand()
    
    from numpy import random as rd
    
    randArray = rd.rand(5,2)  
    randArray
    
    
    OUTPUT
    
    array([[0.87240903, 0.15051331],
           [0.53956761, 0.84593113],
           [0.02653546, 0.32163663],
           [0.5122181 , 0.06274799],
           [0.79155511, 0.87196709]])
    
    
    
    # Using package & sub-package to import user-defined module skillzam
    # Package is "myFirstPkg" and sub-package is "subPkg1"
    # user-defined module is skillzam.py
    
    from myFirstPkg.subPkg1 import skillzam as sz
    
    result1 = sz.add(12,12)
    result2 = sz.mul(12,12)
    print(result1)
    print(result2)
    
    
    OUTPUT
    
    24
    144
    
    

    Importing from Third-Party Modules

  • One of the things that makes Python useful, especially within the world of data science, is its ecosystem of third-party modules.
  • These can be imported just as the built-in modules, but first the modules must be installed on your system.
  • The standard registry for such modules is the Python Package Index (PyPI for short), found on the Web at http://pypi.python.org/.
  • For convenience, Python comes with a program called pip (a recursive acronym meaning "pip installs packages"), which will automatically fetch packages released and listed on PyPI (if you use Python version 2, pip must be installed separately).
  • For example, if you'd like to install the Jupyter Notebook package that I wrote, all that is required is to type the following at the command line:

    
    C:\> pip install notebook
    
    

    The source code for the package will be automatically downloaded from the PyPI repository, and the package installed in the standard Python path (assuming you have permission to do so on the computer you're using).

    Errors & Exceptions

    No matter your skill as a programmer, you will eventually make a coding mistake. Such mistakes come in three basic flavors:

  • Syntax errors : Errors where the code is not valid Python (generally easy to fix)
  • Runtime errors : Errors where syntactically valid code fails to execute, perhaps due to invalid user input (sometimes easy to fix)
  • Semantic errors or Errors in logic : code executes without a problem, but the result is not what you expect (often very difficult to track-down and fix).
  • Here we're going to focus on how to deal cleanly with runtime errors. As we'll see, Python handles runtime errors via its exception handling framework.

    Runtime Errors

    If you've done any coding in Python, you've likely come across runtime errors. They can happen in a lot of ways.

  • Note that in each case, Python is kind enough to not simply indicate that an error happened, but to spit out a meaningful exception that includes information about what exactly went wrong, along with the exact line of code where the error happened.
  • Having access to meaningful errors like this is immensely useful when trying to trace the root of problems in your code.
  • When an error occurs, or exception as we call it, Python will normally stop and generate an error message.
  • For example, if you try to reference an undefined variable:

    
    # RUNTIME ERROR : reference an undefined variable 
    
    print(AnnualPremium)
    
    
    OUTPUT
    
    NameError: name 'AnnualPremium' is not defined
    
    
    
    # RUNTIME ERROR : unsupported operand
    
    125 + 'amount'
    
    
    OUTPUT
    
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    
    
    
    # RUNTIME ERROR: float division by zero
    
    3.142 / 0
    
    
    OUTPUT
    
    ZeroDivisionError: float division by zero
    
    
    
    # RUNTIME ERROR: list index out of range
    
    numList = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    numList[200]            
    
    
    OUTPUT
    
    IndexError: list index out of range
    
    

    Exception Handling: try, except, else, finally

  • try block lets you test a block of code for errors.
  • except block lets you handle the error.
  • else block lets you execute code when there is no error.
  • finally block lets you execute code, regardless of the result of the try and except blocks. Well, the finally clause really is executed no matter what: it used to do some sort of cleanup after an operation completes.
  • 
    # Exception Handling: try, except, else, finally clause
    # try block executes successfully
    
    try:
        result = 125 / 5
        print(f"125 divided by 5 is {result}")
    except:
        print("Something is wrong.")
    else:
        print("Everything worked, just fine.")
    finally:
        print("No matter what, 'finally' will be executed!")
    
    
    OUTPUT
    
    125 divided by 5 is 25.0
    Everything worked, just fine.
    No matter what, 'finally' will be executed!
        
    

    Here we see that when the error was raised in the try statement (in this case, a ZeroDivisionError), the error was caught, and the except statement was executed.

    
    # Exception Handling: try, except, else, finally clause
    # try block has RUNTIME ERROR : ZeroDivisionError
    
    try:
        result = 3.142 / 0             # ZeroDivisionError
    except:
        print("ZeroDivisionError: float division by zero")
    else:
        print("Everything worked, just fine.")
    finally:
        print("No matter what, 'finally' will be executed!")            
    
    
    OUTPUT
    
    ZeroDivisionError: float division by zero
    No matter what, 'finally' will be executed!
      
    

    Raising Exceptions: raise

  • To throw or raise an exception, use the raise keyword.
  • We've seen how valuable it is to have informative exceptions when using parts of the Python language.
  • It's equally valuable to make use of informative exceptions within the code you write, so that users of your code (foremost yourself!) can figure out what caused their errors.
  • The way you raise your own exceptions is with the raise statement. For example:
  • As an example of where this might be useful, let's consider fibonacci function that we defined previously:

  • One potential problem here is that the input value could be negative.
  • This will not currently cause any error in the function, but we might want to let the user know that a negative N is not supported.
  • Errors stemming from invalid parameter values, by convention, lead to a ValueError being raised:
  • 
    # Exception Handling: raise, try, except clause
    
    def fibonacci(N):
        '''Generate Fibonacci Series'''
        try:
            if N < 0:
                raise ValueError("N must be non-negative")
            L = []
            a, b = 0, 1
            while len(L) < N:
                L.append(a)
                a, b = b, a + b
            return L
        except TypeError:
            return "Bad value: need to do handle it!"
    
    # NO ERROR    
    result1 = fibonacci(12) 
    print(result1)
    
    # RUNTIME ERROR : TypeError
    result2 = fibonacci("TWELVE")   
    print(result2)
    
    # RUNTIME ERROR : ValueError
    result3 = fibonacci(-12)  
    print(result3)            
    
    
    OUTPUT
    
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    Bad value: need to do handle it!
    ValueError: N must be non-negative
      
    

    Regular Expressions

  • Regex or Regular Expressions (RegEx) is a special sequence of characters that uses a search pattern to find a string or set of strings. It can detect the presence or absence of a text by matching it with a particular pattern, and also can split a pattern into one or more sub-patterns.
  • The methods of Python's str type give you a powerful set of tools for formatting, splitting, and manipulating string data. But even more powerful tools are available in Python's built-in regular expression (re) module.
  • Fundamentally, regular expressions are a means of flexible pattern matching in strings.
  • If you frequently use the command-line, you are probably familiar with this type of flexible matching with the * character, which acts as a wildcard.
  • For example, we can list all the py file (i.e., files with extension .py) by using the * wildcard to match any characters in between:

    
    C:\Users\Skillzam> dir *.py
    
    

    The Python interface to regular expressions is contained in the built-in re module; as a simple example, let's use it to duplicate the functionality of the string split() method:

    In this case, the input is "\s+": "\s" is a special character that matches any whitespace (space, tab, newline, etc.), and the "+" is a character that indicates one or more of the entity preceding it. Thus, the regular expression matches any substring consisting of one or more spaces.

    
    # Regular Expression module "re"
    
    import re
    
    txt = 'regular expression is sequence of characters that forms search pattern'
    regex = re.compile('\s+')
    regex.split(txt)            
    
    
    OUTPUT
    
      ['regular',
      'expression',
      'is',
      'sequence',
      'of',
      'characters',
      'that',
      'forms',
      'search',
      'pattern']
    
    

    Regex Functions

    re module offers a set of functions that allows us to search a string for a match.

    Method/Attribute Purpose
    match() Determine if the RE matches at the beginning of the string.
    search() Scan through a string, looking for any location where this RE matches.
    findall() Find all substrings where the RE matches, and returns them as a list.
    finditer() Find all substrings where the RE matches, and returns them as an iterator.
    
    # Regular Expression functions 
    # match(), search(), findall(), finditer()
    
    import re
    
    sentence = "regular expression, is a sequence of characters that forms a search pattern"
    
    # Compile a regular expression pattern, returning a Pattern object
    rex1 = re.compile('ar')
    rex2 = re.compile('re')
    
    # Scan through a string, looking for any location where this RE matches.
    patternSearch = rex1.search(sentence)
    
    # Determine if the RE matches at the beginning of the string.
    patternMatch = rex2.match(sentence)
    
    # Find all substrings where the RE matches, and returns them as a list.
    patternFindall = rex1.findall(sentence)
    
    # Find all substrings where the RE matches, and returns them as an iterator.
    patternFinditer = rex1.finditer(sentence)
    
    print("patternSearch:",patternSearch)
    print("patternMatch:",patternMatch)
    print("patternFindall:",patternFindall)
    print()
    print("patternFinditer:",patternFinditer)
    for item in patternFinditer:
        print(item)
    
    
    OUTPUT
    
    patternSearch: <re.Match object; span=(5, 7), match='ar'>
    patternMatch: <re.Match object; span=(0, 2), match='re'>
    patternFindall: ['ar', 'ar', 'ar']
    
    patternFinditer: <callable_iterator object at 0x000002BA46EF3550>
    <re.Match object; span=(5, 7), match='ar'>
    <re.Match object; span=(39, 41), match='ar'>
    <re.Match object; span=(63, 65), match='ar'>
    
    

    MetaCharacters

    To understand the RE analogy, MetaCharacters are useful, important, and will be used in functions of module re. Below is the list of metacharacters.

    MetaCharacters Description
    \ Used to drop the special meaning of character following it
    [ ] Represent a character class
    ^ Matches the beginning
    $ Matches the end
    . Matches any character except newline
    | Means OR (Matches with any of the characters separated by it.
    ? Matches zero or one occurrence
    * Any number of occurrences (including 0 occurrences)
    + One or more occurrences
    { } Indicate the number of occurrences of a preceding regex to match.
    ( ) Enclose a group of Regex
    
    # Regular Expression using Metacharacters
    # find all the email ID's in the given text
    
    text = "To email me, try email@example.com or the you can also try address mail_me@example.com."
    email = re.compile('\w+@\w+\.[a-z]{3}')
    result1 = email.findall(text)
    print(result1)
    
    # you can replace these email addresses with another string, 
    # perhaps to hide addresses in the output
    
    result2 = email.sub('--@--.--', text)
    print(result2)            
    
    
    OUTPUT
    
    ['email@example.com', 'mail_me@example.com']
    To email me, try --@--.-- or the you can also try address --@--.--.
    
    

    Groups in regular expressions

    We can use groups for any general task that involves grouping together regular expressions (so that we can later break them down).

    Example: What if we wanted to do two tasks, find phone numbers, but also be able to quickly extract their area code (the first three digits).

    Method/Attribute Purpose
    group() Return the string matched by the RE
    start() Return the starting position of the match
    end() Return the ending position of the match
    span() Return a tuple containing the (start, end) positions of the match
    
    # Using group related functions in regular expressions
    
    txt = "My phone number is 717-123-9876"
    phone_pattern = re.compile(r'(\d{3})-(\d{3})-(\d{4})')
    results = re.search(phone_pattern,txt)
    
    print(results.group())
    print(results.start())
    print(results.end())
    print(results.span())
    print("----------------------")
    
    # Can then also call by group position.
    # remember groups were separated by parenthesis ()
    # Something to note is that group ordering starts at 1. Passing in 0 returns everything
    print(results.group(0))
    print(results.group(1))
    print(results.group(2))
    print(results.group(3))            
                
    
    OUTPUT
    
    717-123-9876
    19
    31
    (19, 31)
    ----------------------
    717-123-9876
    717
    123
    9876
    
    

    Standard Modules

  • Python comes with a library of standard modules.
  • Some modules are built into the interpreter; these provide access to operations that are not part of the core of the language but are nevertheless built in, either for efficiency or to provide access to operating system primitives such as system calls.
  • The set of such modules is a configuration option which also depends on the underlying platform. For example, the winreg module is only provided on Windows systems.
  • And there are some modules, and particularly one deserves some attention: sys which is built into every Python interpreter.
  • Let us look into some popular modules:

  • random Module
  • statistics Module
  • requests Module
  • math Module
  • sys Module
  • random Module

  • random module implements pseudo-random number generators for various distributions.
  • Almost all module functions depend on the basic function random(), which generates a random float uniformly in the semi-open range [0.0, 1.0).
  • Python uses the Mersenne Twister as the core generator. The Mersenne Twister is one of the most extensively tested random number generators in existence. It produces 53-bit precision floats and has a period of 2**19937-1.
  • random module has a set of methods:

       ➤ random() method returns a random float number between 0 and 1

    
    # returns a random float number between 0 and 1
    
    import random as rd
    print(rd.random())          
    
    
    OUTPUT
    
    0.16157881100117089
    
    

       ➤ randrange() method returns a random number between the given range.

    
    # returns random number between 10 & 99
    
    import random as rd
    print(rd.randrange(10, 99))          
    
    
    OUTPUT
    
    96
    
    

       ➤ shuffle() method takes a sequence like a list as an argument and reorganize the order of the items.

    This shuffle() method changes the original list, it does not return a new list.

    
    # reorganize the order of the items in a sequence
    
    import random as rd
    
    teams = ["Arsenal", "Barcelona", "PSG", "Bayern", "Juventus"]
    rd.shuffle(teams)
    print(teams)         
    
    
    OUTPUT
    
    ['Barcelona', 'Bayern', 'Juventus', 'Arsenal', 'PSG']
    
    

       ➤ choice() method returns a randomly selected element from the specified sequence.

    The sequence can be a string, a range, a list, a tuple or any other kind of sequence.

    
    # select element randomly from list
    
    import random as rd
    
    teams = ["Arsenal", "Barcelona", "PSG", "Bayern", "Juventus"]
    choiceSelected = rd.choice(teams)
    print(choiceSelected)
    
    
    OUTPUT
    
    Barcelona
    
    
    
    # select element randomly from tuple
    
    import random as rd
    
    org = ("Skillzam","Workzam")
    chSelect = rd.choice(org)
    print(chSelect)
      
    
    OUTPUT
    
    Skillzam
    
    
    
    # select element randomly from string
    
    import random as rd
    
    academy = "SKILLZAM"
    charChoice = rd.choice(academy)
    print(charChoice)
      
    
    OUTPUT
    
    Z
    
    
    
    # select element randomly from range()
    
    import random as rd
    
    givenRange = range(1,100)
    selectRange = rd.choice(givenRange)
    print(selectRange)
    
    
    OUTPUT
    
    76
    
    

    statistics Module

  • statistics module provides functions for calculating mathematical statistics of numeric (Real-valued) data.
  • The module is not intended to be a competitor to third-party libraries, hence it is aimed at the level of graphing and scientific calculators.
  • Unless explicitly noted, these functions support int, float, Decimal and Fraction.
  • If your input data consists of mixed types, you may be able to use map() to ensure a consistent result, for example: map(float, input_data).
  • The statistics module was added newly in Python 3.4. Some of methods of this module are listed below:

       ➤ mean() method returns the mean or the average of the given data.

    
    # returns the mean of given data
    
    import statistics as st
    
    tempCelcius  = [12, 33, 29, 11.5, 23, 30, 22]
    meanValue = st.mean(tempCelcius)
    print(meanValue)     
    
    
    OUTPUT
    
    22.928571428571427
    
    

       ➤ median() method returns the median or the middle value of the given data.

    
    # returns the median of given data
    
    import statistics as st
    
    tempCelcius  = [12, 33, 29, 11.5, 23, 30, 22]
    medianValue = st.median(tempCelcius)
    print(medianValue)      
    
    
    OUTPUT
    
    23
    
    

       ➤ mode() method returns the mode or the central tendency of the given numeric or nominal data.

    
    # returns the mode of given data
    
    import statistics as st
    
    tempCelcius  = [12, 33, 29, 11.5, 23, 30, 22]
    modeValue = st.mode(tempCelcius)
    print(modeValue)     
    
    
    OUTPUT
    
    12
    
    

       ➤ stdev() method returns the standard deviation from a sample of data.

    
    # returns the standard deviation of given data
    
    import statistics as st
    
    tempCelcius  = [12, 33, 29, 11.5, 23, 30, 22]
    stdevValue = st.stdev(tempCelcius)
    print(stdevValue)  
    
    
    OUTPUT
    
    8.555838997572415
    
    

       ➤ variance() method returns the variance from a sample of data.

    
    # returns the variance of given data
    
    import statistics as st
    
    tempCelcius  = [12, 33, 29, 11.5, 23, 30, 22]
    varianceValue = st.variance(tempCelcius)
    print(varianceValue)     
    
    
    OUTPUT
    
    73.20238095238095
    
    

    requests Module

  • Using Python, requests module allows you to send HTTP requests.
  • The HTTP request returns a Response Object with all the response data (content, encoding, status, etc).
  • requests.Response() Object contains the server's response to the HTTP request.
  • Install the Requests Module using pip (c:\> pip install requests) before using it.
  • Some of methods of this module are listed below:


       ➤  get() method sends a GET request to the specified url.

    
    # GET request to the specified url
    
    import requests as rq
    
    rqExample = rq.get('https://example.com/index.html')
    print(rqExample.text)     # returns content of the response 
    
    
    OUTPUT
    
    <!doctype html>
    <html>
    <head>
        <title>Example Domain</title>
    
        <meta charset="utf-8" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <style type="text/css">
        body {
            background-color: #f0f0f2;
            margin: 0;
            padding: 0;
            font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
            
        }
        div {
            width: 600px;
            margin: 5em auto;
            padding: 2em;
            background-color: #fdfdff;
            border-radius: 0.5em;
            box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
        }
        a:link, a:visited {
            color: #38488f;
            text-decoration: none;
        }
        @media (max-width: 700px) {
            div {
                margin: 0 auto;
                width: auto;
            }
        }
        </style>    
    </head>
      
      <body>
      <div>
          <h1>Example Domain</h1>
          <p>This domain is for use in illustrative examples in documents. You may use this
          domain in literature without prior coordination or asking for permission.</p>
          <p><a href="https://www.iana.org/domains/example">More information...</a></p>
      </div>
      </body>
      </html>
      
    
    
    
    # GET request to the specified url
    
    import requests as rq
    
    rqFitbit = rq.get('https://fitbit.com')
    print(rqFitbit.url)      # returns URL of the response 
    
    
    OUTPUT
    
    https://www.fitbit.com/global/in/home
    
    
    
    # GET request to the specified url
    
    import requests as rq
    
    rqExample = rq.get('https://example.com')
    print(rqExample.status_code)  # returns a number that indicates status 
    print(rqExample.reason)       # returns a text corresponding to status code
    
    
    OUTPUT
    
    200
    OK
    
    

       ➤ head() method sends a HEAD request to the specified url.

    HEAD requests are done when you do not need the content of the file, but only the status_code or HTTP headers.

    
    # HEAD request to the specified url
    
    import requests as rq
    
    rqYahoo = rq.head('https://yahoo.com')
    print(rqYahoo.headers)    # returns a dictionary of response headers
    
    
    OUTPUT
    
    {'Date': 'Tue, 10 Jan 2023 14:20:17 GMT', 'Connection': 'keep-alive', 'Strict-Transport-Security': 'max-age=31536000', 'Server': 'ATS', 'Cache-Control': 'no-store, no-cache', 'Content-Type': 'text/html', 'Content-Language': 'en', 'X-Frame-Options': 'SAMEORIGIN', 'Expect-CT': 'max-age=31536000, report-uri="http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only"', 'Referrer-Policy': 'no-referrer-when-downgrade', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block', 'Location': 'https://www.yahoo.com/', 'Content-Length': '8'}
    
    

    math Module

  • math module provides access to the mathematical functions defined by the C standard
  • These functions cannot be used with complex numbers. The math module has a set of methods and constants.
  • The following are provided by this module:
  •    ➤ math.floor() method rounds a number down to the nearest integer, if necessary, and returns the result.

    
    # rounds number down to nearest integer, if necessary
    
    import math as mt
    
    numOne = 0.75
    numTwo = 1.25
    
    print(mt.floor(numOne))    
    print(mt.floor(numTwo))           
    
    
    OUTPUT
    
    0
    1
    
    

       ➤ math.factorial() method returns the factorial of a number (positive integers).

    
    # returns the factorial of a number
    
    import math as mt
    
    num = 5
    print(mt.factorial(num))          
    
    
    OUTPUT
    
    120
    
    

       ➤ math.pow() method returns the value of x raised to power y. If x is negative and y is not an integer, it returns a ValueError. This method converts both arguments into a float.

    
    # returns value of x raised to power y
    
    import math as mt
    
    x = 9
    y = 2
    print(mt.pow(x,y))           
    
    
    OUTPUT
    
    81
    
    

       ➤ math.pi constant returns the value of PI: 3.141592653589793.

       ➤ math.e constant returns the Euler's number: 2.718281828459045.

    
    # returns the value of PI & e
    
    import math as mt
    
    print(mt.pi)           
    print(mt.e)           
    
    
    OUTPUT
    
    3.141592653589793
    2.718281828459045
    
    

    sys Module

  • sys module provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter.
  • It is always available.
  • This module has a set of attributes/methods:

       ➤ sys.ps1 and sys.ps2 are strings specifying the primary & secondary prompt of the interpreter. These are only defined, if the interpreter is in interactive mode. Their initial values in this case are '>>> ' and '... ' .

    
    >>> import sys
    >>> print(sys.ps1, sys.ps2)
    
    
    OUTPUT
    
    >>>  ...
    
    

       ➤ sys.path is a built-in variable within the sys module. It contains a list of directories that the interpreter will search in for the required module.

    When a module is imported within a Python file, the interpreter first searches for the specified module among its built-in modules. If not found it looks through the list of directories(a directory is a folder that contains related modules) defined by sys.path

    
    >>> import sys
    >>> sys.path     
    
    
    OUTPUT
    
    ['', 'D:\\install\\python\\python311.zip', 'D:\\install\\python\\Lib', 'D:\\install\\python\\DLLs', 'D:\\install\\python', 'D:\\install\\python\\Lib\\site-packages', 'D:\\install\\python\\Lib\\site-packages\\win32', 'D:\\install\\python\\Lib\\site-packages\\win32\\lib', 'D:\\install\\python\\Lib\\site-packages\\Pythonwin']
    
    

       ➤ sys.stdout a built-in file object that is analogous to the interpreter's standard output stream in Python.

  • stdout is used to display output directly to the screen console.
  • Output can be of any form, it can be output from a print statement, an expression statement, and even a prompt direct for input. By default, streams are in text mode.
  • In fact, wherever a print function is called within the code, it is first written to sys.stdout and then finally on to the screen.
  • sys.stdout.write() serves the same purpose as the object stands for except it prints the number of letters within the text too when used in interactive mode.
  • Unlike print, sys.stdout.write doesn't switch to a new line after one text is displayed. To achieve this one can employ a new line escape character \n.
  • 
    # sys.stdout.write() usuage
    
    import sys
    
    # stdout assigned to a variable (output)
    output = sys.stdout
    mylist = ['Skillzam', 'Learn', 'without', 'limits!']
      
    # printing everything in the same line
    for word in mylist:
        output.write(word)
    
    # printing everything in a new line
    for string in mylist:
        output.write('\n'+string)
    
    
    OUTPUT
    
    SkillzamLearnwithoutlimits!
    Skillzam
    Learn
    without
    limits!
    
    

       ➤ sys.stdin stands for standard input which is a stream from which the program reads its input data from the command line directly.

  • It internally calls the input() method. Furthermore, it, also, automatically adds '\n' after each sentence.
  • sys.stdin.readline() method is slightly different from the input() method as it also reads the escape character entered by the user.
  • More, this method also provides the parameter for the size i.e. how many characters it can read at a time.
  • 
    >>> import sys
    >>> fullName = sys.stdin.readline()
    John Smith
    >>> age = sys.stdin.readline(2)
    025
    >>> print(fullName)
    John Smith
    
    >>> print(age)
    02
    >>>
    
    

    Python Interview Questions

    Get the hold of actual interview questions during job hiring.

    What is Python?

    Python is a high-level, interpreted programming language. It emphasizes code readability and simplicity, making it a popular choice for beginners and experienced developers alike.

    What is the difference between a tuple and a list in Python?

    A list and a tuple are both ordered collections of elements in Python, but there are several key differences between the two. The main difference is that a list is mutable, which means that you can add, remove, or modify elements in a list, while a tuple is immutable, which means that you cannot modify its elements after it has been created. Additionally, a list is defined using square brackets, while a tuple is defined using parentheses.

    What is a lambda function in Python, and when would you use one?

    A lambda function is a small anonymous function that can take any number of arguments, but can only have one expression. Lambda functions are useful when you need to create a simple function for a specific purpose, and you don't want to define a named function. They can be used in place of a regular function wherever a function is expected, such as in the "key" parameter of the "sort" method.

    What are decorators in Python, and how do they work?

    A decorator is a special type of function that can be used to modify the behavior of another function. Decorators are implemented as functions that take another function as input and return a new function that wraps the original function. The new function can modify the behavior of the original function, such as adding functionality, logging, or error handling. Decorators are applied using the "@" symbol followed by the name of the decorator function before the function definition.

    What is the difference between a module and a package in Python?

    In Python, a module is a single file that contains Python code, while a package is a collection of modules in a directory. Packages allow for a hierarchical organization of modules, and they can be nested to any level.

    What is a generator in Python, and how does it work?

    A generator is a special type of iterable that allows you to generate a sequence of values on-the-fly. Generators are implemented as functions that use the "yield" keyword instead of "return" to return values. When a generator function is called, it returns a generator object that can be iterated over using a for loop or other iterable functions. The generator function can pause and resume execution, so it can generate an infinite sequence of values without consuming all of the memory at once.

    What is PEP 8 in Python?

    PEP 8 is a set of guidelines for writing Python code that are meant to promote consistency and readability. The guidelines cover topics such as code layout, naming conventions, and programming practices. Adhering to PEP 8 can make your code more readable and easier to maintain.

    What is the difference between "is" and "==" in Python?

    In Python, "is" is used to test if two variables refer to the same object in memory, while "==" is used to test if two variables have the same value. For example, "x is y" would return True if x and y refer to the same object in memory, while "x == y" would return True if x and y have the same value, regardless of whether they are the same object.

    What is the purpose of "init" in Python?

    The init() method is a special method in Python classes that is used to initialize the object's attributes when it is created. The method is called automatically when a new object of the class is created, and it can take parameters to set the initial values of the object's attributes. The init() method is commonly used to define the state of the object when it is created, such as setting default values or initializing variables.

    What is a dictionary in Python?

    A dictionary is an unordered collection of key-value pairs, where each key is unique.

    What is a module in Python?

    A module is a file containing Python code that can be reused in other Python programs.

    WhWhat is the difference between a module and a package in Python?

    A module is a single file that contains Python code, while a package is a directory that contains one or more modules and an optional init.py file. The init.py file can contain initialization code that is executed when the package is imported. Packages are used to organize related modules into a hierarchical namespace.

    What is the difference between local and global variables in Python?

    Local variables are defined within a function and are only accessible within that function, while global variables are defined outside of any function and can be accessed anywhere in the program.

    How can you debug a Python program?

    You can use the Python debugger (pdb) module, which allows you to step through your code line-by-line and inspect variables.

    What is the use of the "pass" statement in Python?

    The "pass" statement is a placeholder statement that does nothing. It is often used as a placeholder for code that will be written later.

    What is the use of the "yield" keyword in Python?

    The "yield" keyword is used in Python to define a generator function, which is a special kind of function that generates a sequence of values on the fly. When a generator function is called, it returns a generator object, which can be used to iterate over the sequence of values generated by the function. The "yield" keyword is used to return a value from the generator function, but instead of terminating the function like a "return" statement would, it temporarily suspends the function and saves its state, allowing it to be resumed later. The purpose of the "yield" keyword is to allow the generator function to generate a sequence of values without having to create a list or other data structure to hold all the values in memory at once.

    How do you handle exceptions in Python?

    You can use the "try-except" block to catch and handle exceptions in Python.

    What is the purpose of the "finally" block in a "try-except" block?

    The "finally" block is executed regardless of whether an exception was raised or not. It is often used to release resources or perform cleanup tasks.

    What is the purpose of the "super()" function in Python?

    The "super" function in Python is used to call a method in a parent class from a subclass. When a method is called using "super", Python will search for the method in the parent class and call it with the arguments passed to the subclass method. This allows the subclass to extend or modify the behavior of the parent class, without having to duplicate the entire method definition.

    How do you create an empty list in Python?

    You can create an empty list in Python using empty square brackets, like this: my_list = []

    What is a tuple in Python?

    A tuple is a data structure in Python that can hold a collection of values, which can be of different data types. Tuples are immutable, meaning their values cannot be changed once they are created.

    How do you create an empty dictionary in Python?

    You can create an empty dictionary in Python using empty curly braces, like this: my_dict = {}

    What is the difference between a dictionary and a list in Python?

    The main difference between a dictionary and a list in Python is that lists hold an ordered collection of values, while dictionaries hold key-value pairs, and the keys are used to access the corresponding values.

    What is a function in Python?

    A function in Python is a block of code that performs a specific task and can be reused throughout the program.

    How do you define a function in Python?

    You can define a function in Python using the def keyword, like this:

    
    def my_function(parameter1, parameter2):
        # Code to perform the task
        return result
                

    What is pip in Python?

    Pip is a package manager for Python that is used to install, upgrade, and remove Python packages.

    What is virtualenv in Python?

    Virtualenv is a tool in Python that allows you to create isolated Python environments, which can have their own set of packages and dependencies.

    What is object-oriented programming in Python?

    Object-oriented programming (OOP) is a programming paradigm that uses objects to represent data and methods to perform actions on that data.

    What is inheritance in Python?

    Inheritance is a feature of object-oriented programming in Python that allows a new class to be based on an existing class, inheriting its attributes and methods.

    What is polymorphism in Python?

    Polymorphism is a fundamental concept in object-oriented programming that allows objects of different types to be treated as if they are of the same type. In Python, polymorphism is achieved through the use of inheritance, duck typing, and function overloading.

    What is the difference between a shallow copy and a deep copy in Python?

    In Python, when we copy an object, we can create either a shallow copy or a deep copy. The difference between the two is in how they handle mutable objects that are nested inside the object being copied.

    A shallow copy creates a new object that points to the same memory location as the original object. In other words, the new object is a reference to the original object. This means that any changes made to the original object will also affect the shallow copy, and vice versa. Shallow copying is useful when we want to create a new object that contains references to the same objects as the original object.

    On the other hand, a deep copy creates a new object with its own memory allocation. It recursively copies all the nested objects in the original object, and creates new objects for them. This means that any changes made to the original object will not affect the deep copy, and vice versa. Deep copying is useful when we want to create a completely independent copy of the original object, without any references to its nested objects.

    How does Python's garbage collection work, and what are some common issues with it?

    Python's garbage collection automatically frees up memory that is no longer being used by an application. It uses a reference counting system to keep track of the number of references to an object. When the reference count of an object drops to zero, it is removed from memory by the garbage collector. Additionally, Python's garbage collector also uses a cyclic garbage collector to detect and remove circular references. Common issues with Python's garbage collection include memory leaks caused by circular references or long-lived objects, and performance issues caused by frequent garbage collection cycles.

    How does Python implement multithreading?

    Python uses a Global Interpreter Lock (GIL) to ensure that only one thread executes Python bytecode at a time. This means that only one thread can execute Python code at any given time, even on multi-core systems. However, Python also provides a multiprocessing module, which allows multiple processes to execute Python code in parallel.

    What is the difference between Python 2 and Python 3, and how would you migrate code from Python 2 to Python 3?

    Python 2 and Python 3 are two different versions of the Python programming language, with significant differences in syntax and features. Python 3 introduced several changes to the language, such as the print function, integer division, and Unicode string handling. To migrate code from Python 2 to Python 3, you would need to modify the code to use Python 3 syntax and features, such as using the print function instead of the print statement, using integer division instead of floor division, and handling Unicode strings differently. You can use tools like 2to3 or Modernize to automate some of the migration process.

    What is a closure in Python, and how would you use one?

    A closure is a function that has access to a parent function's variables, even after the parent function has completed execution. Closures are implemented using nested functions, where the inner function can reference the variables of the outer function. Closures can be used to implement higher-order functions, such as decorators and generators, or to create private variables that are not accessible outside of the closure.

    What is the Global Interpreter Lock (GIL) in Python, and how does it affect multi-threaded programming in Python?

    The Global Interpreter Lock (GIL) is a mechanism used in the CPython implementation of Python that allows only one thread to execute Python bytecode at a time. This means that even if an application uses multiple threads, only one thread can execute Python code at a time, while other threads are waiting for the GIL to be released. This can limit the performance of multi-threaded applications that spend a lot of time executing Python code. To work around the GIL, you can use multiple processes instead of threads, or use a different implementation of Python, such as Jython or IronPython, that do not have a GIL.

    What is the difference between a class method and a static method in Python?

    A class method is a method that is bound to the class rather than an instance of the class. Class methods are defined using the "@classmethod" decorator, and they take the class itself as the first argument instead of an instance of the class. Class methods can be used to create alternative constructors for a class or to access class-level variables. A static method is a method that does not require access to the class or instance, and is defined using the "@staticmethod" decorator. Static methods can be used as utility functions that do not depend on the state of the class or instance.

    What is the purpose of memoization in Python?

    Memoization is a technique used to improve the performance of functions by caching the results of expensive computations and returning the cached results when the same inputs are provided again. Memoization is typically used with recursive functions or functions that involve expensive computations.

    What is the difference between a generator and a list comprehension in Python?

    A list comprehension is a concise way to create a new list by applying an expression to each element of an existing list or iterable. A generator, on the other hand, is a way to lazily generate a sequence of values on the fly, using a special kind of function called a generator function. The key difference between the two is that a list comprehension creates a new list in memory, while a generator generates values on the fly and does not create a new list in memory. Generators are useful when you need to generate a large sequence of values but don't want to create a new list in memory.

    What is the difference between an abstract class and an interface in Python?

    In Python, there is no formal concept of interfaces, but there are abstract classes, which are similar in concept. An abstract class is a class that cannot be instantiated directly, and is instead meant to be subclassed by other classes. Abstract classes can have abstract methods, which are methods that are meant to be implemented by the subclasses. The purpose of an abstract class is to provide a blueprint or template for the behavior of the subclasses. An interface, on the other hand, is a way to define a contract between different classes, specifying the methods and properties that the classes must implement. Interfaces are typically used in languages like Java and C#, but are not as common in Python.

    What is the difference between a local and a global variable in Python?

    A local variable is a variable that is defined inside a function or a method, and is only accessible within that function or method. Local variables have local scope, meaning that they cannot be accessed from outside the function or method in which they are defined. A global variable, on the other hand, is a variable that is defined outside any function or method, and can be accessed from anywhere in the program. Global variables have global scope, meaning that they can be accessed from any part of the program.

    What is the difference between a thread and a process in Python?

    A process is a separate instance of a running program, while a thread is a separate execution path within a process. This means that a process can have multiple threads, each of which can execute code independently and concurrently with the other threads. Processes have their own memory space and resources, while threads share the same memory space and resources within the process. Threads are often used for tasks that need to be run concurrently, such as downloading multiple files at the same time or performing background processing while the main program is running.

    What is the purpose of the "with" statement in Python?

    The "with" statement is used in Python to define a context in which a particular resource is used, such as a file, a database connection, or a network socket. The purpose of the "with" statement is to ensure that the resource is properly acquired and released, even in the face of errors or exceptions. When the "with" statement is executed, it creates a context and sets up the resource for use. When the context is exited, either normally or due to an exception, the resource is automatically released, regardless of whether an error occurred or not. This helps to ensure that resources are properly managed and that the program does not leak resources or leave them in an inconsistent state.

    What is the difference between a decorator and a closure in Python?

    A decorator is a special kind of function that can be used to modify the behavior of another function or method, while a closure is a function that can access and modify the variables in the enclosing scope. The key difference between the two is that a decorator is a higher-order function that takes a function as input and returns a new function, while a closure is a function that has access to variables defined in its enclosing scope. Decorators are often used for tasks like logging, timing, or caching, while closures are often used for tasks like event handling or callback functions.

    What is the difference between a lambda function and a regular function in Python?

    A lambda function is a small, anonymous function that can be defined in a single line of code, while a regular function is a named function that can have multiple lines of code and is defined using the "def" keyword. Lambda functions are often used for tasks that require a small function to be defined on-the-fly, such as sorting or filtering data. Regular functions are used for more complex tasks that require multiple lines of code, or for tasks that need to be called multiple times.

    How does Python implement inheritance, and what are the benefits of inheritance in object-oriented programming?

    In Python, inheritance is implemented by defining a subclass that inherits attributes and methods from its parent class. The subclass can then extend or modify the behavior of the parent class by adding or overriding methods. When a method is called on an instance of the subclass, Python will first look for the method in the subclass, and if it is not found, it will look in the parent class.

    The benefits of inheritance in object-oriented programming are that it allows for code reuse and promotes code organization and modularity. By defining a common set of attributes and methods in a parent class, subclasses can inherit this behavior and build on it to create new functionality. Inheritance also allows for polymorphism, which means that objects of different classes can be treated as if they are the same type, as long as they have the same interface.