Python & Data Science

Chapter 2
Python Language Essentials

Advance Diploma in Artificial Intelligence and Machine Learning
Python & Data Science   •   Advance Diploma

Python Language Essentials

Introduction and Context

Python is a high-level, interpreted programming language widely used in data science, artificial intelligence, automation, web development, and scientific computing. Its importance in artificial intelligence and machine learning arises from readable syntax, a large standard library, mature numerical tools, and an extensive ecosystem that includes NumPy, pandas, scikit-learn, PyTorch, and TensorFlow.

Before sophisticated models can be trained, a practitioner must reliably express data, transformations, decisions, repetition, and reusable procedures. This chapter develops those foundations. The emphasis is not merely on memorising syntax, but on understanding how Python represents information and executes computational instructions.

Learning Objectives

By the end of this chapter, you should be able to:

  • declare and use variables;
  • distinguish scalar and collection data types;
  • apply operators and construct valid expressions;
  • read input and produce formatted output;
  • implement conditional logic and loops;
  • use comprehensions appropriately;
  • define functions and explain scope;
  • import modules and use standard-library utilities;
  • select suitable Python structures for practical data-processing tasks;
  • identify common errors, limitations, and professional coding practices.

1. Python's Execution Model

Python programs consist of statements and expressions. An expression produces a value, whereas a statement performs an action such as assigning a value, importing a module, or controlling execution. Python code is commonly executed by the Python interpreter. In a typical implementation, source code is parsed and compiled into bytecode, which is then executed by a virtual machine.

Python uses indentation to represent blocks. Indentation is therefore part of the program's structure, not merely a formatting preference.

temperature = 27
if temperature > 25:
    print("Warm day")

The print statement belongs to the if block because it is indented. Mixing tabs and spaces can produce errors; four spaces per indentation level is the widely accepted convention.

Python is dynamically typed: a variable name refers to an object, and the object has a type. The type does not need to be declared in advance.

value = 12
value = "twelve"

This is legal, although repeatedly changing the meaning of a variable can reduce clarity. Dynamic typing does not mean that Python has no types; it means that type checking occurs during execution rather than through mandatory declarations before execution.

2. Variables, Names, and Values

An assignment binds a name to an object:

score = 84
name = "Amina"

A name may contain letters, digits, and underscores, but it cannot begin with a digit and should not be a Python keyword. Names are case-sensitive: total, Total, and TOTAL are different names.

Good names communicate purpose. Prefer monthly_revenue to x, unless x has a clear mathematical role in a short calculation. Constants are conventionally written in uppercase:

TAX_RATE = 0.15

Assignment is not mathematical equality. In x = x + 1, the old value of x is used to calculate a new value, which is then assigned to x.

Python supports multiple assignment and unpacking:

a, b = 10, 20
a, b = b, a

The second statement exchanges the values without requiring a temporary variable. Unpacking is useful when a function returns several values or when iterating over pairs.

3. Scalar Data Types

A scalar represents one logical value. Common built-in scalar types are shown below.

TypeExampleTypical use
int42Whole-number counts and indices
float3.14Approximate real-number calculations
complex2 + 3jComplex-number computation
boolTrueLogical conditions
str"Python"Text
NoneTypeNoneAbsence of a value

Integers have arbitrary precision in standard Python, subject to available memory. Floating-point values use finite binary representation and may not represent every decimal number exactly.

print(0.1 + 0.2)   # commonly displays 0.30000000000000004

For comparisons involving measured or calculated floating-point values, use a tolerance when exact equality is inappropriate. The math.isclose function is often preferable to manually comparing decimal displays.

Boolean values are True and False. In Boolean contexts, some values are treated as false: False, None, numeric zero, and empty collections. Most other values are true.

Strings are immutable sequences of Unicode characters.

message = "Artificial Intelligence"
print(message[0])       # A
print(message[0:10])    # Artificial
print(message.lower())

