Python for Data Science

Chapter 2
Python Syntax, Variables and Control Flow

Advance Diploma in Data Science
Python for Data Science   •   Advance Diploma

Python Syntax, Variables and Control Flow

Introduction

Python programs are built by combining expressions, statements, data values, and control structures. For data science, these foundations are especially important because data-processing programs must be readable, reproducible, and resistant to invalid input. A program that produces the correct result for one dataset but fails when a value is missing, malformed, or outside an expected range is not reliable.

This chapter develops the syntax and execution concepts required for practical Python programming. It covers names and variables, built-in data types, operators, expressions, input and output, conditional logic, iteration, exceptions, and Pythonic conventions. The emphasis is not merely on memorising syntax, but on understanding how Python evaluates instructions and how programmers design clear, testable solutions.

Learning Objectives

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

  • explain Python’s indentation, statement, and naming rules;
  • select suitable built-in data types for common data-science tasks;
  • use operators and expressions correctly, including comparisons and Boolean logic;
  • obtain input and produce formatted output safely;
  • implement decisions with if, elif, and else;
  • implement repetition with for and while loops;
  • control loops using break, continue, and pass;
  • distinguish syntax, runtime, and logical errors;
  • handle expected failures with appropriate exceptions;
  • write readable and idiomatic Python programs.

1. Python’s Execution Model and Syntax

Python is an interpreted, dynamically typed, general-purpose language. In normal execution, Python reads source code, parses it, compiles it to bytecode, and executes that bytecode using the Python interpreter. The implementation most commonly used is CPython, although other implementations exist.

A Python program consists of statements. An assignment statement binds a name to an object:

temperature = 22.5
city = "Nairobi"

The name temperature does not contain a permanently fixed type. It refers to a float object at this point and could later be rebound to another object. The object itself has a type, and operations are determined by that type.

value = 10
value = "ten"

This is legal, although repeatedly changing the meaning of a name can reduce readability. In professional code, a name should normally retain a consistent conceptual meaning.

Indentation and blocks

Python uses indentation to define blocks rather than braces. A block follows a statement ending in a colon, such as an if, loop, function, or class definition.

score = 74

if score >= 50:
    result = "pass"
    print(result)
else:
    result = "fail"
    print(result)

All statements in the same block must have consistent indentation. Four spaces are the conventional choice. Mixing tabs and spaces can cause IndentationError or make code difficult to maintain.

Comments and documentation

A comment begins with # and is ignored by the interpreter.

# Convert metres to kilometres
kilometres = metres / 1000

Comments should explain purpose, assumptions, or non-obvious decisions. They should not merely restate obvious syntax. Documentation strings, or docstrings, describe modules, functions, and classes.

def celsius_to_fahrenheit(celsius):
    """Return a Celsius temperature converted to Fahrenheit."""
    return celsius * 9 / 5 + 32

Naming conventions

Names may contain letters, digits, and underscores, but cannot begin with a digit. Python is case-sensitive: total, Total, and TOTAL are different names. Use snake_case for variables and functions, PascalCase for classes, and uppercase names for constants by convention.

Avoid shadowing built-in names:

# Poor practice
list = [1, 2, 3]

# Better
values = [1, 2, 3]

Shadowing list makes the built-in constructor unavailable through that name in the current scope.

2. Variables, Objects, and Data Types

A variable is best understood as a name bound to an object. Assignment does not copy a value automatically; it creates or changes a reference to an object.

first = [10, 20]
second = first
second.append(30)
print(first)       # [10, 20, 30]

Both names refer to the same list. This is important when working with mutable objects.

Core built-in types

TypeExampleTypical use
int42Counts and whole-number quantities
float3.14Measurements and continuous numerical values
complex2 + 3jSpecialised numerical computation
boolTrueLogical conditions
str"sales"Text and categorical labels
list[2, 4, 6]Ordered, mutable collection
tuple(2024, 12, 31)Ordered, usually immutable collection
set{"A", "B"}Unique, unordered values
dict{"id": 17}Key–value mapping
NoneTypeNoneAbsence of a value

The type() function reports an object’s type, while isinstance() tests whether an object belongs to a type or compatible class.

amount = 125
print(type(amount))
print(isinstance(amount, int))

Numeric types and conversion

