Introduction
You spend hours processing repetitive digital data by hand, copying numbers between spreadsheets and searching through unstructured text files. These manual tasks drain your time and limit your ability to solve larger analytical problems.
Charles R. Severance demonstrates that Python serves as your personal digital assistant to automate these exact chores. You learn to write concise scripts that transform raw information into structured knowledge without requiring a background in computer science.
The journey begins with basic variables and control flow before advancing to data structures, files, and regular expressions. You then extend your programs to communicate across networks, parse web services, and store records in structured databases.
- How do you instruct a computer to make logical decisions?
- What mechanisms store and retrieve complex collections of information?
- How do you fetch data from remote servers across the internet?
1. Why Learn to Write Programs
Programming lets you delegate repetitive tasks to computers, freeing your creativity for higher-level work.
Computers are fast and excel at boring tasks like counting words in text. Humans easily read text but struggle with tedious counting. By learning the language of computers, you can act as the creative force while using computers as personal digital assistants.
Counting the most used word in the first three paragraphs of the chapter by hand is painful for humans. A python script can quickly determine that the word "to" is used 16 times.
Hardware and Interpreters
A computer consists of hardware components that must be orchestrated by you to store data and execute instructions. The hardware includes the Central Processing Unit which continuously asks what to do next, Main Memory which is fast but loses data when power is turned off, Secondary Memory which retains data without power, Input and Output devices, and Network Connection.
A 3.0 Gigahertz CPU rating means the processor asks what next three billion times per second. If you were 5mm tall and inserted into the computer, you would see billions of transistors switching rapidly.
High-level languages like Python must be translated into simple machine language zeros and ones using either interpreters or compilers. An interpreter reads and translates source code line by line on the fly in interactive mode. A compiler translates the entire program into a machine language file all at once for later execution.
Debugging and Core Concepts
Every programming language builds useful programs by combining six basic low-level conceptual patterns. Programs are constructed by weaving together input, output, sequential execution, conditional execution, repeated execution, and reuse.
Fixing code and hard bugs requires a combination of four distinct debugging activities rather than relying on just one. Beginning programmers often get stuck in failure modes like random walk programming. Effective debugging requires reading code aloud, running experiments, ruminating on error types, and retreating to a working state.
- Examine your code, read it back to yourself, and check that it says what you meant to say.
- Experiment by making changes and running different versions.
- Take some time to think about what kind of error it is and what you changed last.
- Back off and undo recent changes until you get back to a program that works and that you understand.
Syntax errors, logic errors, and semantic errors demand careful diagnosis. You must understand how to read error traces and interpret feedback from the interpreter.
2. Variables and Control Flow
Values handled by a program belong to distinct types that dictate how the interpreter treats them. Basic types include integers for whole numbers, floating point numbers for numbers with decimal points, and strings for text enclosed in quotation marks. Variables are names that refer to values, but their naming is constrained by reserved keywords and syntactic rules. Variable names can contain letters, numbers, and underscores, but cannot start with a number or use Python's 35 keywords which define the structure of the language. Expressions combine operators and operands, and Python evaluates them following strict mathematical precedence rules. Mathematical operations follow PEMDAS rules where parentheses have highest precedence, followed by exponentiation, multiplication and division, and lastly addition and subtraction.
Expressions, conditionals, and functions form the foundational syntax that guides program execution.
Conditional Execution
Programs change their behavior based on conditions using if, elif, and else statements. Python executes conditional branches depending on whether a boolean expression evaluates to True or False. Compound statements require a colon at the end of the header and an indented body. The try and except structure acts as an insurance policy to catch expected and unexpected runtime errors gracefully. Python executes code inside the try block. If an exception occurs, execution jumps immediately to the except block, preventing the program from crashing with an unfriendly traceback. Python stops evaluating logical expressions from left to right as soon as the final outcome is already known. This short-circuit behavior allows programmers to use the guardian pattern, placing safe checks before expressions that might otherwise cause runtime errors like division by zero.
Converting user input to a float where typing 'fred' instead of a number triggers a ValueError, caught by the except block. Evaluating x >= 2 and y != 0 and (x / y) > 2 where y != 0 guards against division by zero when y is 0.
You should follow these practices for conditionals:
- Write a header ending with a colon character.
- Indent the block of statements under the header.
- Use elif for chained conditions and else for the final alternative.
- Place potentially risky code inside the try block.
- Define recovery or error message statements inside the except block.
- Order logical expressions so that safer conditions appear before potentially dangerous operations.
- Use comparison guards in conjunction with the and operator.
Functions and Iteration
Functions are named sequences of statements that perform computations and can be reused throughout a program. Defining a function creates a function object using the def keyword, and the statements inside are not executed until the function is explicitly called by name. Arguments passed into functions are assigned to local parameters inside the function definition. When you call a function, expressions used as arguments are evaluated once before execution jumps to the function body. Python provides built-in modules like math and random to handle mathematical functions and pseudorandom number generation. Modules must be imported before use, and their contents are accessed via dot notation.
The while statement executes a block of statements repeatedly as long as a specified condition remains true. An iteration variable must change during each loop cycle so that the condition eventually becomes false, preventing an infinite loop. Definite loops using the for statement iterate through a known set of items, such as lists or strings. Common loop patterns involve initializing accumulator or tracking variables before scanning a sequence to find totals, counts, maximums, or minimums. To track maximum or minimum values, variables are initialized to None and updated inside the loop whenever a value exceeds or falls below the current extreme.
Using an if-else statement to check if a number is even or odd via the modulus operator. Defining print_lyrics and repeat_lyrics to output song lines without repeating code. Passing math.pi or 'Spam' * 4 as an argument to a user-defined function. Using math.sin(radians) and random.randint(5, 10) to generate random integers, where random.randint(5, 10) returns an integer between 5 and 10 including both, and pi value is accurate to about 15 digits. Using a while True loop with a break statement to keep prompting user input until they type done. Iterating through friends = ['Joseph', 'Glenn', 'Sally'] and printing a greeting for each. Initializing largest = None and updating it when itervar > largest during a list scan.
You should follow these practices for functions and iteration:
- Use the def keyword followed by the function name and parentheses.
- End the header line with a colon and indent the function body.
- Call the function by its name followed by parentheses.
- Specify parameter names inside the parentheses in the function definition header.
- Provide arguments matching the required inputs when calling the function.
- Import the module using the import statement.
- Access module functions using dot notation specifying module name and function name.
- Initialize the iteration variable before the loop.
- Write the while statement with a boolean condition.
- Modify the iteration variable inside the loop body to avoid an infinite loop.
- Use the for keyword followed by the iteration variable and the in keyword.
- Specify the collection or list of items to loop through.
- Indent the body statements to be executed for each item.
- Initialize variables like count, total, or extreme trackers before the loop starts.
- Update the variables inside the loop body during each iteration.
- Examine the resulting variables after the loop completes.
3. Sequences and Files
Strings, files, and lists organize data into mutable and immutable sequences for efficient processing.
Strings and Files
Python string indices represent offsets from the beginning, where the first element is at index zero. Negative indices count backward from the end. Because strings are immutable, attempting item assignment raises a TypeError, requiring the creation of a new string variation instead.
Trying to change greeting[0] = 'J' on the string 'Hello, world!' raises a TypeError: 'str' object does not support item assignment.
Strings have built-in methods accessed via dot notation that allow searching, modifying case, stripping whitespace, and parsing substrings. Methods such as find, lower, upper, strip, and startswith provide powerful text processing capabilities. Using find with a starting index and string slicing enables parsing specific data components out of formatted text lines.
Extracting 'uct.ac.za' from the data string 'From stephen.marquard@uct.ac.za Sat Jan 5 09:14:16 2008' using find for '@' and space positions followed by slicing (where atpos = 21 and sppos = 31).
Files reside in secondary memory and can be processed line by line using file handles and for loops to maintain low memory consumption. Opening a file communicates with the operating system to return a file handle. Using a for loop over the file handle reads data one line at a time, splitting on newline characters, which allows processing massive files efficiently without exhausting main memory.
Reading 94,626 characters total in mbox-short.txt into a single string using the read method.
User-supplied file names can cause runtime crashes if the file is missing, which is handled gracefully using try and except blocks. Because users or quality assurance teams will input arbitrary or nonexistent file names, wrapping the open function in a try/except structure allows catching the FileNotFoundError and exiting or recovering cleanly.
Entering missing.txt or 'na na boo boo' causes a FileNotFoundError traceback unless protected by a try/except block (which protects operations across files containing 1797 subject lines in mbox.txt or 27 subject lines in mbox-short.txt).
- Prompt the user for a file name using input.
- Wrap the open statement inside a try block.
- Add an except block to print an error message and terminate via exit() when opening fails.
Lists and Aliasing
Lists are mutable ordered sequences of values that support element reassignment, slice updates, and various modification methods. Unlike strings, lists allow changing their elements or order in place using bracket assignment or slice assignment. Methods like append, extend, sort, pop, remove, and del modify the list and typically return None.
Reassigning the one-th element of numbers = [17, 123] to 5 resulting in [17, 5].
Strings can be broken into lists of characters using list() or words using split(), and lists can be concatenated back into strings using join(). The split method breaks a string into a list of words using whitespace or a specified delimiter. The join method is a string method invoked on a delimiter that takes a list of strings and concatenates them.
Splitting 'pining for the fjords' into ['pining', 'for', 'the', 'fjords'] using s.split().
Variables reference objects in memory, and assigning one variable to another creates an alias where mutations affect all references. Two variables can be equivalent by having the same value, or identical by referencing the exact same object checked via the 'is' operator. When a mutable object like a list is aliased, modifications made through one variable affect the other.
Assigning b = a where a = [1, 2, 3] creates an alias, so changing b[0] = 17 also modifies a.
The guardian pattern uses logical checks before processing operations to prevent runtime crashes caused by malformed or unexpected data. When parsing files and splitting lines into words, blank lines or unexpected structures can lead to IndexError when accessing indices like words[0]. Adding guardian conditions such as checking the length of the words list prevents these failures.
Encountering a blank line during mail log parsing results in an empty word list [], causing an IndexError when checking words[0] without a guardian.
- Split each line of a file into words.
- Check if the length of the words list is zero using a guardian condition.
- Use continue to skip uninteresting or empty lines safely before accessing specific word indexes.
4. Dictionaries and Regular Expressions
Dictionaries map keys to values of any type, unlike lists whose indices must be integers.
A dictionary is a mapping between a set of indices called keys and a set of values. Python uses a hash table algorithm for dictionary lookups, meaning the in operator takes about the same amount of time regardless of how many items are in the dictionary. Each key-value pair forms an item.
Building an English to Spanish dictionary that maps keys like 'one' to values like 'uno'.
Dictionaries
The get method provides a concise idiom for counting frequencies in a dictionary without manual conditional checks. Instead of writing an if-else statement to check if a key exists before incrementing its counter, the get method takes a key and a default value, returning the current value or the default if the key is absent.
Counting letters in the word 'brontosaurus' using counts.get(c, 0) + 1.
- Call the dictionary get method with the key and a default value of zero.
- Add one to the result to implement a concise counting loop idiom.
Advanced text analysis requires cleaning input by stripping punctuation and converting capitalization. To parse text files accurately without treating capitalized words or punctuated words as distinct entities, programmers use string methods like lower and translate combined with string.punctuation.
Parsing the full text of Romeo and Juliet to count word frequencies accurately.
- Open the text file and read line by line.
- Strip trailing whitespaces using rstrip().
- Remove all punctuation using line.translate(line.maketrans('', '', string.punctuation)).
- Convert the line to lowercase using lower().
- Split the line into words and update the dictionary counts.
Tuples and Regular Expressions
Tuples are immutable sequences that can be sorted and used as composite dictionary keys. A tuple is a comma-separated sequence of values that cannot be modified after creation. Because they are immutable, they are hashable, meaning they can serve as dictionary keys or elements in sorted collections. A tuple with one element is a singleton.
Creating a telephone directory that maps last-name and first-name tuple keys to phone numbers.
The Decorate-Sort-Undecorate pattern allows sorting sequences based on custom criteria by leveraging tuple structures. The DSU pattern involves decorating a sequence by building a list of tuples with sort keys preceding the elements, sorting the list using Python built-in sort, and undecorating by extracting the sorted elements.
Sorting a list of words from longest to shortest by prepending each word with its length in a tuple.
- Decorate a sequence by building a list of tuples with one or more sort keys preceding the elements.
- Sort the list of tuples using the Python built-in sort.
- Undecorate by extracting the sorted elements of the sequence.
Tuples are immutable sequences that can be sorted and used as composite dictionary keys.
Tuple assignment allows assigning multiple variables simultaneously from a single sequence on the right side. Python allows a tuple of variables on the left side of an assignment statement and a sequence on the right side, evaluating all expressions on the right before assigning them respectively.
Swapping the values of two variables with a single statement like a, b = b, a.
- Place a tuple of variables on the left side of an assignment statement.
- Provide a sequence on the right side of the assignment statement.
- Omit parentheses on the left side for standard stylistic usage.
Regular expressions provide a powerful mini-language for searching, matching, and extracting patterns within strings. Using the re module, programmers can use special characters like carets for line beginnings, periods for wildcards, and quantifiers to execute sophisticated text parsing with minimal code.
Searching for lines starting with From: using re.search('^From:', line).
- Import the regular expression module re.
- Open the target file and loop through its lines.
- Apply re.search() or re.findall() with specialized pattern strings.
Parentheses in regular expressions isolate specific portions of matching strings for extraction. When using findall(), enclosing a section of a regular expression in parentheses instructs the parser to match the entire expression while only returning the substring trapped inside the parentheses. Greedy matching is the default behavior that captures the longest possible string.
Extracting floating-point numbers from lines starting with X-DSPAM-Confidence using re.findall('^X\\S*: ([0-9.]+)', line).
- Construct a regular expression matching the target line format.
- Wrap the specific target substring portion in parentheses.
- Call re.findall() to retrieve only the extracted portion in the resulting list.
5. Networked Programs and Web Services
Network protocols, web APIs, and object-oriented programming allow your scripts to interact with the world.
Sockets and Web Retrieval
Network communication in Python can be managed at a low level using sockets, which act like bidirectional files requiring strict adherence to defined communication protocols. A socket provides a two-way connection between two programs where both can read and write data. Because endpoints will wait indefinitely if data is missing, protocols establish precise rules for message order and responses. The HTTP protocol on port 80 uses commands like GET followed by a blank line, represented by end-of-sequence characters. The complex RFC2616 document describing HTTP spans 176 pages, while a simple web browser socket read loop uses a chunk size of 512 characters.
Connecting to data.pr4e.org on port 80 to request romeo.txt using socket.socket(socket.AF_INET, socket.SOCK_STREAM).
- Import the socket library.
- Create a socket using socket.socket(socket.AF_INET, socket.SOCK_STREAM).
- Connect to the target host and port using mysock.connect((host, port)).
- Encode and send the GET command ending with a blank line using mysock.send() or mysock.sendall().
- Loop to receive data chunks from the socket until an empty string is returned.
- Close the socket using mysock.close().
Python provides the urllib library to handle HTTP protocol and header details automatically, allowing web pages to be read just like local files. While low-level sockets require manual header parsing and byte encoding, urllib abstracts these tasks. For text files, urlopen returns a file-handle that can be iterated over using a for loop after headers are automatically consumed. For binary files like images, entire contents or buffered blocks can be downloaded and written in binary write mode. Large binary files use a block size of 100,000 characters to download safely without running out of memory, demonstrated by copying the cover3.jpg binary file to a total size of 230,210 characters.
Reading romeo.txt and downloading cover3.jpg using urllib.request.urlopen.
- Import urllib.request.
- Open a web page URL using urllib.request.urlopen(url).
- Iterate over lines or read data from the resulting file handle.
- Decode and strip lines if processing text, or write blocks to disk using a binary write file handle.
Web Services and OOP
BeautifulSoup is a robust Python library designed to parse flawed HTML documents, tolerating malformed syntax and allowing easy extraction of tags and attributes. While regular expressions can extract well-formed HTML links, they often fail on broken HTML. BeautifulSoup builds a parse tree from flawed HTML, letting developers retrieve specific tags such as anchor tags and inspect their attributes, contents, and relative paths.
Using BeautifulSoup with html.parser to extract anchor tags and their href attributes from https://docs.python.org.
- Import urllib.request, BeautifulSoup from bs4, and ssl.
- Bypass SSL certificate errors using an unverified default context.
- Open the URL and read the HTML data.
- Create a BeautifulSoup object using soup = BeautifulSoup(html, 'html.parser').
- Retrieve tags by calling soup('tag_name') and extract attributes using tag.get('attr').
XML is a structured markup format representing data as a tree of elements, which can be parsed efficiently in Python using the ElementTree library. XML uses opening and closing tags, attributes, and nested elements to structure document-style data. Python's xml.etree.ElementTree module converts XML strings into tree objects, enabling data extraction via methods like find() and findall() without manual syntax handling.
Parsing person and user node structures using ET.fromstring() and iterating through subtrees.
- Import xml.etree.ElementTree as ET.
- Convert an XML string into a tree using tree = ET.fromstring(data).
- Extract specific text or attributes using tree.find('tag').text or tree.find('tag').get('attr').
- Use findall('parent/child') with parent level elements included to loop through collections of nodes.
JavaScript Object Notation is a lightweight data exchange format that maps directly to native programming language structures like lists and dictionaries. JSON has fewer capabilities and is less self-describing than XML, but its simplicity has made it the industry standard for web services. Using Python's built-in json library, json.loads() parses JSON strings directly into native Python lists and dictionaries without requiring specialized parsing methods.
Parsing a JSON string containing user records and traversing it with standard Python loops and index operators.
- Import the json library.
- Read and decode raw data into a string from a web service or file.
- Parse the JSON string into native Python data structures using info = json.loads(data).
- Traverse the resulting lists and dictionaries using standard Python index operators and for loops.
Service-oriented architecture uses Application Program Interfaces to enable separate applications to communicate over networks using standardized protocols and data formats. An API acts as a contractual agreement defining interaction patterns between components. SOA allows applications to consume remote services rather than containing all logic locally, ensuring data is maintained in a single place. APIs often enforce rate limits and require security mechanisms such as API keys or OAuth cryptographic signatures. Sample exercises use a default fallback API key of 42, while retrieved JSON responses like the Google Geocoding API response for Ann Arbor, MI span 1736 characters.
Accessing the Google Geocoding API and Twitter timeline API using HTTP requests augmented with parameters and API keys.
- Construct a service URL with proper query parameters using urllib.parse.urlencode().
- Send an HTTP request via urllib.request.urlopen() with SSL context handling.
- Read and decode the response data, checking response status and rate limit headers like x-rate-limit-remaining.
- Handle authentication requirements by appending API keys or generating OAuth signatures.
10 Key Ideas
- Programming lets you delegate repetitive tasks to computers, freeing your creativity for higher-level work.
- A computer consists of hardware components that must be orchestrated by you to store data and execute instructions.
- Fixing code and hard bugs requires a combination of four distinct debugging activities rather than relying on just one.
- Values handled by a program belong to distinct types that dictate how the interpreter treats them.
- Programs change their behavior based on conditions using if, elif, and else statements.
- The while statement executes a block of statements repeatedly as long as a specified condition remains true.
- Strings, files, and lists organize data into mutable and immutable sequences for efficient processing.
- Dictionaries map keys to values of any type, unlike lists whose indices must be integers.
- Tuples are immutable sequences that can be sorted and used as composite dictionary keys.
- Network protocols, web APIs, and object-oriented programming allow your scripts to interact with the world.