A slice s[start:stop:step] includes start but excludes stop. Negative indices count from the end. Because strings are immutable, an operation creates a new string rather than altering the original.

Formatted string literals, or f-strings, make output readable:

student = "Lina"
mark = 87.456
print(f"{student} achieved {mark:.1f}%")

The format specification .1f displays one digit after the decimal point.

4. Collection Data Types

Collections store multiple values. The choice of collection affects ordering, mutability, uniqueness, and lookup behaviour.

Lists

A list is ordered and mutable. It can contain values of different types, although homogeneous lists are usually easier to process.

scores = [72, 81, 94]
scores.append(88)
scores[1] = 83

Lists support indexing and slicing. Methods such as append, extend, insert, remove, pop, and sort modify a list. sorted(values) instead returns a new sorted list and leaves the original unchanged.

Tuples

A tuple is an ordered, immutable collection.

coordinates = (12.5, 8.0)

Tuples are appropriate for fixed records, function returns, and values that should not be modified accidentally.

Sets

A set stores unique, unordered elements. Its elements must be hashable.

features = {"age", "income", "age"}
print(features)  # age occurs once

Sets support union (|), intersection (&), difference (-), and symmetric difference (^). They are useful for membership testing and removing duplicates, but they should not be used when positional order is significant.

Dictionaries

A dictionary maps hashable keys to values.

customer = {
    "id": 104,
    "name": "Ravi",
    "active": True
}
print(customer["name"])

Use get when a key may be absent:

email = customer.get("email", "not supplied")

Dictionaries preserve insertion order in modern Python implementations, but a dictionary should conceptually be selected for key-based association rather than positional access.

Mutability and copying

Lists, dictionaries, and sets are mutable; strings and tuples are immutable. Assignment does not necessarily copy an object:

first = [1, 2]
second = first
second.append(3)
# first is now [1, 2, 3]

To create an independent shallow copy, use first.copy() or list(first). Nested structures may require copy.deepcopy.

5. Operators and Expressions

Python provides arithmetic, comparison, logical, identity, membership, and bitwise operators.

CategoryOperatorsExample
Arithmetic+ - / // % *7 // 2 gives 3
Comparison== != < <= > >=age >= 18
Logicaland or notvalid and complete
Membershipin, not in"AI" in title
Identityis, is notresult is None

/ produces true division, while // produces floor division. The remainder operator is %, and exponentiation is **.

Operator precedence determines grouping, but parentheses improve readability and prevent mistakes. Python evaluates and and or using short-circuit logic, so the second operand may not be evaluated.

Use == to compare values and is to test object identity. The idiomatic test for absence is:

if result is None:
    print("No result")

Expressions can be assigned to variables, passed to functions, or used in conditions. A complex expression should be decomposed when this improves testing and comprehension.

6. Input, Output, and Conversion

input() reads a line of text and always returns a string. Conversion is therefore necessary for numeric input.

radius = float(input("Radius in metres: "))
area = 3.141592653589793 * radius ** 2
print(f"Area: {area:.2f} m²")

The formula used is the area of a circle, where radius is measured in metres and the result is in square metres. It assumes a Euclidean circle and a non-negative radius.

Invalid input raises ValueError. Robust programs validate input or handle exceptions:

try:
    age = int(input("Age: "))
except ValueError:
    print("Please enter a whole number.")

Do not use eval(input()) for ordinary input processing. It can execute arbitrary Python code and creates a serious security risk.

7. Conditional Logic

Conditional execution uses if, elif, and else.

if score >= 70:
    grade = "Distinction"
elif score >= 50:
    grade = "Pass"
else:
    grade = "Fail"

Conditions should be mutually intelligible and ordered carefully. Boundary conditions matter: the example assigns a score of 70 to the first branch and a score of 50 to the second.

Nested conditions are sometimes necessary, but combining them with clear Boolean expressions or using early returns often improves readability.