Python supports ordinary arithmetic on integers and floating-point numbers. Floating-point values are approximations, so direct equality comparisons may be unsafe for some calculations.

print(0.1 + 0.2 == 0.3)  # Often False

For tolerance-based comparison, use math.isclose():

import math
math.isclose(0.1 + 0.2, 0.3)

Conversions should be explicit and validated where input is external:

count = int("25")
ratio = float("0.75")
label = str(25)

The conversion int(3.9) truncates toward zero and returns 3; it does not round to the nearest integer.

Strings

Strings are ordered sequences of Unicode characters and are immutable. Indexing starts at zero.

word = "Python"
print(word[0])      # P
print(word[-1])     # n
print(word[1:4])    # yth

Useful string methods include .strip(), .lower(), .upper(), .split(), .replace(), and .startswith(). Methods return a new string rather than modifying the original.

Formatted string literals, or f-strings, are usually the clearest way to construct output:

name = "Amina"
score = 87.456
print(f"{name} achieved {score:.1f}%")

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

Collections and mutability

Lists are ordered and mutable. Tuples are ordered and immutable. Sets contain unique elements and are useful for membership tests. Dictionaries associate keys with values.

scores = [68, 74, 81]
student = {"name": "Amina", "scores": scores}
unique_scores = set(scores)

Dictionary access can raise KeyError if a key is absent. The .get() method provides a default:

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

Truth-value testing applies to many objects. Empty strings, empty collections, numeric zero, and None are false-like; most other objects are true-like.

if scores:
    print("Scores are available")

Use is None rather than == None when testing for the singleton None value.

3. Operators and Expressions

An expression produces a value. Operators combine values or transform them.

Arithmetic operators

OperatorMeaningExample
+Addition or concatenation4 + 3 → 7
-Subtraction4 - 3 → 1
*Multiplication or repetition4 * 3 → 12
/True division7 / 2 → 3.5
//Floor division7 // 2 → 3
%Remainder7 % 2 → 1
**Exponentiation2 ** 3 → 8

Floor division returns the mathematical floor of the quotient, not merely a truncated result. For negative values, -7 // 2 is -4 because −4 is the greatest integer less than or equal to −3.5.

Operator precedence generally evaluates parentheses first, then exponentiation, multiplication/division/remainder, and addition/subtraction. Use parentheses when they improve clarity.

Comparison and Boolean operators

Comparison operators return Boolean values: ==, !=, <, <=, >, and >=. Boolean operators combine conditions:

  • and is true only when both operands are true;
  • or is true when at least one operand is true;
  • not reverses truth value.

Python uses short-circuit evaluation. In A and B, Python does not evaluate B if A is false. In A or B, it does not evaluate B if A is true. This can prevent errors:

if record is not None and record["status"] == "valid":
    process(record)

The second condition is evaluated only when record is not None.

Do not confuse assignment (=) with equality comparison (==). The identity operator is tests whether two names refer to the same object; it is not a general replacement for ==.

Membership and assignment expressions

The operators in and not in test membership:

if "error" in message.lower():
    flag_record()

The assignment expression operator :=, introduced in Python 3.8, can assign within an expression. It should be used sparingly because clarity is more important than compactness.

4. Input and Output

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

raw_age = input("Enter age: ")
age = int(raw_age)

This code fails if the user enters non-numeric text. A reliable program validates or handles the conversion.

try:
    age = int(input("Enter age: "))
except ValueError:
    print("Age must be a whole number.")

Output is commonly produced with print(). Multiple arguments are separated by a space by default, and sep and end can change formatting.

print("A", "B", sep=" | ")
print("Loading", end="...")

For data-science workflows, avoid relying on visually formatted output as a data-storage method. Use structured formats such as CSV, JSON, or database tables when results must be reused.

5. Conditional Logic

Conditional statements select actions based on Boolean expressions.

if temperature < 0:
    category = "freezing"
elif temperature < 20:
    category = "cool"
else:
    category = "warm"

Conditions are evaluated from top to bottom, and only the first matching branch executes. Therefore, branch order matters. A broad condition placed first can make later branches unreachable.

Nested conditions are sometimes appropriate, but compound Boolean expressions are often clearer:

if 0 <= score <= 100 and attendance >= 75:
    eligible = True
else:
    eligible = False

