By the end of this chapter, you should understand:
- What a class is in Python.
- What an instance is.
- Why classes are objects too.
- How class definitions create class objects.
- How instances are created from classes.
- What
selfmeans. - How
__init__initializes a new instance. - Why instance attributes belong to individual objects.
- Why class attributes belong to the class object.
- How classes connect to namespaces.
- How classes connect to the object/reference model from Volume I.
- How
type()andisinstance()help inspect objects. - Why object-oriented programming is about modeling behavior and state together.
- How this chapter prepares us for attributes, methods, inheritance, MRO, descriptors, and the Python data model.
Welcome to Volume II.
Volume I built the ground:
objects
references
mutability
functions
data structures
memory
modules
imports
namespaces
Volume II begins by asking:
How do we define our own kinds of objects?
That is what classes do.
A class lets you define a new kind of object.
An instance is one object created from that class.
This sounds simple.
But in Python, classes are not just syntax for grouping functions.
Classes are runtime objects.
Instances are runtime objects.
Methods are function objects connected through attribute lookup.
Inheritance, descriptors, properties, dataclasses, MRO, and metaclasses all build on this foundation.
So we will go slowly.
Not because classes are impossible.
Because classes are the doorway to Python's advanced object model.
A class defines a kind of object.
Example:
class User:
passThis creates a class object named User.
You can create an instance:
ada = User()Now:
User -> class object
ada -> User instance
Conceptually:
User ──▶ class object
ada ──▶ instance of User
The class is the template-like object.
The instance is an individual object created from the class.
But be careful with the word template.
In Python, a class is not only a static blueprint.
It is a live object with a namespace.
It can be passed around, stored in variables, inspected, modified, and used to create instances.
This is why Python's object model is so flexible.
Functions let us group behavior.
Dictionaries let us group data.
Classes let us group data and behavior together.
Suppose we represent a user with a dictionary:
user = {
"name": "Ada",
"email": "ada@example.com",
}We can write functions:
def display_name(user):
return user["name"].title()Use:
print(display_name(user))This works.
But as behavior grows, the connection between the data and the functions can become scattered.
Classes let us express:
this data and this behavior belong together
Example:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def display_name(self):
return self.name.title()Use:
user = User("Ada", "ada@example.com")
print(user.display_name())Now the object carries both:
state -> name and email
behavior -> display_name
Object-oriented programming begins there.
When Python executes a class statement, it creates a class object.
Example:
class User:
passThis is executable code.
It is not merely a declaration read by a compiler long before runtime.
Python executes the class body and creates a class object.
Then it binds the class name:
User -> class object
You can prove the class is an object:
class User:
pass
print(User)
print(type(User))Output looks like:
<class '__main__.User'>
<class 'type'>
The class User is itself an object.
Its type is type.
We will study metaclasses later.
For now, remember:
classes are objects that create instances
After defining:
class User:
passyou can call the class:
ada = User()This creates a new instance.
The name ada refers to that instance.
Check:
print(type(ada))Output:
<class '__main__.User'>
The instance's type is User.
Conceptually:
User() creates a new User instance
ada ──▶ User instance
Each call creates a separate instance:
ada = User()
grace = User()
print(ada is grace)Output:
False
Both are User instances.
They are not the same object.
This follows the identity model from Volume I.
The name of a class is a normal name binding.
Example:
class User:
passThis binds:
User -> class object
You can assign another name to the same class:
Person = UserNow:
User ──┐
▼
Person ─▶ class object
Use:
ada = Person()
print(type(ada))Output:
<class '__main__.User'>
The class object still has its original class name for representation.
But Person refers to the same class object.
This matters because classes obey the same reference rules as other objects:
- They can be assigned to names.
- They can be stored in containers.
- They can be passed to functions.
- They can be returned from functions.
- They can have attributes.
Python is consistent here.
Classes are first-class objects.
When you write:
user = User()you are calling the class object.
At a beginner level, class calling means:
create a new instance
initialize it
return it
This process involves advanced hooks:
__new__
__init__
metaclass __call__
We will study those later.
For now, the practical model is:
ClassName(arguments) -> new initialized instance
Example:
class User:
def __init__(self, name):
self.name = name
ada = User("Ada")The call:
User("Ada")creates an instance and runs initialization code.
The result is bound to ada.
__init__ is the initializer method.
Example:
class User:
def __init__(self, name, email):
self.name = name
self.email = emailWhen you call:
user = User("Ada", "ada@example.com")Python creates a new User instance and calls:
user.__init__("Ada", "ada@example.com")Conceptually.
Inside __init__, self refers to the new instance.
The assignments:
self.name = name
self.email = emailstore attributes on that instance.
After initialization:
print(user.name)
print(user.email)Output:
Ada
ada@example.com
__init__ prepares the object for use.
It should usually leave the object in a valid starting state.
Beginners often say:
__init__ creates the object
That is close enough for casual speech, but not technically correct.
More precise:
__init__ initializes an object that has already been created
Object creation involves __new__.
Initialization involves __init__.
Example:
__new__ -> create object
__init__ -> initialize object
Most classes do not need to define __new__.
Most classes define __init__.
For now, use this model:
User("Ada")
creates a User instance
passes it to __init__
returns the initialized instance
We will revisit creation and initialization when we study the data model and metaclasses.
self is the conventional name for the current instance.
Example:
class User:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, {self.name}"When you call:
ada = User("Ada")
ada.greet()inside greet, self refers to ada.
Conceptually:
self -> ada instance
self is not a keyword.
Python does not require the name self.
This works but is bad style:
class User:
def __init__(this_object, name):
this_object.name = nameUse self.
Every Python reader expects it.
Conventions are part of readability.
Look at:
class User:
def greet(self):
return "hello"Use:
user = User()
print(user.greet())It looks like greet is called with no arguments.
But the method definition expects one parameter:
def greet(self):What happened?
When you access a function through an instance:
user.greetPython creates a bound method.
That bound method remembers:
function -> User.greet
instance -> user
When you call:
user.greet()Python passes the instance as the first argument automatically.
Conceptually:
User.greet(user)This is a key piece of Python's object model.
We will study methods more deeply in Chapter 44.
Instance attributes are attributes stored on individual instances.
Example:
class User:
def __init__(self, name):
self.name = nameCreate two users:
ada = User("Ada")
grace = User("Grace")Each instance has its own name attribute:
ada instance namespace:
name -> "Ada"
grace instance namespace:
name -> "Grace"
Use:
print(ada.name)
print(grace.name)Output:
Ada
Grace
Same attribute name.
Different instance namespaces.
This is exactly the namespace model from Chapter 41.
Classes make that model practical for custom objects.
For many normal Python objects, you can inspect instance attributes with vars().
Example:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Ada", "ada@example.com")
print(vars(user))Output:
{'name': 'Ada', 'email': 'ada@example.com'}
This shows the instance namespace.
At a simple level:
user.name is stored in user.__dict__["name"]
Do not rely on this for every object.
Some classes use __slots__ or custom attribute behavior.
But for ordinary beginner classes, vars(instance) is a clear way to see instance state.
This connects directly to namespaces.
Class attributes are attributes stored on the class object.
Example:
class User:
species = "human"
def __init__(self, name):
self.name = nameHere:
species = "human"is in the class body.
It becomes a class attribute.
Access:
print(User.species)Output:
human
Instances can also access class attributes:
ada = User("Ada")
print(ada.species)Output:
human
Python does not copy species into each instance.
It finds the attribute on the class when it is not found on the instance.
We will study attribute lookup in detail in Chapter 44.
Example:
class User:
role = "member"
def __init__(self, name):
self.name = nameCreate:
ada = User("Ada")
grace = User("Grace")Access:
print(ada.role)
print(grace.role)Both output:
member
Now:
ada.role = "admin"This creates an instance attribute on ada.
Now:
print(ada.role)
print(grace.role)
print(User.role)Output:
admin
member
member
Namespace explanation:
ada instance:
role -> "admin"
grace instance:
no role
User class:
role -> "member"
Same name.
Different namespaces.
Lookup rules decide what is found.
Mutable class attributes can surprise beginners.
Example:
class Team:
members = []
def __init__(self, name):
self.name = nameCreate:
red = Team("Red")
blue = Team("Blue")
red.members.append("Ada")
print(blue.members)Output:
['Ada']
Why?
members is stored on the class.
Both instances find the same list through the class.
Conceptually:
Team.members -> one shared list
red.members -> same list through class lookup
blue.members -> same list through class lookup
If each instance should have its own list, create it in __init__:
class Team:
def __init__(self, name):
self.name = name
self.members = []Now each instance gets its own list.
This is one of the most important beginner class mistakes.
A class body is executed when Python creates the class.
Example:
class Example:
print("inside class body")
value = 10When the class statement runs, it prints:
inside class body
The class body creates a namespace.
Names assigned in the class body become class attributes.
Example:
class Example:
value = 10
def show(self):
return self.valueClass namespace:
value -> 10
show -> function object
After the class object is created, the name Example is bound to it.
This is a runtime process.
It is not just static description.
When you define a function inside a class body:
class User:
def greet(self):
return "hello"the class namespace gets:
greet -> function object
You can inspect:
print(User.greet)You will see a function-like representation.
When accessed through an instance:
user = User()
print(user.greet)you see a bound method.
This transformation is part of Python's descriptor machinery.
We will study descriptors later.
For now:
function stored on class
access through instance
becomes bound method
That is why self is passed automatically.
Without classes:
def area(rectangle):
return rectangle["width"] * rectangle["height"]
rectangle = {"width": 10, "height": 5}
print(area(rectangle))With classes:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rectangle = Rectangle(10, 5)
print(rectangle.area())Now the area behavior lives with the rectangle object.
This can improve readability:
rectangle.area()means:
ask this rectangle for its area
Object-oriented design is not always better.
But it is powerful when data and behavior naturally belong together.
It is tempting to replace every dictionary with a class.
Do not.
Dictionaries are excellent for flexible key-value data.
Example:
settings = {
"theme": "dark",
"language": "en",
}This may not need a class.
Classes are useful when:
- Objects have meaningful behavior.
- Objects have consistent structure.
- You want clear construction rules.
- You want methods attached to data.
- You want type-based behavior.
- You want inheritance or protocols later.
Dictionary:
flexible mapping of keys to values
Class:
defined kind of object with state and behavior
Choose based on the problem.
Professional Python uses both.
When you define:
class User:
passyou create a new type of object.
Check:
user = User()
print(type(user))Output:
<class '__main__.User'>
The class User is the type of user.
You can ask:
print(isinstance(user, User))Output:
True
This means:
user is an instance of User
Types are central to Python's runtime behavior.
Even though Python is dynamically typed, objects still have types.
Dynamic typing means names do not have fixed declared types.
It does not mean objects lack types.
type() returns the type of an object.
Examples:
print(type(10))
print(type("Ada"))
print(type([]))Output:
<class 'int'>
<class 'str'>
<class 'list'>
For custom classes:
class User:
pass
user = User()
print(type(user))Output:
<class '__main__.User'>
Use type() for inspection.
But do not overuse exact type checks in design.
Later we will study polymorphism and duck typing.
Often, what an object can do matters more than its exact type.
For now, type() helps us see the class-instance relationship.
isinstance() checks whether an object is an instance of a class or a compatible class through inheritance.
Example:
class User:
pass
user = User()
print(isinstance(user, User))Output:
True
With built-in types:
print(isinstance(10, int))
print(isinstance("Ada", str))
print(isinstance([], list))Output:
True
True
True
isinstance() is more flexible than:
type(obj) is SomeClassbecause isinstance() understands inheritance.
We will study inheritance soon.
For now, remember:
isinstance(obj, Class) asks whether obj belongs to that class family
A class can create many instances.
Example:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
book_one = Book("Python Basics", "Ada")
book_two = Book("Advanced Python", "Grace")Each instance has its own state:
book_one:
title -> "Python Basics"
author -> "Ada"
book_two:
title -> "Advanced Python"
author -> "Grace"
Use:
print(book_one.title)
print(book_two.title)Output:
Python Basics
Advanced Python
This is one of the main reasons classes exist.
They let us create many objects with the same structure and behavior but different state.
Methods are shared through the class.
Example:
class Book:
def __init__(self, title):
self.title = title
def describe(self):
return f"Book: {self.title}"Create:
a = Book("Python Basics")
b = Book("Advanced Python")Each instance has its own title.
But the method function describe lives on the class.
Conceptually:
Book class:
describe -> function object
a instance:
title -> "Python Basics"
b instance:
title -> "Advanced Python"
When called:
a.describe()
b.describe()the same method behavior runs with different self objects.
This is efficient and expressive.
State varies by instance.
Behavior is usually defined once on the class.
Instance state is the data stored on a particular instance.
Example:
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1Use:
a = Counter()
b = Counter()
a.increment()
a.increment()
b.increment()
print(a.value)
print(b.value)Output:
2
1
Both objects are counters.
Each has its own value.
State is not shared unless you explicitly store it somewhere shared.
This is the heart of instance-based object-oriented programming:
same class
same behavior
different instance state
__init__ should usually create a valid starting state.
Bad:
class User:
def __init__(self, name):
self.name = nameMaybe this is incomplete if every user must have an email too.
Better:
class User:
def __init__(self, name, email):
if name == "":
raise ValueError("name cannot be empty")
if email == "":
raise ValueError("email cannot be empty")
self.name = name
self.email = emailNow a User object cannot be created without required information.
This is one advantage of classes over loose dictionaries.
Construction can enforce rules.
An object should not have to wander through the program half-formed.
Initialize it clearly.
Example:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def describe(self):
return f"{self.owner}: {self.balance}"Use:
account = BankAccount("Ada", 100)
account.deposit(50)
print(account.describe())Output:
Ada: 150
Methods can read and modify instance state through self.
The method does not need the account passed explicitly:
deposit(account, 50)Instead:
account.deposit(50)The object is the receiver of the operation.
That is object-oriented style.
Sometimes a class only holds data:
class Point:
def __init__(self, x, y):
self.x = x
self.y = yThis can be fine.
But if a class only stores data and has no behavior, ask whether another structure is better:
- A tuple.
- A dictionary.
- A dataclass.
- A named tuple.
- A simple object class.
Later, Chapter 51 will study dataclasses.
For now, writing simple classes by hand is useful because it teaches the object model.
But professional Python often uses dataclasses for structured data.
Classes are not only for data.
They shine when they express behavior, invariants, and relationships.
Class names usually use CapWords.
Examples:
class User:
pass
class BankAccount:
pass
class JsonParser:
passFunction and variable names usually use lowercase with underscores:
def create_user():
...
user_name = "Ada"The naming difference helps readers.
When you see:
User()you expect a class being called to create an instance.
When you see:
create_user()you expect a function.
Naming conventions make code easier to scan.
An empty class can be written with pass:
class Marker:
passThis creates a class object.
You can create instances:
marker = Marker()You can attach attributes:
marker.name = "important"This works because ordinary instances can have dynamic attributes.
But empty classes are not common in well-designed application code unless they serve a clear purpose.
They may be used for:
- Simple markers.
- Experiments.
- Tests.
- Dynamic attribute containers.
- Teaching.
In real design, a class usually defines meaningful initialization or behavior.
Ordinary Python objects can often receive new attributes after creation.
Example:
class User:
pass
user = User()
user.name = "Ada"
user.email = "ada@example.com"This flexibility is powerful.
It can also become messy.
If every part of the program adds attributes whenever it wants, objects become hard to reason about.
Better:
class User:
def __init__(self, name, email):
self.name = name
self.email = emailNow the expected attributes are visible at construction.
Dynamic attributes are part of Python's flexibility.
Clear initialization is part of Python's maintainability.
Use both with judgment.
Classes usually live in modules.
Example:
users.py:
class User:
def __init__(self, name):
self.name = namemain.py:
from users import User
ada = User("Ada")Namespace model:
users module namespace:
User -> class object
main module namespace:
User -> same class object
ada -> User instance
The class object can be imported like any other object.
This connects classes back to modules and namespaces.
Classes do not live in a separate universe.
They are objects bound to names in namespaces.
In larger projects, classes are often organized inside package modules.
Example:
app/
__init__.py
users/
__init__.py
models.py
app/users/models.py:
class User:
def __init__(self, name):
self.name = nameImport:
from app.users.models import UserUse:
ada = User("Ada")Full namespace path:
app.users.models.User
This tells us:
application package
users package
models module
User class
Good package organization makes class locations predictable.
Classes and instances are objects in memory.
Example:
class User:
def __init__(self, name):
self.name = name
ada = User("Ada")Conceptual graph:
module namespace:
User ──▶ class object
ada ──▶ instance object
instance object:
name ──▶ "Ada"
class object:
__init__ ──▶ function object
The instance knows its class.
The class stores methods.
The module stores the class name.
The instance stores per-object state.
All the Volume I ideas still apply:
- Names.
- References.
- Objects.
- Namespaces.
- Memory.
- Attribute lookup.
Classes combine those ideas.
Suppose we want to model a task.
Class:
class Task:
def __init__(self, title):
if title.strip() == "":
raise ValueError("title cannot be empty")
self.title = title.strip()
self.done = False
def complete(self):
self.done = True
def display(self):
marker = "x" if self.done else " "
return f"[{marker}] {self.title}"Use:
task = Task("learn classes")
print(task.display())
task.complete()
print(task.display())Output:
[ ] learn classes
[x] learn classes
What the class defines:
construction rule:
title cannot be empty
state:
title
done
behavior:
complete
display
This is a good beginner example of object-oriented design.
The object owns state and exposes behavior that changes or reads that state.
Class:
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, name, price):
self.items.append({"name": name, "price": price})
def total(self):
amount = 0
for item in self.items:
amount += item["price"]
return amountUse:
cart = ShoppingCart()
cart.add_item("Book", 30)
cart.add_item("Pen", 5)
print(cart.total())Output:
35
Why self.items = [] belongs in __init__:
each cart should have its own list
If items were a mutable class attribute, carts would accidentally share one list.
This example reinforces:
per-instance mutable state belongs on the instance
Class:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def fahrenheit(self):
return self.celsius * 9 / 5 + 32
def is_freezing(self):
return self.celsius <= 0Use:
today = Temperature(12)
print(today.fahrenheit())
print(today.is_freezing())Output:
53.6
False
This class groups:
state:
celsius
behavior:
fahrenheit conversion
freezing check
Could this be simple functions?
Yes.
Example:
def fahrenheit(celsius):
return celsius * 9 / 5 + 32For a tiny conversion, a function may be enough.
Classes become more useful when the object has multiple operations, invariants, or relationships.
Do not force classes where functions are clearer.
Object-oriented programming can sound grand.
At the beginning, think simply:
What kind of thing am I modeling?
What data does one thing need?
What behavior belongs to that thing?
How should one thing be initialized?
What should be impossible for this thing?
Example:
Task
data:
title
done
behavior:
complete
display
rules:
title cannot be empty
That is enough to start a class.
Do not begin with inheritance.
Do not begin with patterns.
Do not begin with metaclasses.
Begin with:
state + behavior + rules
The advanced machinery exists to support good models, not replace them.
Bad:
class User:
def greet():
return "hello"Use:
user = User()
user.greet()This raises an error because Python passes the instance automatically, but the method does not accept it.
Correct:
class User:
def greet(self):
return "hello"Remember:
instance.method()
conceptually passes the instance as the first argument.
That first parameter is conventionally called self.
Bad:
class User:
def __init__(self, name):
self.name = name
return self__init__ should return None.
Do not return the instance from __init__.
Python handles returning the new object from the class call.
Correct:
class User:
def __init__(self, name):
self.name = nameIf __init__ explicitly returns a non-None value, Python raises an error.
Mental model:
__init__ initializes
class call returns the instance
Bad:
class Notebook:
notes = []
def add(self, note):
self.notes.append(note)Use:
a = Notebook()
b = Notebook()
a.add("one")
print(b.notes)Output:
['one']
Both notebooks share the class-level list.
Correct:
class Notebook:
def __init__(self):
self.notes = []
def add(self, note):
self.notes.append(note)Now each notebook has its own notes list.
Rule:
mutable per-object data belongs in __init__
shared class-level data belongs on the class
Not every problem needs a class.
Good function:
def slugify(text):
return text.lower().replace(" ", "-")Unnecessary class:
class Slugifier:
def slugify(self, text):
return text.lower().replace(" ", "-")If there is no meaningful state, no clear object identity, and no group of related behavior, a function may be clearer.
Classes are powerful.
Functions are powerful too.
Professional Python is not "classes everywhere."
It is choosing the simplest structure that expresses the idea well.
__init__ should initialize the object.
It should not usually perform surprising heavy work.
Risky:
class Report:
def __init__(self):
self.data = download_large_dataset()
send_tracking_email()
connect_to_database()Now creating a Report object does network work, sends email, and opens a database connection.
Sometimes constructors need resources.
But be deliberate.
Better:
class Report:
def __init__(self, data):
self.data = dataThen:
data = download_large_dataset()
report = Report(data)Clear construction is easier to test and reason about.
Example:
class User:
passUser is the class.
ada = User()ada is an instance.
This fails:
User.nameunless the class has a class attribute named name.
This works after initialization:
ada.nameif the instance has an instance attribute named name.
Class:
defines shared behavior and class-level data
Instance:
individual object with per-object state
Keep the distinction sharp.
It will matter even more when we study inheritance and descriptors.
You may see:
class User:
name: str
email: strThis declares annotations.
It does not automatically create instance attributes.
This will fail:
user = User()
print(user.name)unless something assigned self.name.
Correct initialization:
class User:
name: str
email: str
def __init__(self, name: str, email: str):
self.name = name
self.email = emailAnnotations describe expected types.
Assignments create bindings.
This distinction will matter when we study static typing and dataclasses.
Classes are the root of many advanced topics.
Attributes and methods:
how names are found on objects and classes
Encapsulation and properties:
how access to attributes can be managed
Inheritance:
how classes reuse and specialize behavior
MRO:
how Python decides where inherited methods come from
Duck typing:
how behavior can matter more than exact class
Descriptors:
how attribute access can be customized deeply
Dunder methods:
how objects participate in Python syntax and protocols
Metaclasses:
how classes themselves are created and controlled
Everything starts with:
class object
instance object
attribute lookup
self
This chapter is the foundation.
- Create a class:
class User:
passCreate two instances:
ada = User()
grace = User()Check:
print(ada is grace)
print(type(ada))
print(isinstance(ada, User))Explain each result.
- Write a
Bookclass withtitleandauthorinstance attributes.
Create two books.
Print their titles.
Explain why each book has its own title.
- Write a
Counterclass:
counter = Counter()
counter.increment()
counter.increment()
print(counter.value)The output should be:
2
Use __init__ to initialize value.
- Explain the difference between:
class User:
role = "member"and:
class User:
def __init__(self):
self.role = "member"Where is role stored in each case?
- Fix this class:
class Team:
members = []
def add(self, member):
self.members.append(member)Why is the original version dangerous?
- Explain what
selfrefers to here:
class Task:
def __init__(self, title):
self.title = title
def complete(self):
self.done = TrueWhat object is self when you call:
task.complete()- Why should
__init__not returnself?
What is the role of __init__?
- Create a
Rectangleclass with:
widthheightarea()perimeter()
Then create two rectangles and show that each has different state but shared behavior.
- Explain why this annotation does not create an attribute:
class User:
name: strWhat line actually creates the instance attribute?
- In your own words, explain:
classes are objects
instances are objects
classes create instances
Use a diagram with arrows from names to objects.
In this chapter we learned:
- A class defines a kind of object.
- A class statement creates a class object.
- A class name is a normal name bound to a class object.
- Calling a class creates a new instance.
- Instances are individual objects created from classes.
__init__initializes a new instance.selfconventionally refers to the current instance.- Instance attributes belong to individual instances.
- Class attributes belong to the class object.
- Mutable per-instance state should usually be created in
__init__. - Methods are functions stored on classes and bound to instances when accessed through instances.
type()shows an object's type.isinstance()checks whether an object is an instance of a class family.- Classes connect state, behavior, and rules.
- Classes are part of the same object/reference/namespace model from Volume I.
Core model:
module namespace:
User -> class object
ada -> instance object
class object:
methods
class attributes
instance object:
per-object attributes
Object-oriented model:
class:
defines structure and behavior
instance:
carries individual state
self:
the instance currently receiving a method call
This is the base layer for advanced Python.
Everything that follows in Part I builds on this.
Next we study attributes and methods in depth.
This chapter introduced instance attributes, class attributes, and methods.
Chapter 44 explains how Python finds them.
We will study:
- Attribute lookup order.
- Instance namespaces.
- Class namespaces.
- Method binding.
- Bound methods.
- How
selfis passed. - Why class attributes can be read through instances.
- Why instance attributes can shadow class attributes.
- How attribute assignment differs from attribute lookup.
- How this prepares us for properties and descriptors.
The transition is direct:
classes create objects
attributes and methods define how those objects expose names and behavior
Once attribute lookup is clear, inheritance, descriptors, properties, and MRO become much easier to understand.