2. Syntax, Variables and the Python Object ModelIntroduction and ContextPython programs are built from a small set of syntactic rules, expressions, objects, and operations. These elements may appear simple, but they determine how data is represented, transformed, stored, and passed through an artificial intelligence or machine-learning system. A model-training pipeline, for example, may involve reading configuration values, converting input data, manipulating numerical objects, and passing references to arrays or data structures across many functions. Errors in basic language semantics can therefore produce incorrect results even when the machine-learning algorithm itself is appropriate. This chapter develops the foundations required to reason accurately about Python programs. It explains how Python interprets statements, how names refer to objects, why variables do not have fixed declared types, how identity differs from equality, and how mutability affects program behaviour. It also introduces scalar data types, operators, type conversion, and basic input/output. These concepts provide the basis for later study of functions, collections, modules, classes, numerical computing, and machine-learning libraries. Learning ObjectivesBy the end of this chapter, the learner should be able to: 1. Describe Python's indentation-based syntax and naming conventions. 2. Distinguish expressions, statements, names, objects, and references. 3. Explain dynamic typing, object identity, equality, and mutability. 4. Use Python's principal scalar types appropriately. 5. Apply arithmetic, comparison, logical, assignment, and membership operators. 6. Perform explicit type conversion and identify conversion risks. 7. Use `input()` and `print()` for basic interaction and formatted output. 8. Diagnose common errors involving names, types, aliases, and mutable objects. 9. Select reliable coding practices for AI and ML development. Reading Python as a LanguagePython source code is interpreted according to lexical, syntactic, and semantic rules. Lexical rules determine how characters form tokens such as identifiers, keywords, literals, and operators. Syntax determines whether those tokens form a valid statement. Semantics determines what a valid statement does. Python uses indentation to delimit blocks rather than braces. The statements controlled by an `if`, `for`, `while`, function, or class definition must be indented consistently: ```python score = 0.82 if score >= 0.80: print("Accept model") else: print("Review model") ``` A colon introduces a suite, or block of statements. Indentation is therefore part of the program's structure, not merely a formatting preference. Mixing tabs and spaces or using inconsistent indentation can cause an `IndentationError` or make code difficult to maintain. The widely adopted convention is four spaces per indentation level. Comments begin with `#` and extend to the end of the line. They explain intent, assumptions, or non-obvious decisions, but should not be used to compensate for unclear code. Python also supports strings delimited by triple quotes; when placed immediately inside a function, class, or module, such a string can serve as a docstring. Python identifiers are names used for variables, functions, classes, modules, and other objects. An identifier may contain letters, digits, and underscores, but cannot begin with a digit and is case-sensitive. Thus, `model`, `Model`, and `MODEL` are different names. Keywords such as `if`, `class`, `return`, and `True` have predefined grammatical meanings and cannot be used as ordinary identifiers. Good naming communicates a program's domain. `learning_rate`, `validation_loss`, and `image_count` are preferable to vague names such as `x1`, `temp`, or `data2` when the meaning is important. The usual conventions are `snake_case` for variables and functions, `PascalCase` for classes, and uppercase names for constants by convention. A convention does not make a value immutable; `MAX_RETRIES = 3` can still be reassigned. Expressions, Statements, and AssignmentAn expression is a piece of code that produces a value. Literals such as `42`, `3.14`, and `"Python"` are expressions. So are operations such as `2 + 3`, comparisons such as `accuracy > 0.9`, and function calls such as `len(features)`. A statement performs an action. Assignment is a statement that binds a name to an object: ```python batch_size = 32 ``` The right-hand expression is evaluated first. The resulting object is then associated with the name `batch_size`. Assignment does not copy the textual value into a box labelled with the variable name. This distinction becomes important when several names refer to one mutable object. Python permits multiple assignment and unpacking: ```python width, height = 224, 224 first, second = ["train", "test"] ``` The number of names and values must generally correspond. Chained assignment is also possible: ```python left = right = [] ``` Here both names refer to the same list, a fact that can be surprising if one expects two independent lists. Augmented assignment combines an operation with assignment: ```python learning_rate *= 0.9 counter += 1 ``` For immutable objects such as integers, this normally creates or obtains another integer and rebinds the name. For mutable objects, the precise behaviour depends on the object's implementation; a list's `+=` commonly changes the existing list. The Python Object ModelPython treats data as objects. Every object has an identity, a type, and a value. Identity answers which particular object is involved; type describes the object's classification and supported behaviour; value is the data represented by the object. A variable in Python is best understood as a name bound to an object rather than a permanently typed storage location. Consider: ```python quantity = 10 quantity = "ten" ``` The name `quantity` first refers to an integer object and later refers to a string object. This is dynamic typing: type information is associated with objects and checked during execution, rather than being fixed by a declaration attached to the name. Dynamic typing increases flexibility, but it does not eliminate type discipline. An operation must still be valid for the objects involved: ```python value = "12" value + 3 # TypeErrorvalue = int(value) result = value + 3 ``` The function `type()` reports an object's type, while `id()` returns an identity identifier whose practical use is comparison during a program's execution. The `is` operator tests identity; `==` tests equality. These operations must not be confused: ```python a = [1, 2] b = [1, 2] print(a == b) # True: equal contents print(a is b) # False: distinct list objects ``` Identity comparisons are appropriate for singleton objects such as `None`: ```python if result is None: print("No result was produced") ``` Using `is` to compare ordinary numbers or strings is poor practice because implementation details such as object reuse may make results appear correct in one environment and fail in another. Mutability and AliasingAn object is mutable if its state can be changed after creation. Lists, dictionaries, and sets are mutable. Integers, floats, booleans, strings, tuples, and `None` are immutable in the ordinary Python sense. An immutable object cannot be modified in place; an apparent change produces another object and rebinds a name. Aliasing occurs when multiple names refer to the same object: ```python features = [0.2, 0.7] backup = features features.append(0.9) print(backup) # [0.2, 0.7, 0.9] ``` The assignment `backup = features` did not create a copy. Both names refer to one list. To create a shallow copy, use `features.copy()` or `features[:]`. A shallow copy duplicates the outer container but not necessarily nested objects. Deep copying may be required for nested structures, although it should be used deliberately because it can be expensive and may not work meaningfully for every object. Mutability has practical consequences in AI and ML. A preprocessing function that modifies an input list or configuration dictionary can unintentionally alter data used by another stage. Clear ownership rules, defensive copying at boundaries, and functions that return transformed values rather than silently changing shared state reduce such risks. Scalar Types and Their MeaningA scalar represents one logical value rather than a collection of values. Python's principal built-in scalar types include integers, floating-point numbers, complex numbers, booleans, strings, and the special null object `None`. `int` represents whole numbers and supports arbitrary precision, subject to available memory. `float` usually represents double-precision floating-point values. Floating-point arithmetic is efficient but approximate: ```python print(0.1 + 0.2 == 0.3) # commonly False ``` This is not a failure of Python; it reflects the binary representation of many decimal fractions. For threshold decisions, numerical tolerances are often more appropriate than exact equality. In scientific work, libraries such as NumPy provide more specialized numerical types and comparison utilities. `bool` has the values `True` and `False`. In Python, booleans are related to integers, but code should preserve conceptual clarity rather than relying on that relationship. Values have truth-value behaviour: `None`, `False`, zero, and empty strings or collections are false in Boolean contexts, while most other values are true. `str` represents Unicode text. Strings are immutable and support indexing, slicing, concatenation, and formatting. In data pipelines, it is important to distinguish textual numbers such as `"3.5"` from numerical values such as `3.5`. `None` represents the absence of a value. It is often used as a default, a missing result, or a marker indicating that a function has no meaningful return value. It should be tested with `is None` or `is not None`. Operators and EvaluationArithmetic operators include `+`, `-`, ``, `/`, `//`, `%`, and `*`. Division with `/` produces a floating-point result, whereas `//` performs floor division. The remainder operator `%` is useful for periodic conditions and divisibility. Parentheses should be used when they improve clarity, even when precedence rules would produce the intended result. Comparison operators include `==`, `!=`, `<`, `<=`, `>`, and `>=`. They produce Boolean values. Logical operators `and`, `or`, and `not` combine or invert conditions. Python's `and` and `or` use short-circuit evaluation and return one of their operands, not necessarily a Boolean: ```python name = supplied_name or "anonymous" ``` The default is selected if `supplied_name` is false. Short-circuiting is also useful for guarding an operation, but it should not obscure important logic. Membership operators `in` and `not in` test whether a value occurs in a container or other supported object. Identity operators are `is` and `is not`. Bitwise operators operate on integer bit patterns and are distinct from logical operators; confusing `&` with `and`, or `|` with `or`, is a common error. Operator precedence determines grouping, but explicit parentheses improve maintainability. Python also supports conditional expressions: ```python label = "positive" if score >= 0.5 else "negative" ``` Conversion, Validation, and Input/OutputType conversion may be explicit or implicit. Common constructors include `int()`, `float()`, `str()`, and `bool()`. Conversion can fail or lose information: ```python samples = int("250") threshold = float("0.75") ``` `int(3.9)` truncates toward zero rather than rounding to the nearest integer. `bool("False")` is `True` because the string is non-empty; it does not interpret the word semantically. Applications receiving textual configuration should therefore implement explicit validation rather than rely on naïve Boolean conversion. The `input()` function displays an optional prompt and returns a string. It never returns an integer automatically: ```python raw = input("Enter epochs: ") epochs = int(raw) ``` In robust software, conversion should be protected with validation or exception handling so that malformed input produces a useful message rather than terminating unexpectedly. The `print()` function writes text to standard output. It accepts multiple objects, supports separators and line endings, and works well with f-strings: ```python model_name = "baseline" accuracy = 0.9134 print(f"{model_name}: accuracy={accuracy:.3f}") ``` F-strings make the relationship between values and displayed output clear. Output intended for logs, experiment tracking, or regulatory records should be sufficiently precise and unambiguous. An Applied Scenario: A Model-Training ConfigurationThe following illustrative case shows how the object model and conversion rules affect a small ML workflow. BackgroundA research team creates a command-line experiment script. Users enter the number of training epochs, the learning rate, and whether data augmentation should be enabled. Situation and ProblemThe initial script stores all input directly: ```python epochs = input("Epochs: ") learning_rate = input("Learning rate: ") augment = input("Augment? ") ``` The training function then attempts numerical comparison and arithmetic. It fails because the first two names refer to strings. In addition, checking `if augment:` treats both `"yes"` and `"no"` as true because both are non-empty. Analysis and ApproachThe team defines the expected types and validates values at the program boundary: ```python epochs = int(input("Epochs: ")) learning_rate = float(input("Learning rate: ")) augment_text = input("Augment? [yes/no]: ").strip().lower() if augment_text not in {"yes", "no"}: raise ValueError("Augment must be yes or no") augment = augment_text == "yes" if epochs <= 0 or learning_rate <= 0: raise ValueError("Training parameters must be positive") ``` The configuration is then represented explicitly, and its values are not silently reinterpreted later. If a mutable dictionary is passed to several components, the team either documents ownership or supplies copies so that one component cannot unexpectedly change another component's settings. OutcomeThe revised workflow distinguishes input text from operational values, rejects invalid configurations early, and produces clearer experiment logs. The change is small but improves reproducibility, debugging, and confidence in model comparisons. Learning PointsThe case demonstrates that dynamic typing is not the same as absent typing. Types should be checked at meaningful boundaries. Boolean interpretation requires semantic conversion, and mutable shared configuration requires deliberate ownership. These principles apply equally to notebooks, production APIs, and automated training pipelines. Common Errors, Risks, and Professional PracticeA frequent error is using an uninitialised or misspelled name, producing a `NameError`. Assigning a value of an inappropriate type may produce a `TypeError`; malformed conversion may produce a `ValueError`. These exceptions convey useful information and should be handled selectively rather than hidden with broad exception handlers. Other recurring mistakes include comparing floats for exact equality, using `is` instead of `==`, assuming assignment copies mutable data, relying on the truthiness of arbitrary strings, and naming a variable `list`, `str`, or `sum`, thereby shadowing a built-in function or type. Shadowing can make later code confusing and may break library calls. Professional Python practice includes using a consistent style, meaningful names, small testable expressions, explicit conversions at system boundaries, and automated tools such as linters, formatters, static type checkers, and unit tests. Type hints do not change Python's runtime dynamic typing, but they communicate intended interfaces and allow tools to detect many mismatches before execution: ```python def train(epochs: int, learning_rate: float) -> None: ... ``` In AI and ML, reproducibility also depends on recording configuration values, conversion decisions, units, and missing-value conventions. A value such as `None` should not be confused with zero, an empty string, or a missing numerical observation. Data governance requires that input handling be transparent, validated, and auditable, particularly when data influences consequential decisions. Contemporary RelevanceModern Python development increasingly combines dynamic language flexibility with formal tooling. Type annotations, integrated development environments, linters, formatters, notebooks, and testing frameworks help teams retain Python's rapid development advantages while reducing ambiguity. Data-science libraries introduce additional object models—such as NumPy arrays, tensors, and data frames—with their own mutability, broadcasting, and truth-value rules. Mastery of Python's core object model makes those later behaviours easier to understand, while preventing the mistaken assumption that every library object behaves like a built-in list or scalar. Best-Practice Principles- Use names that express domain meaning and follow established naming conventions.
- Treat assignment as name binding; copy mutable objects deliberately when independence is required.
- Use `==` for equality and `is` primarily for singleton checks such as `None`.
- Convert and validate external input before using it in computation.
- Avoid exact comparison of floating-point values when a tolerance is appropriate.
- Keep type assumptions visible through validation, documentation, and type hints.
- Avoid shadowing built-ins and avoid unnecessary mutation of shared state.
- Record configuration and conversion choices in reproducible AI and ML workflows.
Chapter SummaryPython syntax combines statements, expressions, indentation, names, and operators to form executable programs. Names bind to objects; they do not possess permanent declared types. Every object has identity, type, and value, and the distinction between identity and equality is essential when reasoning about program behaviour. Immutable objects cannot be changed in place, whereas mutable objects can be shared through aliases and modified unexpectedly. The chapter examined scalar types, truth values, operators, floating-point limitations, explicit conversion, and basic input/output. It showed why external text must be converted and validated before entering an ML computation and why mutable configuration requires clear ownership. These foundations support reliable programming with later Python collections, functions, classes, numerical libraries, and machine-learning frameworks. Key TermsDynamic typing: A typing model in which objects carry type information checked during execution, while names may be rebound to objects of different types. Identifier: A syntactically valid name used for a variable, function, class, or other program entity. Expression: Code that evaluates to a value. Statement: An instruction that performs an action or controls execution. Object: A Python runtime entity with identity, type, and value. Reference: The relationship by which a name or container element points to an object. Binding: The association of a name with an object. Identity: The property distinguishing one particular object from another. Equality: A comparison of whether two objects represent equivalent values. Mutability: The ability of an object to change state after creation. Immutable object: An object whose state cannot be changed in place. Aliasing: The condition in which multiple names refer to the same object. Scalar: A single logical value rather than a collection. Type conversion: The transformation of a value from one type representation to another. Truth value: The Boolean interpretation of an object in a conditional context. Operator precedence: Rules determining how an expression is grouped and evaluated. Short-circuit evaluation: Evaluation that stops once the result of a logical expression is determined. F-string: A string literal supporting embedded expressions and formatted output. Type hint: Optional annotation communicating an intended type or interface. Review QuestionsShort Answer Questions1. Why is indentation syntactically significant in Python? 2. Distinguish an expression from a statement. 3. What is the difference between `is` and `==`? 4. Why does `input()` require conversion before numerical computation? 5. What is aliasing, and why can it be dangerous with lists? Long Answer Questions1. Explain Python's object model using the concepts of identity, type, value, names, and references. 2. Compare mutable and immutable objects, including their implications for assignment and function design. 3. Discuss dynamic typing and explain why it does not remove the need for type validation. 4. Explain the major scalar types and identify practical issues associated with floating-point values and Boolean conversion. 5. Describe a robust approach to reading, converting, validating, and displaying ML experiment parameters. Analytical / Application Questions1. A developer writes `a = [1, 2]; b = a; a.append(3)`. Predict the value of `b`, explain why it occurs, and propose two ways to obtain an independent list. 2. A program accepts `"False"` from a user and executes `enabled = bool(user_input)`. Analyse the result and design a safer conversion strategy. 3. An experiment compares `validation_loss == 0.3` and makes a deployment decision. Explain the risk and recommend a more reliable numerical comparison approach. 22. Multiple Choice Questions with AnswersQuestion 1What does the statement `x = 10` primarily do in Python? A. Declares x as a permanently integer variable B. Binds the name x to an integer object C. Copies the integer into a fixed memory box named x D. Converts every future value of x to an integer Correct Answer: B. Binds the name x to an integer object Question 2Which comparison is appropriate for checking whether a result represents the absence of a value? A. result == None B. result is None C. result = None D. result in None Correct Answer: B. result is None Question 3What is the value of `b` after `a = [1, 2]; b = a; a.append(3)`? A. [1, 2] B. [3] C. [1, 2, 3] D. An error occurs because lists cannot be assigned Correct Answer: C. [1, 2, 3] Question 4Why can `bool("False")` be misleading when interpreting user input? A. Strings cannot be converted to Boolean values B. The conversion always returns False for text C. Any non-empty string is truthy, including "False" D. Boolean conversion rounds the string numerically Correct Answer: C. Any non-empty string is truthy, including "False" Question 5Which statement correctly describes `0.1 + 0.2 == 0.3` in typical Python floating-point arithmetic? A. It is guaranteed to be True because decimal addition is exact B. It is commonly False because many decimal fractions are approximated in binary C. It raises a TypeError because floats cannot be compared D. It is False only when integers are present Correct Answer: B. It is commonly False because many decimal fractions are approximated in binary Question 6What does `input()` return before any explicit conversion? A. An integer B. A Boolean C. A floating-point number D. A string Correct Answer: D. A string Question 7Which naming style is generally recommended for a variable representing a learning rate? A. LearningRate B. learning-rate C. learning_rate D. LEARNINGRATE() Correct Answer: C. learning_rate Question 8What is the main distinction between `==` and `is`? A. `==` tests equality, while `is` tests object identity B. `==` tests identity, while `is` tests numerical order C. They always produce identical results D. `is` is used only for strings Correct Answer: A. `==` tests equality, while `is` tests object identity Question 9What does `int(3.9)` normally produce? A. 4 B. 3 C. 3.9 D. A Boolean value Correct Answer: B. 3 Question 10A function unexpectedly changes a configuration dictionary used elsewhere. Which concept most directly explains the problem? A. Operator precedence B. String formatting C. Aliasing of a mutable object D. Floating-point approximation Correct Answer: C. Aliasing of a mutable object
|