Python’s chained comparison 0 <= score <= 100 is equivalent to (0 <= score) and (score <= 100).

A conditional expression is suitable for a simple two-way choice:

status = "pass" if score >= 50 else "fail"

It should not be used for complex multi-step logic.

Structural pattern matching

Python 3.10 introduced match and case, which can express choices based on a value or structure.

command = "quit"

match command:
    case "start":
        print("Starting")
    case "quit":
        print("Stopping")
    case _:
        print("Unknown command")

For simple value tests, if may remain more familiar. Pattern matching becomes valuable when processing structured records or multiple alternatives.

6. Iteration and Repetition

A for loop iterates over items in an iterable, such as a list, string, dictionary, set, file, or range.

scores = [68, 74, 81]
for score in scores:
    print(score)

Use range() when a sequence of integer positions is required:

for index in range(3):
    print(index)

range(3) produces 0, 1, and 2. The stop value is excluded. The general form is range(start, stop, step).

A while loop repeats while a condition remains true:

attempts = 0
while attempts < 3:
    print("Attempt", attempts + 1)
    attempts += 1

Every while loop must have a credible path toward termination. Failure to update the controlling state creates an infinite loop.

Loop control

break exits the nearest loop. continue skips to the next iteration. pass performs no action and is useful as a temporary placeholder, not as a substitute for completed logic.

for value in values:
    if value is None:
        continue
    if value < 0:
        break
    process(value)

A loop may have an else clause. The else block executes if the loop finishes normally, but not if it exits through break.

for number in numbers:
    if number == target:
        print("Found")
        break
else:
    print("Not found")

Pythonic iteration

Prefer direct iteration over manually managing indices when indices are unnecessary. Use enumerate() when both index and value are needed, and zip() to traverse related sequences.

for position, score in enumerate(scores, start=1):
    print(position, score)

for name, score in zip(names, scores):
    print(name, score)

Comprehensions provide concise construction of collections when the transformation is simple.

squares = [number ** 2 for number in range(6)]
positive = [value for value in values if value > 0]

Avoid excessively complex comprehensions; a conventional loop is better when several operations or conditions are involved.

7. Exceptions and Reliable Programs

An exception is an event indicating that normal execution cannot continue at a particular point. Common exceptions include ValueError, TypeError, IndexError, KeyError, FileNotFoundError, and ZeroDivisionError.

The basic structure is:

try:
    value = int(user_text)
except ValueError:
    print("Enter a valid integer.")
else:
    print(f"Accepted: {value}")
finally:
    print("Input attempt complete.")
  • try contains code that may fail;
  • except handles a specified exception;
  • else runs when no exception occurs;
  • finally runs whether or not an exception occurs.

Catch specific exceptions rather than using a bare except, which can hide programming errors and interrupt signals.

A program may raise an exception deliberately when an input violates a contract:

def percentage(value):
    if not 0 <= value <= 100:
        raise ValueError("percentage must be between 0 and 100")
    return value

Exception handling should occur at the level that can make a meaningful decision. Do not silently discard errors. Record, report, recover, or terminate clearly according to the application’s requirements.

8. Worked Example: Validating and Summarising Measurements

The following program accepts measurements until the user enters done. It handles invalid input, rejects negative values, and calculates a mean only when valid data are available.

measurements = []

while True:
    text = input("Enter a non-negative measurement or 'done': ").strip()

    if text.lower() == "done":
        break

    try:
        measurement = float(text)
    except ValueError:
        print("Please enter a numeric value.")
        continue

    if measurement < 0:
        print("Measurement cannot be negative.")
        continue

    measurements.append(measurement)

if measurements:
    mean = sum(measurements) / len(measurements)
    print(f"Count: {len(measurements)}")
    print(f"Mean: {mean:.2f}")
else:
    print("No valid measurements were entered.")

The mean is calculated using:

x̄ = (1/n) ∑ᵢ₌₁ⁿ xᵢ

Here, xᵢ is the i-th valid measurement, n is the number of valid measurements, and x̄ is their arithmetic mean. The formula applies when n > 0; the program therefore checks that the list is non-empty before division. The measurements must also be comparable and expressed in compatible units.

This example illustrates input conversion, validation, a sentinel value, exception handling, a while loop, continue, and guarded calculation.

9. Applied Case: Quality Screening of Sensor Readings

Background