8. Iteration and Loops

A for loop iterates over items in an iterable.

total = 0
for value in [4, 7, 9]:
    total += value

range(start, stop, step) generates an arithmetic progression of integers, with stop excluded. Thus, range(1, 5) produces 1, 2, 3, and 4.

Use enumerate when both index and value are required, and zip when traversing related sequences:

names = ["A", "B", "C"]
marks = [72, 81, 66]
for position, (name, mark) in enumerate(zip(names, marks), start=1):
    print(position, name, mark)

A while loop repeats while a condition remains true. Its state must eventually change, or the program may enter an infinite loop.

break exits a loop; continue skips to the next iteration. These controls are useful but should not conceal complicated logic.

9. Comprehensions

Comprehensions provide compact syntax for constructing collections.

squares = [n ** 2 for n in range(1, 6)]
even_squares = [n ** 2 for n in range(1, 10) if n % 2 == 0]

Equivalent dictionary and set comprehensions are:

lookup = {n: n ** 2 for n in range(5)}
unique_lengths = {len(word) for word in ["AI", "data", "AI"]}

A comprehension is appropriate when its logic remains readable. Deeply nested comprehensions should generally be replaced with named loops or functions. Generator expressions use parentheses and produce values lazily, reducing memory use:

total = sum(n * n for n in range(1_000_000))

10. Functions and Modular Design

A function packages a task behind a name and interface.

def mean(values):
    if not values:
        raise ValueError("values must not be empty")
    return sum(values) / len(values)

Parameters receive inputs; return sends a result to the caller. A function should generally perform one coherent responsibility. A docstring documents its contract:

def discount(price, rate=0.10):
    """Return price after applying a fractional discount rate."""
    return price * (1 - rate)

Default arguments are evaluated when the function is defined. Mutable defaults should therefore be avoided:

# Avoid: def collect(item, items=[]): ...
def collect(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Arguments may be passed positionally or by keyword. Type hints improve documentation and tool support, although they do not enforce types at runtime by themselves.

def percentage(part: float, whole: float) -> float:
    if whole == 0:
        raise ValueError("whole must not be zero")
    return 100 * part / whole

11. Scope and Namespaces

Scope determines where a name can be accessed. Python commonly follows the LEGB lookup rule: Local, Enclosing, Global, Built-in.

rate = 0.2

def tax(amount):
    return amount * rate

Here, rate is found in the global scope. Excessive reliance on global variables makes programs difficult to test and maintain; passing values as parameters is usually better.

A local assignment creates a local name unless global or nonlocal is explicitly used. These declarations have legitimate specialised uses but should be employed sparingly. Variables defined inside a function normally disappear from direct access after the function returns, although returned objects remain available through references.

12. Modules and the Standard Library

A module is a Python file containing reusable definitions. Importing promotes modularity and avoids rewriting tested code.

import math
from statistics import mean, median

print(math.sqrt(49))
print(median([3, 8, 10]))

Useful standard-library modules include:

ModuleTypical purpose
mathMathematical functions and constants
statisticsBasic descriptive statistics
randomPseudorandom sampling
datetimeDates and times
pathlibPlatform-independent file paths
jsonJSON encoding and decoding
collectionsSpecialised containers such as Counter
itertoolsEfficient iterator operations
reRegular expressions

The random module is not suitable for passwords, tokens, or security-sensitive applications; use the secrets module for those purposes. File paths should preferably be built with pathlib rather than manually concatenating platform-specific strings.

A module can protect executable demonstration code with:

if __name__ == "__main__":
    print(mean([2, 4, 6]))

This code runs when the file is executed directly, but not when it is imported.

13. Errors, Testing, and Professional Practice

Errors may be syntax errors, runtime exceptions, or logical errors. Exceptions should be handled at an appropriate boundary, not indiscriminately suppressed.

try:
    result = numerator / denominator
except ZeroDivisionError:
    result = None

Avoid broad except Exception unless there is a documented reason and the error is logged or re-raised. Assertions are useful for programmer assumptions, but they should not replace validation of untrusted user input.

Professional Python practice includes meaningful names, small functions, consistent formatting, docstrings for public interfaces, version control, automated tests, and dependency management. PEP 8 provides widely adopted style guidance. Unit tests should check normal cases, boundary cases, invalid inputs, and expected exceptions.

Applied Case: Cleaning Sensor Readings

Background

Consider an illustrative industrial-monitoring application that receives temperature readings from several sensors. A reading may arrive as text, may be missing, or may fall outside the physically meaningful operating range of −40 °C to 125 °C.

Problem

The operations team needs the valid readings, their average, and the number of rejected values. The solution should be transparent, reusable, and safe when no valid readings exist.

Approach and solution

from statistics import mean


def clean_temperatures(raw_readings, lower=-40.0, upper=125.0):
    valid = []
    rejected = 0

    for raw in raw_readings:
        try:
            value = float(raw)
        except (TypeError, ValueError):
            rejected += 1
            continue

        if lower <= value <= upper:
            valid.append(value)
        else:
            rejected += 1

    average = mean(valid) if valid else None
    return valid, rejected, average

readings = ["21.5", 22, None, "error", 130, -41, 20.0]
valid, rejected, average = clean_temperatures(readings)
print(valid)
print(rejected)
print(average)

The valid values are 21.5, 22.0, and 20.0; five readings are rejected, and the average is approximately 21.17 °C. The function separates conversion, validation, aggregation, and reporting sufficiently to support testing.

Outcome and learning points

This case demonstrates strings and numeric conversion, lists, loops, exception handling, comparisons, function parameters, conditional expressions, and a standard-library module. In a production system, validation limits would need confirmation from engineering specifications, raw data would be retained for auditability, and anomalous readings would not automatically be deleted without investigating sensor failure or data corruption.

Limitations, Ethics, and Contemporary Practice

Python's flexibility can produce inconsistent styles, late-discovered type errors, and performance limitations for heavily iterative numerical workloads. Vectorised libraries or compiled extensions may be preferable for large arrays. Memory use also matters: a list materialises all elements, whereas an iterator can process data incrementally.

Data-processing programs must protect personal and commercially sensitive information. Input validation, access control, secure file handling, dependency updates, and careful logging are essential. Logs should not expose passwords, authentication tokens, or unnecessary personal data. In AI projects, apparently simple preprocessing decisions can influence model bias: dropping invalid-looking records may disproportionately remove certain populations or environments. Data quality rules should therefore be documented, justified, and evaluated for unintended effects.

Contemporary Python development increasingly uses type checkers, formatters, linters, notebooks, packaging tools, reproducible environments, and automated continuous integration. These tools complement—not replace—conceptual understanding. A reliable data scientist must be able to reason about data representation, execution order, exceptions, and computational cost.

Chapter Summary

Python programs combine expressions, statements, names, and objects. Variables bind names to values, while scalar types represent individual values and collections represent groups of values. Lists are mutable sequences, tuples are immutable sequences, sets store unique elements, and dictionaries associate keys with values. Operators construct expressions for arithmetic, comparison, logic, membership, and identity.

Input arrives as text and often requires explicit conversion. Conditional statements express alternatives, loops express repetition, and comprehensions construct collections concisely. Functions provide reusable interfaces, while scope determines name visibility according to the LEGB rule. Modules and the standard library support modular, maintainable solutions. Exception handling, testing, validation, secure practices, and appropriate data structures are essential for professional work in data science and AI.

Key Terms

Variable: A name bound to an object.

Object: A runtime entity containing a value and a type.

Scalar: A single logical value.

Mutable: Capable of being changed after creation.

Immutable: Not changeable after creation.

List: An ordered, mutable collection.

Tuple: An ordered, immutable collection.

Set: A collection of unique hashable elements.

Dictionary: A key–value mapping.

Expression: Code that evaluates to a value.

Statement: An instruction that performs an action.

Iterable: An object that can provide values sequentially.

Comprehension: Compact syntax for constructing a collection.

Function: A named, reusable block of code.

Parameter: A variable specified in a function definition.

Scope: The region in which a name can be accessed.

Module: A Python file containing reusable code.

Exception: An event signalling an execution error or unusual condition.

Short-circuit evaluation: Stopping Boolean evaluation when the result is already determined.

Type hint: Optional annotation describing an expected type.

Review Questions

Short Answer Questions

  1. What is the difference between a mutable and an immutable object? Give one example of each.
  2. Why does input() commonly need conversion before numerical calculations?
  3. Distinguish == from is in Python.
  4. What is the purpose of the return statement in a function?
  5. State the LEGB order used in Python name lookup.

Long Answer Questions

  1. Compare lists, tuples, sets, and dictionaries in terms of ordering, mutability, uniqueness, and typical applications.
  2. Explain conditional statements, for loops, while loops, break, and continue, using suitable examples.
  3. Explain function design, default arguments, type hints, docstrings, and the danger of mutable default arguments.
  4. Describe Python exception handling and discuss why broad exception-catching can be harmful.
  5. Explain how modules and standard-library packages contribute to maintainability and reuse in data-science programs.

Analytical / Application Questions

  1. Design a function that receives a sequence of marks, rejects values outside 0–100, and returns the valid mean and rejection count. State how your design handles an empty valid set.
  2. A program aliases one list to another and later changes the second list unexpectedly. Explain the cause and propose two ways to avoid it.
  3. A sensor-processing system removes all readings marked as invalid. Analyse two technical and two ethical risks of this approach, and propose safeguards.

Multiple Choice Questions with Answers

Question 1

Which Python collection is most appropriate for storing unique feature names and testing membership?

A. Set

B. List

C. Tuple

D. Float

Correct Answer: A. Set

Question 2

What does input() return before explicit conversion?

A. An integer

B. A string

C. A Boolean

D. A list

Correct Answer: B. A string

Question 3

Which operator tests whether two names refer to the same object?

A. ==

B. =

C. is

D. in

Correct Answer: C. is

Question 4

What values are produced by range(1, 5)?

A. 1, 2, 3, 4

B. 1, 2, 3, 4, 5

C. 0, 1, 2, 3, 4

D. 2, 3, 4, 5

Correct Answer: A. 1, 2, 3, 4

Question 5

Which statement about a dictionary is correct?

A. It stores only duplicate values

B. It maps keys to values

C. Its keys may be mutable lists

D. It is used only for numerical data

Correct Answer: B. It maps keys to values

Question 6

What is the result of 7 // 2?

A. 2.5

B. 3

C. 3.5

D. 4

Correct Answer: B. 3

Question 7

Which construct creates a list of squares from 0 through 4?

A. [n ** 2 for n in range(5)]

B. [n ^ 2 in range(5)]

C. list(n ** 2 if range(5))

D. for n ** 2 in range(5)

Correct Answer: A. [n 2 for n in range(5)]**

Question 8

What is the main purpose of a function's return statement?

A. To import a module

B. To stop all Python programs

C. To send a result back to the caller

D. To create a global variable

Correct Answer: C. To send a result back to the caller

Question 9

Which module provides the mean and median functions described in the chapter?

A. statistics

B. random

C. json

D. pathlib

Correct Answer: A. statistics

Question 10

Why is eval(input()) unsafe for ordinary user input?

A. It always converts input to a float

B. It cannot read spaces

C. It may execute arbitrary Python code

D. It prevents all exceptions

Correct Answer: C. It may execute arbitrary Python code

Python & Data Science
Chapter 2 of 15