Programmer’s Python: Everything Is An Object¶
Chapter 1 – Getting Started¶
Main Idea: Python is different from many languages because everything is built around objects and references.
Key Notes:
Python is high-level and easy to read.
Basic Python includes:
Strings → text
Data structures → lists, tuples, dictionaries, sets
Loops →
for,whileConditionals →
if,elif,else
Indentation matters in Python — spacing controls code blocks.
Variables do not store raw values directly the same way some lower-level languages do.
Instead, variables usually refer to objects in memory.
Functions and classes are introduced as things you can create and use like any other object.
Python is considered “Pythonic” when code is clean, readable, and simple.
Important Takeaway: In Python, you should think of code as working with objects and references, not just “variables holding values.”
Chapter 2 – Variables, Objects, and Attributes¶
Main Idea: A variable is really just a name attached to an object.
Key Notes:
Variables are references, not boxes.
Example:
x = 5
y = x
Both
xandycan point to the same object.Objects can have attributes (extra data or properties attached to them).
Python lets you create and use dynamic attributes.
Objects can contain other objects (nested objects).
Python uses namespaces to organize names and avoid conflicts.
Modules also act like namespaces.
Important Terms:
Object = actual thing in memory
Reference = a variable name pointing to that object
Attribute = data or behavior attached to an object
Namespace = a container of names
Important Takeaway: Variables don’t “own” values — they point to objects.
Chapter 3 – The Function Object¶
Main Idea: Functions in Python are objects too.
Key Notes:
A function can be:
stored in a variable
passed into another function
returned from another function
Example:
def greet():
print("hi")
x = greet
x()
Python uses pass-by-object-reference.
You pass the object reference, not a copy of the variable itself.
Lambda expressions create short anonymous functions:
square = lambda x: x * x
Functions can even refer to themselves.
Important Takeaway: A Python function is not just code — it is a real object you can store and manipulate.
Chapter 4 – Scope, Lifetime, and Closure¶
Main Idea: This chapter explains where variables exist, how long they last, and how functions can “remember” data.
Key Notes:
Local variables exist only inside a function.
Global variables exist outside functions.
globallets you change a global variable inside a function.nonlocallets an inner function change a variable in an outer function.Inner functions = functions inside other functions.
Closures happen when a function remembers variables from where it was created.
Example of Closure:
def outer(x):
def inner():
return x
return inner
inner()remembersx.
Why Closures Matter:
Useful for:
keeping state
creating customized functions
decorators later on
Important Takeaway: Functions can carry data with them, even after the outer function is done.
Chapter 5 – Advanced Functions¶
Main Idea: Functions can be made much more flexible and powerful.
Key Notes:
Default parameters
def greet(name="Guest"):
print(name)
Keyword arguments
greet(name="Bob")
Variable-length arguments
def add(*nums):
return sum(nums)
Keyword variable arguments
def info(**kwargs):
print(kwargs)
Unpacking
nums = [1, 2, 3]
print(*nums)
Functions can return multiple values.
Functions can have docstrings and annotations.
This chapter also introduces decorators.
Decorators: A decorator wraps a function to add extra behavior.
Example:
@decorator_name
def my_function():
pass
Important Takeaway: Python functions are powerful because they are flexible objects that can be passed around, customized, and wrapped.
Chapter 6 – Class, Methods, and Constructors¶
Main Idea: Classes are also objects, and methods are just functions with special behavior.
Key Notes:
A class is a blueprint for making objects.
An instance is an object created from a class.
Example:
class Dog:
def __init__(self, name):
self.name = name
__init__is the initializer (often casually called a constructor).selfrefers to the current object.Instance attributes belong to each object.
A method is just a function inside a class.
Python automatically binds methods to objects.
Important Concepts:
Class for behavior
Instance for data
Important Takeaway: Classes themselves are objects, and methods are really just functions that get attached and bound to objects.
Chapter 7 – Inside Class¶
Main Idea: This chapter looks deeper into how objects are created.
Key Notes:
__new__creates the object first.__init__sets it up after creation.So object creation happens in two steps:
create object →
__new__initialize object →
__init__
Special Ideas:
Singleton pattern:
only one object of a class should exist
Useful for controlling object creation.
Immutable objects (like strings, tuples) may need
__new__more than__init__.Objects can sometimes be made callable like functions.
Example:
class Test:
def __call__(self):
print("Called!")
Important Takeaway: Python classes don’t magically create objects — there’s a real creation process happening underneath.
Chapter 8 – Meeting Metaclass¶
Main Idea: A metaclass is basically a class that creates classes.
Key Notes:
If:
a class creates objects,
then a metaclass creates classes
Python’s built-in metaclass is usually
type.
Example:
class Dog:
pass
print(type(Dog))
Output:
<class 'type'>
That means:
Dogis an objectcreated by
type
Metaclass Uses:
control how classes are built
enforce rules
automatically add methods/attributes
create “final” or restricted classes
advanced frameworks often use them
Important Methods:
__new____init____prepare__
Important Takeaway: In Python, classes are objects too, and metaclasses are what make that possible.
Chapter 9 – Advanced Attributes¶
Main Idea: This chapter explains how to control access to object data.
Key Notes:
Python has name mangling for “private” attributes:
self.__secret
This makes it harder (not impossible) to access directly.
Python doesn’t have true private variables like some languages — it uses conventions and name handling.
Properties let you control access to attributes.
Example:
class Person:
def __init__(self):
self._age = 0
@property
def age(self):
return self._age
Why Properties Matter:
Let you make attributes act like methods
Help with:
validation
cleaner code
safer data access
Important Takeaway: Attributes in Python are flexible, and you can control how they are read or changed.
Chapter 10 – Custom Attribute Access¶
Overview¶
Python’s attribute model lets you intercept and customize how attribute lookup, assignment, and deletion behave. This chapter explains how Python resolves attribute access and how you can plug into that mechanism.
Descriptor Objects¶
Python implements the core of attribute access using descriptors — objects that define one or more of:
__get__(self, obj, type=None)
__set__(self, obj, value)
__delete__(self, obj)
A descriptor is any object assigned to a class attribute that implements these methods. For example:
class ValueLogger:
def __init__(self, name):
self.name = name
def __get__(self, obj, obj_type=None):
print(f"Accessing {self.name}")
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
print(f"Setting {self.name} = {value}")
obj.__dict__[self.name] = value
Descriptors are the foundation for Python’s built‑in features such as property, methods, and classmethod/staticmethod behavior.
Customizing Attribute Access¶
Override __getattribute__(self, name) on your class to intercept all attribute lookup. Be careful: you must delegate to the base implementation to avoid infinite recursion:
class Intercept:
def __getattribute__(self, name):
print(f"Looking up {name!r}")
return super().__getattribute__(name)
Customizing Assignment¶
To intercept assignment, override __setattr__. For example, to enforce read‑only semantics:
class ReadOnly:
def __init__(self, value):
self._value = value
def __setattr__(self, name, value):
if name.startswith("_"):
super().__setattr__(name, value)
else:
raise AttributeError("Read‑only attribute")
Default Methods¶
Python provides default implementations for many lookup operations; understanding them helps you decide when to override.
Deleting Attributes¶
Override __delattr__ to control behavior when del obj.attr is used.
Custom Dir¶
Override __dir__ to customize what dir(obj) returns.
Slots¶
Using __slots__ can restrict attribute creation and save memory by preventing the creation of __dict__.
Index/Key Access¶
Python also decouples attribute vs. item access:
Attribute access: obj.attr
Item access: obj[key]
Implement __getitem__, __setitem__, and __delitem__ to define dictionary‑like behaviors.
Summary¶
This chapter shows how Python’s attribute system works under the hood and how you can customize attribute behaviors via descriptors and magic methods.
Chapter 11 – Single Inheritance¶
Overview¶
Inheritance enables you to build a class hierarchy where a subclass reuses and extends the behavior of a base class. This chapter explains how single inheritance works and how Python resolves attribute and method lookup. :contentReference[oaicite:1]{index=1}
Basic Inheritance¶
Define a subclass by specifying a base class in parentheses:
class Base:
def greet(self):
print("Hello from Base")
class Sub(Base):
pass
The subclass automatically inherits any public method or attribute.
Overriding Methods¶
A subclass can provide a replacement implementation:
class Sub(Base):
def greet(self):
print("Hello from Sub")
Using super()¶
The built‑in super() function makes it easy to delegate to a parent’s method without naming the base class explicitly:
class Sub(Base):
def greet(self):
super().greet()
print("…and hello from Sub!")
Python Method Resolution Order (MRO)¶
In single inheritance the lookup path is linear, and Python consults the class’s MRO when resolving attribute and method names.
Inheriting Instance Attributes¶
Subclass initializers (__init__) can extend the base class’s initialization:
class Sub(Base):
def __init__(self, value):
super().__init__()
self.value = value
Customizing Subclassing¶
You can control subclass behavior via metaclasses and custom hooks (for example, using __init_subclass__ to perform checks on subclass creation).
Metaclass Inheritance¶
When a class has a custom metaclass, inheritance still applies at the class object creation level; the metaclass’s behavior participates in constructing the subclass.
Summary¶
Single inheritance is straightforward: subclasses extend or override base class functionality and can use super() to coordinate with ancestor behavior.
Chapter 12 – Multiple Inheritance¶
Overview¶
Python supports multiple inheritance — a class can have more than one base. This chapter covers the motivations and the rules that govern method resolution in the presence of multiple parents. :contentReference[oaicite:2]{index=2}
Why Multiple Inheritance?¶
It lets you combine behavior from several sources. For example:
class A:
def method(self): print("A")
class B:
def method(self): print("B")
class C(A, B): pass
The question then becomes: which parent’s `method` should be called?
The Problem with Multiple Inheritance¶
Ambiguity arises when multiple parents implement the same attribute or method.
Linearization¶
Python uses a deterministic ordering called linearization to flatten the inheritance graph into a sequence. This ensures each class appears only once and respects parent order.
C3 Algorithm and the MRO¶
Python’s MRO is computed using the C3 linearization algorithm. It produces an order that preserves the local precedence ordering of base classes and their ancestors.
Cooperative super()¶
To make multiple inheritance work well, each class should use super() rather than naming the parent explicitly. This lets the next method in the MRO be called cooperatively.
Dwindling Parameter Pattern¶
When overriding methods with different signatures across the hierarchy, use *args/**kwargs to accept parameters flexibly.
Summary¶
Multiple inheritance is powerful but requires careful design; Python’s MRO ensures consistent lookup order when classes have multiple parents.
Chapter 13 – Class and Type¶
Overview¶
This chapter explores Python’s approach to type and how it relates to classes and instances. It clarifies what “type” means in Python and how strong vs. dynamic typing is realized. :contentReference[oaicite:3]{index=3}
Three Types of Type¶
There are multiple notions of type in programming:
The runtime type of an object (type(obj))
The static annotated type (type hints)
Conceptual type based on behavior
Primitive Data Types¶
Built‑in types like int, str, and float have fixed behaviors but are still Python objects.
Class‑Based Type¶
Every class defines a type. When you create a class, you create a new type.
Hierarchical Typing¶
Python’s inheritance hierarchy means types can participate in subclass relationships. For example:
isinstance(obj, Base)
Instance and Subclass¶
isinstance and issubclass help test whether an object belongs to a type or whether a class is derived from another.
Duck Typing¶
Python generally uses duck typing — if an object has the right methods and attributes, its type often doesn’t matter for correctness:
def quack(duck):
duck.quack()
As long as duck has a quack method, this works irrespective of actual type.
Summary¶
Python’s notion of type combines runtime objects, class definitions, and behavioral expectations rather than rigid compile‑time constraints.