A manufacturing plant receives temperature readings from a sensor every minute. The data-ingestion script must classify readings as normal, warning, or invalid before they enter an analytics pipeline. The illustrative policy is that temperatures below 0 °C or above 120 °C are invalid, readings from 0 °C through 80 °C are normal, and readings above 80 °C through 120 °C require a warning.

Problem

A naïve script might compare values without checking their type or might stop completely when one malformed reading appears. Such behaviour can lose an entire batch of otherwise usable observations.

Approach

The following function validates one raw reading and returns a structured result:

def classify_reading(raw_value):
    try:
        value = float(raw_value)
    except (TypeError, ValueError):
        return {"status": "invalid", "reason": "not numeric"}

    if value < 0 or value > 120:
        return {"status": "invalid", "reason": "outside sensor range"}
    if value > 80:
        return {"status": "warning", "value": value}
    return {"status": "normal", "value": value}

raw_readings = [22.4, "81.2", "offline", 121, 79.8]
results = [classify_reading(item) for item in raw_readings]

Outcome

The valid readings are classified without allowing the malformed string or out-of-range value to terminate the batch. A production implementation would also log invalid records, retain timestamps and sensor identifiers, and define what happens to missing values.

Learning points

Reliable data-science code separates conversion, validation, classification, and reporting. It uses explicit thresholds, handles expected exceptions narrowly, and preserves enough context for later auditing. The policy itself must be documented because thresholds are domain assumptions, not universal Python rules.

10. Pythonic Conventions and Professional Practice

The official style guide, PEP 8, recommends readable formatting, meaningful names, moderate line length, and consistent spacing. Important practices include:

  1. Prefer clarity over cleverness.
  2. Use four spaces for indentation.
  3. Keep functions focused on one responsibility.
  4. Avoid duplicated logic.
  5. Use descriptive names such as valid_measurements, not x when the meaning matters.
  6. Validate data at system boundaries, such as user input or files.
  7. Use with for resources that must be closed.
  8. Write tests for normal, boundary, and invalid cases.
  9. Use version control and code review for collaborative work.
  10. Keep secrets, passwords, and personal data out of source code and logs.

Python’s conventions are not merely cosmetic. Consistent code reduces defects, makes peer review easier, and lowers the cost of modifying analytical pipelines.

11. Common Errors and Limitations

Syntax errors occur before a program can execute, for example a missing colon or unmatched parenthesis. Runtime errors occur during execution, such as dividing by zero. Logical errors allow execution but produce incorrect results, such as using the wrong threshold or calculating a mean over invalid records.

Other recurring problems include:

  • using = where == is required;
  • assuming input() returns a number;
  • modifying a list while iterating over it;
  • relying on exact equality for floating-point calculations;
  • writing a loop with no terminating condition;
  • catching every exception and hiding the underlying defect;
  • confusing a shallow reference with an independent copy;
  • accepting unvalidated external data;
  • using a variable before it has been assigned.

Python’s dynamic typing supports rapid development but shifts some error detection from writing time to execution time. Type hints, linters, formatters, tests, and static analysis can reduce this risk, although they do not replace sound reasoning.

12. Contemporary Relevance to Data Science

Modern data-science programs frequently combine core Python control flow with libraries such as NumPy, pandas, and scikit-learn. Library operations may process entire arrays or columns at once, a practice called vectorisation. Nevertheless, control flow remains essential for validation, configuration, exception handling, model evaluation, and workflow orchestration.

In artificial-intelligence systems, Python code also controls data-quality checks, experiment loops, evaluation criteria, and safety constraints. Automated tools can generate code, but generated code must be reviewed for security, correctness, privacy, computational cost, and hidden assumptions. Input validation is particularly important when code processes user-supplied files or runs in cloud environments.

Chapter Summary

Python programs consist of statements and expressions whose meaning depends on objects, types, operators, and control flow. Names are bound to objects, and mutable objects can be shared by multiple names. Built-in types such as integers, floating-point numbers, strings, lists, sets, tuples, dictionaries, Booleans, and None support common data-science tasks.

Expressions use arithmetic, comparison, Boolean, membership, and identity operators. Input arrives as text and should be converted and validated explicitly. Conditional statements select among alternatives, while for and while loops support repetition. break, continue, loop else, comprehensions, enumerate(), and zip() provide useful iteration patterns.

