Unit 1 (Review and Programming Practice)
Section 1
- Why decimal numbers are natural for humans, and why binary numbers are more natural for computers.
 - What is a 0 and a 1 in computer hardware.
 - Properties of, and operations with binary numbers.
 - The Carry, and the Ripple Effect.
 - Review: various types of variables, such as integer (int), real number (float), text string (str), tuple (tuple), list (list), dictionary (dict) etc.
 - Review: checking types of variables using the built-in Boolean function isinstance().
 - Review: generating random integers using the function randint() from the random library.
 - Generating random binary numbers.
 - Review: integer division operator // and the modulo operator %.
 - Converting decimal numbers to binary and vice versa.
 - Addition of decimal and binary integers.
 
Section 2
- Properties of, and operations with hexadecimal numbers.
 - Main applications of hex numbers: represent locations (addresses) in computer memory, MAC (hardware) addresses, and color codes.
 - Review: checking whether a given item is present in a list.
 - Using the ASCII table to generate hex numbers.
 - Converting decimal numbers to hexadecimal and vice versa.
 - Hex numbers may contain both lowercase or uppercase characters.
 - Special relationship between hexadecimal and binary numbers.
 - Converting hex numbers to binary one digit at a time.
 - Converting binary numbers to hexadecimal in four-digit chunks.
 - Review: using assertions and exceptions to protect functions against flawed input data.
 - Sometimes, sanity checks may take more lines than the actual function body.
 
Section 3
- Transistor count, Moore’s Law, and CPU clock speed.
 - Exploring transistors and logic gates.
 - Using Python to simulate the seven basic types of logic gates: AND, OR, NOT, NAND, NOR, XOR, and XNOR.
 - Practicing safe programming with assertions and exceptions.
 
Section 4
- Combining logic gates and simulating simple logic circuits in Python.
 - Simulating the XOR gate using the AND, OR and NAND gates, as well as using the AND, OR and NOT gates.
 - Simulating the Half Adder using the XOR and AND gates.
 - Simulating the Full Adder by adjusting the Half-Adder to be stackable.
 - Simulating the Two-Bit, Four-Bit and Eight-Bit Adders as arrays of Full Adders.
 - Simulating the Full-Adder using the XOR, AND and OR gates.
 
Section 5
- Various uses of the underscore character _ in Python.
 - Role of the so-called “shift” in multiplication.
 - Performing multiplication with just addition and shift.
 - Simulating the Four-Bit Multiplier using the Eight-Bit Adder.
 - Simulating the Eight-Bit Multiplier using the Sixteen-Bit Adder.
 - Using the bitwise shift operators <<, >>, <<= and >>= in Python.
 
Unit 2 (Object-Oriented Programming I)
Section 6
- Brief history of computers and programming languages.
 - Importance of using well-designed data structures.
 - How did object-oriented programming evolve from procedural programming.
 - Main differences between procedural and object-oriented programming.
 - Classes consist of data (attributes) and functions (methods).
 - An object is an instance of a class.
 - A class can have many different instances.
 - Instances of a class do not share data.
 - Methods are defined on the level of a class but used on the level of an instance.
 - All data types in Python are classes.
 - The so-called “magic methods” in Python and how they work.
 - The bitwise operators & (AND) and | (OR).
 
Section 7
- Defining new classes using the keyword class.
 - Using the constructor __init__() to define class attributes and initialize their values.
 - The constructor as well as all class methods must have a mandatory first parameter self.
 - All attributes and methods of a class must be accessed through the prefix self.
 - Adding new methods and new attributes to a class.
 - Instantiating a class.
 - It is a good programming practice in OOP to always access class attributes via class methods (not directly).
 - Using the Sympy and other Math libraries.
 - ‘self’ is not a Python keyword.
 - Creating two training classes: Vocabulary and Number.
 
Section 8
- Defining new classes with the keyword class.
 - Using the constructor __init__() to define class attributes and initialize their values.
 - Adding new methods and new attributes to a class.
 - Instantiating a class.
 - Using file operations and working with regular expressions.
 - Not every class needs to have attributes.
 - A class without attributes does not need a constructor.
 - Creating two new classes: Converter and DataMiner.
 
Section 9
- Implementing class Line to represent lines in the XY plane:
- Attributes: startx, starty, angle, length, lw (line width), color, ls (line style).
 - Methods: __init__(), get_endpoint(), set_endpoint(), plot(), get_length(), get_area().
 
 - Making data structures flexible and open to future development, even though this could mean that they require more memory.
 - Premature optimization is the root of all evil (or at least most of it) in programming.
 - Matplotlib has four different line styles, and how to define them.
 - Objects can be stored in lists like any other variables.
 