Exceptions represent abnormal conditions and should be handled specifically at an appropriate level. Reliable programs distinguish invalid external data from programming defects, document assumptions, and test boundary cases. Pythonic code values readability, meaningful names, small responsibilities, explicit validation, and maintainable structure.

Key Terms

Variable: A name bound to an object.

Object: A runtime entity with a value, identity, and type.

Statement: An instruction executed by Python.

Expression: Code that produces a value.

Dynamic typing: A model in which names do not have permanently fixed types.

Mutable: Capable of being changed after creation.

Immutable: Not changeable after creation.

Boolean: A truth value represented by True or False.

Operator: A symbol or keyword that performs an operation.

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

Iterable: An object whose elements can be visited sequentially.

Indentation: Leading whitespace used to define Python blocks.

Comprehension: Compact syntax for constructing a collection.

Exception: An event signalling an abnormal execution condition.

Validation: Checking whether data meet defined requirements.

Sentinel value: A special value indicating termination or a particular state.

PEP 8: Python’s widely used style guide.

Type conversion: Transforming a value from one data type to another.

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

Vectorisation: Applying an operation to a collection or array without an explicit Python-level loop.

Review Questions

Short Answer Questions

  1. Why does Python use indentation, and what problem can inconsistent indentation cause?
  2. What is the difference between == and is?
  3. Why must the result of input() usually be converted before numerical processing?
  4. What is the purpose of the else clause on a loop?
  5. Why is catching a bare except generally discouraged?

Long Answer Questions

  1. Explain Python’s object-and-name model, including the consequences of assigning one mutable list to two names.
  2. Compare for and while loops and give circumstances in which each is appropriate.
  3. Explain arithmetic, comparison, Boolean, membership, and identity operators with examples.
  4. Describe the structure and purpose of try, except, else, and finally.
  5. Discuss how Pythonic conventions improve reliability and maintainability in data-science programs.

Analytical / Application Questions

  1. Design a program that reads a sequence of examination marks, rejects values outside 0–100, and reports the mean and number of valid marks. Identify the exceptions and boundary cases that must be handled.
  2. A loop processes sensor records but occasionally receives None, text, or negative values. Propose a validation and exception-handling strategy that preserves valid records and provides an audit trail.
  3. A program classifies customer transactions using several thresholds, but the broadest condition is placed first and prevents later conditions from running. Explain the defect and redesign the conditional logic.

Multiple Choice Questions with Answers

Question 1

What does the expression 7 // 2 evaluate to?

A. 3

B. 3.5

C. 4

D. 1

Correct Answer: A. 3

Question 2

Which function converts a string such as "12.5" to a floating-point number?

A. str()

B. float()

C. int()

D. bool()

Correct Answer: B. float()

Question 3

What does input() return before any explicit conversion?

A. An integer

B. A Boolean

C. A string

D. A list

Correct Answer: C. A string

Question 4

Which statement skips the remainder of the current loop iteration?

A. break

B. pass

C. stop

D. continue

Correct Answer: D. continue

Question 5

Which exception is most directly associated with int("abc")?

A. ValueError

B. KeyError

C. IndexError

D. FileNotFoundError

Correct Answer: A. ValueError

Question 6

What values are produced by range(3)?

A. 1, 2, 3

B. 0, 1, 2

C. 0, 1, 2, 3

D. 3 only

Correct Answer: B. 0, 1, 2

Question 7

Which expression safely checks whether a variable named value is the singleton None?

A. value == empty

B. value = None

C. value is None

D. value equals None

Correct Answer: C. value is None

Question 8

When does a loop’s else block execute?

A. Whenever the loop contains a continue

B. Only when the loop exits through break

C. Before the first iteration

D. When the loop finishes without break

Correct Answer: D. When the loop finishes without break

Question 9

Which construct is most suitable for creating a list of squares from 0 through 5?

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

B. [n + 2 for n in range(5)]

C. {n ** 2 in range(6)}

D. range(n ** 2, 6)

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

Question 10

Why does the worked measurement example test whether the list is non-empty before calculating the mean?

A. To prevent division by zero

B. To convert strings to integers

C. To sort the measurements

D. To make negative values positive

Correct Answer: A. To prevent division by zero

Python for Data Science
Chapter 2 of 15