Section 10
- Using the class Line as a basis to implement class Turtle, a simplified version of the well known Python Turtle Graphics:
- Attributes: posx, posy, angle, linewidth, linecolor, linestyle, drawing, lines.
 - Methods: __init__(), go(), left(), right(), penup(), pendown(), goto(), back(), set_angle(), show(self), width(), color(), style(), get_length(), get_area().
 
 - Practicing working with exceptions.
 - Exploring additional useful functions of Matplotlib.
 
Unit 3 (Object-Oriented Programming II)
Section 11
- Static methods and when they can be useful.
 - How to derive a subclass from a superclass.
 - When a new subclass should be created, as opposed to modifying the original class.
 - If the superclass has attributes, then the constructor of the subclass must call the constructor of the superclass.
 - Using the function super() to access the superclass.
 - Subclasses can add new attributes and methods, as well as override methods.
 - Importance of a well-designed class hierarchy.
 - What is polymorphism and when it can be useful.
 - What are abstract methods and abstract classes, and when they can be useful.
 - Using the abc (Abstract Base Classes) module.
 - Using ternary conditional expressions.
 
Section 12
- Programming practice: Upgrading the Graphics Editor from Python Fundamentals to an object-oriented design (Phase 1).
 - Designing a base class Shape with 6 attributes and 20 methods.
 - Meaning of single and double underscores in the names of class attributes and methods.
 - Review: PEP 8 – Style Guide for Python Code, copying lists via slicing, list comprehension, shallow and deep copying, obtaining memory addresses of Python objects, accessing class attributes via dedicated class methods.
 
Section 13
- Programming practice: Upgrading the Graphics Editor from Python Fundamentals to an object-oriented design (Phase 2).
 - Designing and implementing a hierarchy of classes to represent 10 basic geometric shapes.
 - Practicing class creation and inheritance.
 
Section 14
- Programming practice: Upgrading the Graphics Editor from Python Fundamentals to an object-oriented design (Phase 3).
 - Designing and implementing class Figure to represent figures consisting of one or more basic shapes.
 - Practicing class creation and inheritance.
 
Section 15
- Programming practice: Upgrading the Graphics Editor from Python Fundamentals to an object-oriented design (Phase 4).
 - Designing and implementing three different classes SimpleDrawing, SmartDrawing, and TechDrawing based on three different use cases.
 - Analyzing an example of bad object-oriented programming.
 - In OOP, classes should always work with their own attributes, not with the attributes of other classes.
 - Multiple inheritance, its applications, and its limitations.
 - Multiple inheritance vs. standard inheritance.
 - The Diamond Problem and when it represents a real problem.
 - Mixin classes.
 
Unit 4 (Selected Advanced Techniques)
Section 16
- Recursion and its applications.
 - Flattening lists.
 - Polish (prefix) notation.
 - Interactive input.
 - Functions eval() and exec().
 - Variadic functions.
 - Anonymous (lambda) functions.
 
Section 17
- Various types of tree data structures.
 - Using recursion to work with binary trees.
 - Iterables and iterators, functions iter() and next().
 - Built-in functions any() and all().
 - Defining custom iterators from scratch.
 - Making iterators via generator functions and generator expressions.
 - Applying maps and filters to data, functions map() and filter().
 - Using the function reduce() for cumulative operations with iterables.
 
Section 18
- Using metasyntactic variables.
 - Working with names and namespaces.
 - Creating modules and packages.
 - Using built-in decorators.
 - Creating custom decorators.
 - Writing decorators with parameters.
 - Queuing (chaining) decorators.
 - Decorating class methods.
 - Making Python classes callable.
 - Working with JSON and XML data.
 
Section 19
- Overview of the Pandas and Seaborn libraries.
 - Structure of Pandas DataFrames.
 - Entering data into a DataFrame.
 - Creating scatter plots, regression plots, and residual plots with Seaborn.
 - Using the residual plot to justify the linear regression model.
 - Making simple predictions.
 
Section 20
- Using Pandas to read files from the hard disk or from a URL.
 - Obtaining the number of rows and number of columns of a DataFrame.
 - Obtaining the list of all column names of a DataFrame.
 - Displaying the beginning and the end of a DataFrame.
 - Other operations with DataFrames.
 - Cleaning data.