Python Classes: Object-Oriented Programming (OOP) in Python

Learn to write your own classes in Python. Define a class with a constructor and attributes, create objects from it and give them methods that use and change their data safely. Make objects print clearly and compare with ==, sorting and + through special methods, build programs in which objects hold and call other objects, and judge when a class makes a program clearer than functions and dictionaries.

BeginnerLesson 10 of 10 in Python for Beginners: Introduction to Programming

What you will learn

  1. See that every Python value is an object with its own data and methods, then write a class of your own and create objects from it.
    • Recognize the objects you already use
      Describe strings, lists and dictionaries as objects that have a type, hold data and offer methods you call with dot notation.
    • Define a class and create instances
      Write a class statement, create several separate objects from it, and set and read their attributes with dot notation.
  2. Use a constructor to give every new object the attributes it needs, choose between instance and class attributes, and stop invalid objects from being created.
    • Write a constructor with __init__ and self
      Define __init__ so that each new object starts with its own attributes, taken from the arguments or set to starting values.
    • Choose instance attributes or class attributes
      Decide whether a value belongs to each object or to the class as a whole, and predict how Python finds and changes each kind.
    • Keep objects valid from the start
      Check the arguments a constructor receives, raise a clear exception for bad values, and keep the rules that make an object meaningful.
  3. Write methods that use and change an object's data, and protect that data from changes that would break the object.
    • Define and call instance methods
      Write methods that work with self, call them on objects, and return results instead of printing them.
    • Change an object's state with methods
      Write methods that update attributes while keeping the object valid, and predict when a change is visible through other names for the same object.
    • Protect attributes with naming conventions and properties
      Mark attributes as internal with an underscore, and use properties to check new values or to compute values that stay up to date.
  4. Write special methods that let your objects show readable text, compare with == and <, and work with operators and built-in functions.
    • Give objects readable string representations
      Write __str__ and __repr__ so that objects show useful text when printed, inspected or stored in a list.
    • Compare and combine objects with operators
      Define __eq__, __lt__, __add__ and __len__ so that your objects work with ==, sorting, + and len(), and decide when an operator is clearer than a named method.
  5. Combine objects so that each one handles its own data, trace programs where objects call each other, and decide when a class makes a program clearer.
    • Build objects that contain other objects
      Store objects as attributes or in lists inside another object, and write methods that work across the whole collection.
    • Make objects interact through methods
      Pass objects to methods and functions, return new objects, and trace a short program in which several objects work together.
    • Decide when a class makes a program clearer
      Judge when bundling data and behavior in a class helps, plan a small class from a description, and rewrite dictionary-based code as a class.

Python Classes and OOP study guide

5 chapters

1

Understand objects and define your first class

Objects already appear in every Python program you write: a string has methods, a list changes its contents, and a dictionary answers lookups. Reading those familiar operations as object behavior gives you a starting point for classes of your own. Learn how types, identity, and shared references affect a program, then define a small class and give separate instances their own attributes.

1.1 Recognize the objects you already use

You have already used objects whenever you have worked with a number, string, list, or dictionary. An object has a type, a value or state, and operations it supports. Even functions and None are objects. Writing your own class extends this familiar system.

Find an object's type and methods

type("hello") returns <class 'str'>; type([1, 2]) returns <class 'list'>. The words type and class describe the same role here. isinstance(3.5, float) is True, while isinstance("3.5", float) is False: the second value is text, even though it looks numeric.

A method is a function provided by a type. In "ada lovelace".title(), the string before the dot is the object, title is the method, and the parentheses call it. The result is "Ada Lovelace". dir(value) lists names offered by an object; names such as __len__ are special methods that connect objects to Python operations such as len(value).

Track the result and the original

A method call can return a result, change an object, or do both. Check the method's behavior before deciding what to assign.

word = "level"
print(word.count("l"))  # 2
print(word.upper())     # LEVEL
print(word)             # level

nums = [3, 1, 2]
result = nums.sort()
print(nums)             # [1, 2, 3]
print(result)           # None

Strings are immutable: their contents cannot change. upper() returns another string, so word keeps its original value. To keep the uppercase result under that name, write word = word.upper().

Lists are mutable. sort() changes the list in place and returns None. Writing nums = nums.sort() loses the list reference held by nums and assigns None. Use nums.sort() on its own line, or nums = sorted(nums) to get a new sorted list. For a dictionary, {"a": 1}.get("b", 0) returns 0 because the requested key is missing.

Parentheses matter too. word.upper is a method object; word.upper() runs it. The same behavior can be offered by many objects: "cat".upper() and "dog".upper() use one kind of method with different string data.

Separate equality from identity

Equality asks whether values match. Identity asks whether two names refer to one object.

a = [1, 2]
b = [1, 2]
c = a
print(a == b)  # True
print(a is b)  # False
print(c is a)  # True
c.append(3)
print(a)       # [1, 2, 3]
print(b)       # [1, 2]

a and b start with equal contents but are separate lists. c = a creates an alias, another name for the same list. Appending through c changes the object that a also refers to. It does not change the separate list held by b. When tracing a program, draw one box per object and an arrow from each name to its box.

Key Takeaways

  • Every Python value is an object with a type, state, and supported operations.
  • Call a method with parentheses; a method reference alone does not run it.
  • Check whether a method returns a new value or changes the existing object.
  • == compares values; is compares identity.
  • Assignment can give two names references to the same mutable object.

1.2 Define a class and create instances

A class defines a type you can use in your own program. Each object made from it is an instance. One class describes the kind of thing; instances hold the data for particular things. A blank form and its filled-in copies make a useful comparison.

Write a class and call it

A class statement has the keyword class, a name, a colon, and an indented body. pass supplies an empty body while you try out the structure.

class Book:
    pass

b1 = Book()
b2 = Book()
print(b1 is b2)           # False
print(isinstance(b1, Book))  # True

Each Book() call makes a separate instance. In a script, type(b1) shows <class '__main__.Book'>. The class statement must run before code tries to use Book. You can also define a class in a module and import it with from library import Book, then create instances in the calling program.

Follow Python's naming conventions: Book, BankAccount, and ShoppingCart use CapWords, with each word capitalized. Variables and methods use lowercase words separated by underscores, such as book_count and add_item. Use four spaces for each level of indentation.

Store data on each instance

An attribute is a named value reached with a dot. Assigning an attribute stores it; reading the same name retrieves it.

b1.title = "Dune"
b2.title = "Emma"
b1.pages = 412
print(b1.title)  # Dune
print(b2.title)  # Emma
print(b1.pages)  # 412

The data belongs to the selected instance. Giving b1 a pages attribute does not give one to b2. Reading b2.pages now raises AttributeError, because that object has no attribute named pages.

Assignments can create new names without checking what you meant. After b1.titel = "X", the original b1.title still holds "Dune"; the object has gained a second attribute with a misspelled name. If a value seems not to change, compare the spelling where it was assigned and where it was read.

Keep the class and its instances distinct
ExpressionMeaning
BookThe class object, defining the type
Book()A call that creates an instance
b1.titleThe title stored on this book
Book.titleAn attribute requested on the class itself

b = Book gives the class another name. It does not create a book. If titles were set only on instances, asking for Book.title raises AttributeError; there is no single title shared by the type.

Give every object a dependable start

Setting attributes from outside is possible, but it leaves setup scattered across the program. One book might receive a title and page count while another receives only a title. Later code cannot safely assume that both have pages.

A setup method called __init__ places the starting assignments inside the class. Each creation call then follows the same setup process. The class can require a title and create other attributes with sensible initial values, so calling code does not have to remember a separate sequence of assignments.

Key Takeaways

  • A class defines a type; an instance is one object made from it.
  • Use ClassName() to create an instance and dot notation to reach its attributes.
  • Each instance can hold different data under the same attribute names.
  • A missing attribute raises AttributeError; a misspelled assignment can create a new attribute.
2

Set up objects with constructors and attributes

An object needs a dependable starting state before its methods can do useful work. Initialization connects the arguments in a class call to attributes stored on the new instance. Choose which values belong to one object and which belong to the class, then validate inputs before accepting them. Tracing lookup and setup failures will help you spot accidental sharing and missing data.

2.1 Write a constructor with __init__ and self

Every newly created account needs an owner and a starting balance. Putting that setup in __init__ gives each object the attributes that later methods expect. Python creates the instance first, then calls __init__ to initialize it.

Connect arguments to attributes
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
        self.transactions = []

acct = BankAccount("Maya", 50)
other = BankAccount("Leo")

For the first call, Python passes the new account as self, "Maya" as owner, and 50 as balance. The assignments make acct.owner equal "Maya" and acct.balance equal 50. The second call uses the default balance, so other.balance is 0.

self is the parameter that receives the current object. It is a convention, not a Python keyword. Use this name so readers can recognize the object immediately. Callers leave it out: BankAccount("Maya", 50) supplies two arguments, not three.

Keep local names and stored data separate

In self.owner = owner, the name on the right is a local parameter. The name on the left is an attribute stored on the object. When initialization ends, other methods can still use self.owner.

Writing balance = balance only reassigns the local name. It creates no attribute, so reading acct.balance later fails. Put self. before each attribute that should remain part of the object.

Plan initialization by listing what each instance needs. Required values become parameters after self; optional values can have defaults. Values that always start the same way can be assigned in the body, such as self.visits = 0. A list created by self.transactions = [] is made anew on every call.

Avoid a shared default list

A default argument is evaluated when Python defines the method. With def __init__(self, items=[]), calls that omit items receive the same list. If the initializer stores that list directly, changing one object's items changes the items seen by another.

Use None as the default and create a fresh list during each call:

class Cart:
    def __init__(self, items=None):
        self.items = [] if items is None else list(items)

a = Cart()
b = Cart()
a.items.append("pen")
print(b.items)  # []

list(items) also makes a new outer list when a caller supplies one. The objects inside that list are still shared references; creating the list does not recreate every item.

Read setup errors

BankAccount() is missing the required owner argument and raises TypeError. Check the call against the parameters after self.

The spelling __init__ has two underscores on each side. _init_ is an ordinary method name and will not perform this setup automatically. __init__ must return None, so normally leave out a return statement. Returning a balance raises TypeError; the class call itself supplies the new instance to the caller.

Key Takeaways

  • __init__ initializes the instance after Python creates it.
  • Python supplies self; callers supply the remaining arguments.
  • self.name = name stores a parameter as an instance attribute.
  • Create per-instance lists inside initialization; use None instead of a mutable default.
  • __init__ must return None.

2.2 Choose instance attributes or class attributes

Some values describe one object; others describe the type as a whole. A pizza's size belongs to that pizza. A topping limit or a count of pizzas created can belong to the class. Where an assignment occurs determines which kind of attribute it creates.

Put the value in the right place
class Pizza:
    MAX_TOPPINGS = 5
    count = 0

    def __init__(self, size):
        self.size = size
        self.toppings = []
        Pizza.count += 1

p1 = Pizza("large")
p2 = Pizza("small")

size and toppings are instance attributes, set on self. Each pizza has its own values. MAX_TOPPINGS and count are class attributes, assigned in the class body outside a method. Capital letters mark a value intended as a constant; they do not prevent reassignment.

After these calls, Pizza.count is 2. Every initializer updates the same class attribute. p1.size is "large", while p2.size is "small".

Trace lookup and shadowing

For ordinary data attributes such as these, reading p1.MAX_TOPPINGS first checks the instance and then the class. Since no value is stored on p1, Python finds Pizza.MAX_TOPPINGS, which is 5.

Assignment through an instance has a different effect:

p1.MAX_TOPPINGS = 8
Pizza.MAX_TOPPINGS = 6
print(p1.MAX_TOPPINGS)     # 8
print(p2.MAX_TOPPINGS)     # 6
print(Pizza.MAX_TOPPINGS)  # 6

The first line creates an instance attribute on p1. It shadows the class value for that object. Changing the class value later reaches p2, which still reads from the class, but does not replace p1's own value.

AssignmentWhat changes
self.size = sizeData on the current instance
Pizza.count += 1The shared class counter
p1.MAX_TOPPINGS = 8An instance value hiding the class value
Watch two forms of accidental sharing

If the initializer used self.count += 1, Python would read the class's 0, add one, and store an instance attribute. Each new pizza would receive its own count of 1, while Pizza.count stayed 0. Name the class when updating a class-wide counter.

A list in the class body causes a different problem. With toppings = [], instances that have no separate toppings attribute all find the same list. p1.toppings.append("olives") mutates that shared list; it does not assign a new instance attribute. Put self.toppings = [] in __init__ when each pizza needs its own toppings.

Inspect an object's own data

For the original p2, vars(p2) gives {'size': 'small', 'toppings': []}. The class values are absent because they live on Pizza. This is useful when debugging a surprising lookup: check whether the instance has its own value, then inspect the class value. A shared setting belongs on the class; a name, size, balance, or personal collection belongs on the instance.

Key Takeaways

  • Instance attributes hold data for one object; class attributes hold shared settings or class-wide data.
  • An instance data attribute can shadow a class attribute of the same name.
  • Update a shared counter through the class name.
  • Mutating a class-level list changes the one shared list.
  • vars(obj) shows its own stored attributes, not the class attributes it can find.

2.3 Keep objects valid from the start

An object should start in a state its methods can use. A rectangle with a negative width cannot represent the intended shape. A rule that must remain true for every object of a class is an invariant. Validation checks those rules before accepting data.

Check before storing
class Rectangle:
    def __init__(self, width, height):
        if width <= 0:
            raise ValueError("width must be positive")
        if height <= 0:
            raise ValueError("height must be positive")
        self.width = width
        self.height = height

Rectangle(3, 4) passes both checks and returns an initialized object. Rectangle(-2, 4) raises ValueError at the first check. The later assignments never run. If the program does not catch that exception, execution stops there.

Suppose r already refers to a valid rectangle. In r = Rectangle(-2, 4), the right-hand call fails before assignment. r keeps its previous value. If r had no value before the failed call, the assignment does not create one.

Choose a useful exception
ExceptionProblem it describes
ValueErrorAn acceptable kind of input has an unacceptable value
TypeErrorThe input has the wrong type for the operation

A negative numeric width is a value error. Text supplied where a number is required is a type error. The comparison with 0 in this example already raises TypeError for text. A class can also check a type explicitly with isinstance when it needs a clearer message.

Write messages that name the rule, such as "width must be positive". A message saying only "bad input" leaves the caller guessing which value needs repair.

Let calling code handle recovery
while True:
    try:
        width = float(input("Width: "))
        room = Rectangle(width, 3)
        break
    except ValueError as error:
        print("Try again:", error)

Entering abc fails during float; entering -2 passes conversion but fails in Rectangle. Both raise ValueError, so this loop handles both. The class states its data rule, while the calling code decides to ask again.

Printing a warning inside __init__ does not reject the call. If execution continues, the program can receive an invalid object and fail later when using it. Raising an exception stops the unsuccessful setup where the problem occurs.

Clean data deliberately and maintain the rule

A documented cleanup can be useful. self.name = name.strip() removes surrounding spaces; validate the cleaned name if blank names are forbidden. self.items = list(items) makes a separate outer list, so appending to the caller's list does not append to this one. Changing an invalid width to its absolute value would change the caller's meaning, so it needs an explicit design rule.

Initialization checks run at creation time. Plain assignment such as r.width = -5 still bypasses them later. Methods that change a width must check it again. A property can place a check on attribute assignment itself, allowing the same rule to run during setup and later updates.

Key Takeaways

  • An invariant is a rule that valid object state must satisfy.
  • Validate before accepting data and raise a clear exception for a failure.
  • A failed creation call does not assign a new object to the caller’s name.
  • Constructor checks also need support in later methods or properties that change state.
3

Give objects behavior with instance methods

Methods turn stored data into useful behavior. A rectangle can calculate its area, and an account can update its balance while recording a transaction. Follow how Python passes the receiving object into each method, then distinguish a returned result from a change in state. Properties let a class keep readable attribute access while checking assignments or computing values on demand.

3.1 Define and call instance methods

An instance method is a function defined inside a class that works with the object passed as self. The object supplies its stored data; extra parameters supply any additional information needed for the action.

Read attributes through self

Add these methods inside the validated Rectangle class, at the same indentation level as __init__:

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

    def is_square(self):
        return self.width == self.height

With r = Rectangle(3, 4), r.area() returns 12, r.perimeter() returns 14, and r.is_square() returns False. Each method reads the same object's attributes. A second rectangle uses the same method definitions with its own width and height.

Inside a method, self.width names an attribute. A bare width names a local or other accessible variable; Python does not automatically search the current object for it. If no such variable exists, return width * height raises NameError. Writing self. makes the intended data owner explicit.

Follow the call

For this class, r.area() works like Rectangle.area(r). Python finds the function on the class and supplies r as its first argument. A method reference such as r.area is a bound method: it remembers the function and this rectangle. Adding () performs the call.

If a method is defined as def resize(self, factor), call it as r.resize(2). r fills self, and 2 fills factor. You do not pass r again. If an instance method is accidentally written as def area():, a call on r raises TypeError because Python still supplies the object to a function that accepts no arguments.

One method can use another through self:

    def double_area(self):
        return self.area() * 2

Here self.area() calculates using the same rectangle. Leaving out self. would ask for a separate function called area, not the object's method.

Return results for the caller

A calculation method should normally return its result. Returning gives calling code a value to store, combine, test, or display:

r1 = Rectangle(3, 4)
r2 = Rectangle(2, 5)
total = r1.area() + r2.area()
print(total)  # 22

If area only printed its answer, each call would return None. The addition would then fail with TypeError, even though two numbers had appeared on the screen. Printing is an action; returning supplies a value to the expression containing the call.

Use method names that show the task. deposit and resize name actions; is_square and has_room suggest Boolean checks. A method does not need parameters for data already stored on its object. It needs parameters for new inputs, such as a resize factor or a deposit amount.

Key Takeaways

  • Put self first in an instance method; Python supplies the receiving object.
  • Read attributes and call other methods through self..
  • Parentheses run a bound method.
  • Return calculated values so callers can combine, test, or display them.

3.2 Change an object's state with methods

An object's state is the current set of values in its attributes. An accessor reports on that state. A mutator changes it. A useful mutator checks its inputs and maintains every related attribute, so calling code does not have to coordinate several updates.

Validate the whole change first

Add these methods inside the BankAccount class with its owner, balance, and transaction list:

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("deposit must be positive")
        self.balance += amount
        self.transactions.append(("deposit", amount))

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("withdrawal must be positive")
        if amount > self.balance:
            raise ValueError("insufficient funds")
        self.balance -= amount
        self.transactions.append(("withdraw", amount))

A deposit changes the balance and records the event. A withdrawal checks both the amount's sign and the available balance before subtracting. These checks prevent a rejected withdrawal from changing either the balance or its history.

Starting with BankAccount("Maya", 50), depositing 25 gives 75, and withdrawing 30 gives 45. A later attempt to withdraw 100 raises ValueError; it leaves the balance at 45 and adds no transaction. Putting the subtraction before the funds check would change state even on failure.

Distinguish a change from a returned value

These mutators have no return statement, so they return None. After new_balance = acct.deposit(25), the deposit has happened, but new_balance is None. Read acct.balance afterwards to obtain the updated number.

A class can offer a different style of method that returns a new object. resize suggests changing a rectangle; resized can be chosen for a method that returns a new rectangle and leaves the original alone. Read the method's contract: the name is helpful, but its definition determines the behavior.

Trace shared references
a = BankAccount("Maya", 50)
b = a
b.deposit(10)
print(a.balance)  # 60
print(a is b)     # True

Assignment binds a name to an object; it does not copy that object. Both names reach the account whose balance changed. Passing a to a function also passes access to that object. A function calling account.withdraw(5) can therefore change the account seen by its caller.

For an independent account with the same owner and balance, create BankAccount(a.owner, a.balance). That constructor also supplies a fresh, empty history. It is a new account, not a complete historical copy.

copy.copy(a) creates a new outer object but keeps references to the same inner objects, including the transaction list. copy.deepcopy(a) copies nested objects too. When independence matters, check the objects inside the attributes as well as the outer instance.

Directly assigning acct.balance = -500 skips the checks in the two methods. Internal attribute conventions and properties help make the intended update path clear and keep validation close to state changes.

Key Takeaways

  • Check a requested change before updating attributes.
  • A mutator can change state while returning None.
  • Assignment and argument passing preserve references to the same object.
  • A shallow copy can still share nested mutable objects.
  • Keep related state updates together in the method responsible for them.

3.3 Protect attributes with naming conventions and properties

Calling code needs a clear way to read and change an object. Encapsulation keeps its data rules with the methods that manage them. Python uses conventions and properties to express that interface.

Mark internal storage

A leading underscore in _balance signals an internal attribute. Callers should use the account's methods, but Python still permits direct access. There is no private keyword that locks the value away.

A name such as __balance, with two leading underscores and no double trailing underscores, triggers name mangling. Inside class Acc, self.__balance is stored under _Acc__balance. Outside, obj.__balance fails, but obj._Acc__balance can still work. Mangling prevents name clashes; it does not secure sensitive data.

Run a method through attribute syntax

A property makes a method run when an attribute is read. The @property line is a decorator: it changes how Python exposes the method. A property without a setter permits reading but rejects assignment.

class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    @property
    def balance(self):
        return self._balance

After acct = Account("Maya", 50), read acct.balance with no parentheses. Assigning acct.balance = 10 raises AttributeError because this property has no setter. A deposit method inside the class could update _balance after its checks.

Check assignment with a setter
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not 0 <= value <= 100:
            raise ValueError("score must be between 0 and 100")
        self._score = value

The getter and setter share the name score; the stored value uses _score. On creation, self.score = score runs the setter.

For s = Student("Ana", 88), assigning s.score = 92 stores 92. Assigning s.score = -1 raises ValueError before storage, so the score stays 92. Student("Ana", 105) fails during initialization.

Inside the setter, self.score = value would call the setter again, repeating until RecursionError. The getter has the same risk if it returns self.score. Use the separate storage name _score in both bodies.

Compute values when they are read

A property can calculate a value from other attributes:

    @property
    def area(self):
        return self.width * self.height

Placed inside a rectangle class, this permits r.area. For width 3 and height 4, the value is 12. Changing the width to 5 makes the next read return 20. Storing an area once during initialization would leave an old value behind after a width change.

Start with a plain attribute when simple storage meets the need.

Key Takeaways

  • A leading underscore marks internal storage by convention; it does not block access.
  • Name mangling changes an attribute name to avoid clashes.
  • A property runs a getter through attribute syntax.
  • A setter can validate initialization and later assignments in one place.
  • Use a separate backing attribute to avoid calling the property recursively.
  • A computed property derives its value when read.
4

Use special methods so objects print and compare well

A useful object should fit naturally into ordinary Python expressions. Its special methods can supply readable text, value equality, an ordering, arithmetic results, or a length. Learn which method Python requests in each setting and what that method must return. Pay particular attention to displays inside containers, stable dictionary keys, and the difference between an operation and its reflected form.

4.1 Give objects readable string representations

An object is easier to inspect when it can describe its own data. Special methods let Python request that description through familiar actions such as print, str, and repr. Their names start and end with two underscores, often called dunder names.

Choose text for two audiences

__str__ supplies readable text for a program's users. __repr__ supplies a precise description for programmers. Both return strings; neither should print its result as a substitute for returning it.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(p)        # (3, 4)
print(repr(p))  # Point(3, 4)

str(p) returns the string "(3, 4)". repr(p) returns "Point(3, 4)". When possible, a representation resembles a class call that would recreate the value. It still is text: calling repr does not itself create another point.

Without a custom representation, a plain instance typically displays a class name and an address-like identifier. That helps distinguish objects but does not show their useful data. You rarely call these special methods directly; define them once and use Python's ordinary display operations.

Predict what a container displays
ExpressionRepresentation requested
print(p) or str(p)__str__
repr(p)__repr__
f"{p}"The usual string form for this class
f"{p!r}"__repr__
print([p])The list's display, using repr for its item

With the class above, print([Point(3, 4), Point(0, 0)]) shows [Point(3, 4), Point(0, 0)]. The list uses the representations of its elements, even though each element also has __str__.

If you define __repr__ alone, the default __str__ uses it too. Defining __str__ alone does not change repr or container displays. When adding just one display method, __repr__ therefore serves more inspection contexts.

Make string fields unambiguous

For a book that already stores title, author, and year, these methods give different useful views:

    def __str__(self):
        return f"{self.title} by {self.author} ({self.year})"

    def __repr__(self):
        return f"Book({self.title!r}, {self.author!r}, {self.year})"

For the book with title "Dune", author "Frank Herbert", and year 1965, the first method gives "Dune by Frank Herbert (1965)". The second gives "Book('Dune', 'Frank Herbert', 1965)". !r asks for the string fields' representations, including quotes.

A body containing only print(self.x) returns None, so str(p) fails with TypeError. Returning an integer fails too. Build the required text, then return that string.

Key Takeaways

  • __str__ supplies user-facing text; __repr__ supplies a useful programmer-facing representation.
  • Containers display their elements with repr.
  • __repr__ can supply the default string form; __str__ does not replace repr.
  • Both methods must return strings.
  • Use !r in an f-string to show quoted representations of string fields.

4.2 Compare and combine objects with operators

Special methods connect a class to operators and built-in functions. Define only operations with a clear meaning for that type: adding vectors is understandable; adding a player to a team is clearer as team.add_player(player).

Compare values and keep hashes safe

Two separate plain instances compare unequal by default. Add value equality to a point class with:

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

Now two points at (3, 4) compare equal even though their identities differ. in, count, index, and remove on lists can find such equal values. The usual != result follows from __eq__.

NotImplemented is a return value that lets Python try the other operand or a fallback. It is not an exception to raise. When neither operand supports value equality, == falls back to identity; a point and the string "3,4" compare unequal.

Defining __eq__ without __hash__ makes instances unhashable. They cannot then be set members or dictionary keys. Equal objects must have equal hashes, and a key's hash must stay stable. If the compared attributes never change, a matching hash can return hash((self.x, self.y)). Keep mutable value objects unhashable when their equality fields can change.

Define an ordering

__lt__ supplies <. A student class can compare scores with return self.score < other.score. Scores 88, 72, and 95 then sort into 72, 88, 95. min chooses the lowest; max chooses the highest by using > and its reflected < comparison.

With only __lt__, a > b can use b < a. Python does not infer <= or >= from that definition; those need their own methods. If an order is useful only for one task, pass a key function instead:

def by_score(student):
    return student.score

ordered = sorted(students, key=by_score)
Return new results from arithmetic

Inside a vector class with x and y attributes:

    def __add__(self, other):
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __mul__(self, factor):
        if not isinstance(factor, (int, float)):
            return NotImplemented
        return Vector(self.x * factor, self.y * factor)

    def __rmul__(self, factor):
        return self * factor

Vector(1, 2) + Vector(3, 4) produces a new vector (4, 6). Multiplying the first vector by 3 produces (3, 6), leaving it unchanged. __mul__ handles the vector on the left; __rmul__ supports a number on the left. Python does not assume the two orders mean the same thing.

Supply a meaningful length

For a playlist storing self.songs, define __len__ to return len(self.songs). The result must be a nonnegative integer. len(playlist) then counts songs. In this class, length zero also makes if playlist: false; a custom __bool__ would take priority over length when testing truth.

Key Takeaways

  • __eq__ defines value equality; return NotImplemented for unsupported operand types.
  • Equal hashable objects need equal, stable hashes.
  • __lt__ supports sorting but does not supply <= or >=.
  • Use a key function for a task-specific sort order.
  • Arithmetic methods can create new objects without changing the operands.
  • __rmul__ supports multiplication with the object on the right.
  • __len__ returns a nonnegative integer and can supply truthiness when no __bool__ exists.
5

Build programs from objects that work together

Larger programs connect objects with different jobs. A cart can ask items for subtotals; one account can ask another to receive a deposit. Trace the references and the sequence of calls so you can see which state changes and where an error stops execution. Then use those ideas to choose a clear design, plan a small class, and test its public behavior in manageable steps.

5.1 Build objects that contain other objects

An object's attributes can refer to other objects. A cart holds items, a library holds books, and a course holds students. Composition builds a larger object from smaller ones through this has-a relationship.

Give each object its own responsibility
class Item:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    def subtotal(self):
        return self.price * self.quantity

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add(self, item):
        if not isinstance(item, Item):
            raise TypeError("a cart holds Item objects")
        self.items.append(item)

    def total(self):
        total = 0
        for item in self.items:
            total += item.subtotal()
        return total

The cart owns a separate list created during initialization. Its add method checks the incoming object before appending it. cart.add("pen") raises TypeError instead of storing text that will fail when a subtotal is requested.

Create the items before or during the calls:

cart = ShoppingCart()
cart.add(Item("notebook", 2.50, 4))
cart.add(Item("pen", 1.25, 3))
print(cart.total())         # 13.75
print(cart.items[0].name)   # notebook

The subtotals are 10.0 and 3.75; the cart adds them to get 13.75. The chain cart.items[0].name first reaches the cart's list, then its first object, then that object's name.

Delegate work to the object that knows it

The cart asks each item for subtotal(). This is delegation: one object calls another object's method for part of a task. If a bulk discount changes how a subtotal is calculated, that rule belongs in Item.subtotal. The cart can keep adding the returned subtotals without duplicating price rules.

Passing Item instead of Item(...) passes the class, not a purchased item. The type check rejects that class object. Keep all additions on the checked path; direct access to cart.items.append(...) would bypass add.

Search for an object

Add this method inside ShoppingCart:

    def find(self, name):
        for item in self.items:
            if item.name == name:
                return item
        return None

The first match returns the existing item. No match returns None. Check found is None before using found.name; None has no such attribute. A __len__ method returning len(self.items) could also make len(cart) count stored item objects, while __str__ could join item lines into a receipt.

Track references inside collections

A list stores references, so placing the same item in two carts does not copy it. Updating that item's price affects the next total calculated by both carts. Separate cart lists prevent accidental list sharing, but they do not make the objects inside independent. Create separate items when the carts must hold independent data.

Key Takeaways

  • Composition stores objects inside another object.
  • Delegation asks a contained object to perform its part of a task.
  • Check an object before storing it when later methods require its type.
  • A search can return the matching object or None.
  • Separate containers can still refer to the same contained object.

5.2 Make objects interact through methods

Objects can be method arguments and return values just like numbers or strings. Track the receiver, arguments, and result separately: a method may change an existing object, return a new object, or simply report a value.

Use another object as input

Inside a point class with x and y, a method can read both points:

    def midpoint(self, other):
        return Point((self.x + other.x) / 2,
                     (self.y + other.y) / 2)

For p = Point(3, 4) and q = Point(1, 0), middle = p.midpoint(q) creates a third point at (2.0, 2.0). During the call, self refers to p and other refers to q. The method calculates coordinates and constructs a new point; it assigns nothing to either original.

With import math, a distance method can return math.hypot(self.x - other.x, self.y - other.y). Point(0, 0).distance_to(Point(3, 4)) then gives 5.0. This call returns a number, while midpoint returns an object.

Let each receiver manage its changes

Inside an account class with checked withdraw and deposit methods:

    def transfer_to(self, other, amount):
        self.withdraw(amount)
        other.deposit(amount)

If Maya has 100 and Leo has 20, maya.transfer_to(leo, 30) first withdraws from Maya, leaving 70, then deposits into Leo, giving 50. In the first call, self is Maya's account. In the deposit call, its own self is Leo's account.

A later transfer of 500 fails in withdraw because Maya has only 70. Execution stops before deposit, so both balances keep their previous values. The order of the calls matters. Python does not undo an earlier state change if a later call raises; this short method depends on the receiver's deposit being able to accept the amount.

Calling other.deposit(amount) runs the receiving account's rules in one place. Directly changing other.balance would skip them.

Trace the objects, not just the variable names

Draw each instance as a box containing its attributes. Draw an arrow from each variable to its box. At a call, identify which box receives self and which boxes are passed as arguments. Update the affected box after each successful state-changing line. Two arrows to one box mean a change through either name reaches the same object.

A plain function can also work across objects. total_balance(accounts) can loop over accounts and add their balances. That whole-list calculation need not belong to a single account.

Represent an absent relationship

A book can start with self.borrower = None, then later store a member object. Before reading a member attribute, check the relationship:

if book.borrower is not None:
    print(book.borrower.name)

Otherwise book.borrower.name fails when the book has no borrower. Classes can be defined above a main() function that creates and connects the objects. Calling main() under if __name__ == "__main__": keeps those actions from running when the classes are imported.

Key Takeaways

  • Identify the receiver as self and the object argument as its own parameter.
  • A method can return a new object while leaving its inputs unchanged.
  • Calls happen in order; an exception stops later calls but does not undo earlier changes.
  • Use another object’s public methods to apply its rules.
  • Check for None before following an optional object relationship.
  • A function can organize calculations that span several objects.

5.3 Decide when a class makes a program clearer

A class earns its place when related data and repeated operations become easier to understand together. You do not need to turn every function or record into a class. Choose the structure that makes the program's responsibilities clear.

Look for data and behavior that travel together

Classes help when several functions keep accepting the same group of values, many records share a layout, and state changes must follow common rules. Several accounts that all support deposits and withdrawals are a good fit. A single calculation can stay a function. A fixed, read-only record can stay a tuple or dictionary.

A class with only setup and a one-time operation often adds more ceremony than useful structure. Ask what one instance represents and which repeated actions belong to it. If you cannot answer those questions simply, reconsider the design.

Move an operation next to its data

A dictionary-based account might begin as:

account = {"owner": "Maya", "balance": 50}

def deposit(account, amount):
    account["balance"] += amount

The class version can place the same operation beside its state and its rules:

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("deposit must be positive")
        self.balance += amount

    def __repr__(self):
        return f"BankAccount({self.owner!r}, {self.balance})"

After acct = BankAccount("Maya", 50) and acct.deposit(25), its representation is BankAccount('Maya', 75). The positive-deposit rule now runs whenever callers use that method. Reading account["balence"] would raise KeyError; reading acct.balence raises AttributeError. A class does not eliminate spelling mistakes, but it gives the data and its operations a named home.

Plan from a short description

For “A library lends books to members. A member can borrow at most 3 books,” nouns suggest Book, Member, and Library. Verbs suggest checkout and return methods.

  1. List what each instance knows: a book's title, author, and borrower; a member's name and borrowed books; a library's books and members.
  2. Identify rules: a book cannot already be on loan, and a member cannot exceed MAX_LOANS = 3.
  3. Put each rule in the operation that changes the relationship, such as Library.check_out(book, member).
  4. Write initialization and a useful representation, then add and test one action at a time.

Different titles need different instances of Book, not separate classes named after each title. Use singular nouns for individual objects, such as Book; name a collection for the collection, such as Library. Keep each class's job clear enough to describe in one sentence.

Test observable behavior
acct = BankAccount("Maya", 50)
acct.deposit(25)
assert acct.balance == 75

assert raises AssertionError if its condition is false. Test rejected actions too: catch the expected exception and verify that the state did not change. Use explicit validation with raise for caller input; assertions are checks during development and can be disabled when Python runs with optimization.

Key Takeaways

  • Choose a class when related state and repeated operations benefit from one home.
  • Keep a simple calculation as a function when no lasting object state is needed.
  • Plan attributes from the data and methods from the actions and rules.
  • Model different data values as instances of one class.
  • Test both successful updates and rejected changes.

Python Classes and OOP examples

5 Walkthroughs

1

From familiar values to your first class

11 steps
Trace method calls and shared references, then create two objects with their own attributes.
  1. 1
    IntroductionStart with objects you know

    A list, a string, and a number are all objects. Their types determine which operations they support, and each value has its own identity.

    Begin by tracking a string method and a list method. Then use the same ideas to make a simple class whose instances store different data. Keep asking two questions: which object does this name reach, and what does this call return?

  2. 2
    ExampleAsk a value about its type

    Python can tell you what kind of object a value is.

    text = "river"
    values = [4, 2]
    print(type(text))              # <class 'str'>
    print(isinstance(values, list))  # True
    

    The string and list offer different methods because they have different types. You can use dir(values) to inspect the names available on the list, including ordinary methods and special methods.

  3. 3
    ExampleTrack a returned string

    A string method can produce a new value without replacing the original.

    name = "robin"
    result = name.title()
    print(result)  # Robin
    print(name)    # robin
    

    title() returns a new string. The assignment stores that result under result, while name still refers to "robin". Writing name = name.title() would instead make name refer to the returned string.

  4. 4
    ExampleTrack an in-place list change

    Compare the string call with a list sort.

    values = [4, 2]
    result = values.sort()
    print(values)  # [2, 4]
    print(result)  # None
    

    The list itself changes, and the return value is None. Assigning the call back to values would replace that name’s list reference with None. Use values.sort() alone when you want to change the existing list.

  5. 5
    ChallengeWhere does the append appear?

    Predict both printed lists.

    a = [7]
    b = a
    c = [7]
    b.append(2)
    print(a, c)
    

    ---reveal---

    The output is [7, 2] [7]. a and b refer to one list, so appending through b changes the list seen through a. c holds a separate list. The lists were initially equal, but a is c was false.

  6. 6
    TipDraw references before tracing changes

    Draw one box for each separately created list. Point both a and b to the first box, and point c to the second. An append changes the box; assignment changes where a name points.

    Also distinguish a method from its call: name.title gives a method object, while name.title() runs it. Parentheses can be the difference between holding behavior and obtaining its result.

  7. 7
    AnalogyOne form, several filled-in copies

    Think of a class as a blank form describing the information one kind of object holds. An instance is a filled-in copy with its own data.

    A Badge class can represent every badge in a program. One instance holds a visitor’s name; another holds a different name. The class describes the shared kind of object. You do not need a new class for every value written on the form.

  8. 8
    ExampleDefine the type and make two instances

    Use a minimal class while learning how instances work.

    class Badge:
        pass
    
    first = Badge()
    second = Badge()
    first.name = "Rae"
    second.name = "Ivo"
    

    pass provides an empty body. Each class call creates an instance, and the dot assignments store data on the selected object. first is second is false. first = Badge would instead give the class another name without creating an instance.

  9. 9
    ChallengeFind a misspelled attribute

    Continue with the two badges:

    first.nmae = "Tess"
    print(first.name, second.name)
    

    What appears?

    ---reveal---

    The output is Rae Ivo. The assignment creates a separate attribute named nmae; it does not change name. Reading an attribute that does not exist raises AttributeError, but assigning a new name is allowed. Check the spelling on both sides of a surprising read.

  10. 10
    ChallengeCombine instances and aliases

    Continue with first and second from the badge example:

    copy_name = first
    copy_name.name = "Noor"
    print(first.name, second.name)
    

    ---reveal---

    The output is Noor Ivo. copy_name is an alias for the first badge, so the assignment changes that object. The second badge remains separate. Creating instances controls how many objects exist; assigning names controls which objects those names reach.

  11. 11
    RecapTrace one more program on paper

    Try a short program with two class calls and a third name assigned to the first instance. Give the objects different attributes, then change one through the alias.

    Before running it, draw the object boxes and predict every read. If a prediction differs from the output, inspect the attribute spelling and the arrows from names to objects. This separates a setup mistake from a reference mistake.

2

Build objects with dependable starting data

14 steps
Initialize a small task collection, distinguish shared settings from personal data, and reject invalid setup.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionSet up a task collection
  2. 2
    ExplanationGive Python an initializer
  3. 3
    ExampleMatch a call to its parameters
  4. 4
    ChallengeSpot the missing attribute
  5. 5
    ExampleAccept an optional starting list
  6. 6
    TipCheck how far the copy goes
  7. 7
    ExplanationAdd data about the class
  8. 8
    ExampleFollow a shadowing assignment
  9. 9
    ChallengeRepair the shared counter
  10. 10
    ExplanationReject a limit that cannot work
  11. 11
    ExampleChoose the error and recovery path
  12. 12
    ChallengeDoes printing reject setup?
  13. 13
    ChallengeKeep the earlier object on failure
  14. 14
    RecapCheck setup as a caller
3

Control state through methods and properties

16 steps
Build a checked stock counter, trace an alias, and expose validated attribute access.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake a stock counter useful
  2. 2
    ExampleStart with stored quantity
  3. 3
    ExplanationLet the receiver supply self
  4. 4
    ChallengeMake a result reusable
  5. 5
    ExampleAdd stock after checking the amount
  6. 6
    ExampleReject a removal before subtraction
  7. 7
    ChallengeTrace a change through an alias
  8. 8
    ConnectionCopy the right level of state
  9. 9
    ExplanationMark internal storage
  10. 10
    ExampleRead through a property
  11. 11
    ExampleAllow checked public assignment
  12. 12
    ChallengeAvoid a recursive setter
  13. 13
    Key InsightCompute a value when it is read
  14. 14
    ChallengeCombine a setter with an alias
  15. 15
    TipRead the definition and the call together
  16. 16
    RecapTest one operation at a time
4

Make objects fit Python expressions

17 steps
Give a coordinate pair useful displays, comparisons, arithmetic, and clear built-in behavior.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionChoose what an object should mean
  2. 2
    ExampleBegin with a point
  3. 3
    ExampleAdd text for inspection
  4. 4
    ChallengePredict a list’s display
  5. 5
    TipReturn text in both display methods
  6. 6
    ExampleCompare coordinate values
  7. 7
    Key InsightLeave unsupported comparisons to Python
  8. 8
    ChallengeCan equal pairs go in a set?
  9. 9
    ExampleChoose a coordinate ordering
  10. 10
    ConnectionOrder can belong to one task
  11. 11
    ExampleAdd without changing the operands
  12. 12
    ExampleSupport both multiplication orders
  13. 13
    ChallengeSeparate a new result from an alias
  14. 14
    ExplanationUse length for a collection
  15. 15
    ChallengePredict the empty queue branch
  16. 16
    TipKeep the meaning visible
  17. 17
    RecapCheck each expression separately
5

Connect objects with clear jobs

15 steps
Build a small order from line objects, follow shared references, and test a rejected operation.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionBuild an order from smaller parts
  2. 2
    ExampleGive one line its own calculation
  3. 3
    ExampleStore lines in an order
  4. 4
    ExampleDelegate each cost
  5. 5
    ChallengeTrace a line shared by two orders
  6. 6
    TipGuard the missing search result
  7. 7
    ExplanationPass an object into another method
  8. 8
    ExampleFollow the receiver and result
  9. 9
    ChallengeWhere does a failed combination stop?
  10. 10
    ConnectionTrace calls that change two objects
  11. 11
    ExplanationDecide which behavior belongs together
  12. 12
    ChallengeAvoid a class for every product
  13. 13
    ExampleTest the checked public entry point
  14. 14
    ChallengeChoose a test for the combined design
  15. 15
    RecapPlan a small collaboration of your own

Python Classes and OOP practice questions

50 Practice Questions in 5 sets

Set 1: Objects, references, and dependable setup10 Practice Questions

Question 1 of 10

What does this code print?

a = [6, 2]
b = a
a = sorted(a)
b.append(9)
print(a, b)
  1. [2, 6] [6, 2, 9]
  2. [2, 6, 9] [2, 6, 9]
  3. [6, 2, 9] [2, 6]
  4. [2, 6] [2, 6, 9]
Show answerHide answer
Answer: A
sorted(a) creates a new sorted list, and the assignment makes a refer to it. b still refers to the original list, so appending changes that list to [6, 2, 9]. Keeping both names on the same list misses the reassignment of a.

Question 2 of 10

What does first.name contain at the end?

class Badge:
    pass
first = Badge()
second = Badge()
first.name = "Rae"
second.name = "Ivo"
first.nmae = "Sol"
  1. "Sol"
  2. "Ivo"
  3. No value; assignment fails.
  4. "Rae"
Show answerHide answer
Answer: D
first.name was assigned "Rae" and is never assigned again. The misspelled nmae creates a separate attribute on first, while second.name belongs to another object. A new attribute assignment is allowed, so the typo does not itself raise an error.

Question 3 of 10

What does this code print?

label = "cedar"
label.upper()
print(label)
  1. Uppercase text: CEDAR
  2. The value None
  3. Original text: cedar
  4. Title-case text: Cedar
Show answerHide answer
Answer: C
upper() returns a new string, but this call does not store that result. The name label still refers to the original string, so the output is cedar. Expecting CEDAR treats the string method as an in-place update; strings are immutable.

Question 4 of 10

A program needs three sensors with different labels but the same readings and reset methods. How should its classes and objects be arranged?
  1. One sensor class per label, with one object each.
  2. One sensor class, with three separate instances.
  3. One sensor instance, with three names for it.
  4. Three label classes, sharing one sensor instance.
Show answerHide answer
Answer: B
The sensors share a type of behavior, so one class can define their readings and reset methods. Separate instances can hold their different labels and current readings. Three names for a single instance would all reach the same state, so resetting one would reset the shared sensor.

Question 5 of 10

What does this code print?

class Counter:
    def __init__(self, start=4):
        self.value = start
c = Counter(9)
print(c.value)
  1. 4
  2. 0
  3. 9
  4. None
Show answerHide answer
Answer: C
Python supplies the new object as self and passes 9 as start. The initializer stores that value in self.value, so the output is 9. The default 4 is used only when the caller leaves out start; it does not replace an explicit argument.

Question 6 of 10

Why does the final line fail?

class Lamp:
    def __init__(self, brightness):
        brightness = brightness
lamp = Lamp(7)
print(lamp.brightness)
  1. The assignment changes only a local parameter.
  2. The class call leaves the parameter unset.
  3. The attribute is stored on the class instead.
  4. The initializer must return the brightness.
Show answerHide answer
Answer: A
brightness = brightness reassigns the local parameter and never creates an attribute on the lamp. The read therefore raises AttributeError; setup should store self.brightness = brightness. Returning the number from __init__ would cause a separate TypeError, not create the attribute.

Question 7 of 10

What does this code print?

class Folder:
    def __init__(self, tags=[]):
        self.tags = tags
a = Folder()
b = Folder()
a.tags.append("urgent")
a.tags = []
print(b.tags, a.tags)
  1. [] []
  2. ['urgent'] ['urgent']
  3. [] ['urgent']
  4. ['urgent'] []
Show answerHide answer
Answer: D
The omitted arguments initially make both objects store the one default list. Appending puts "urgent" in that shared list; the later assignment gives only a a new empty list. Expecting two empty lists confuses replacing one attribute reference with clearing the original shared object.

Question 8 of 10

What does this code print?

class Ticket:
    fee = 3
x = Ticket()
y = Ticket()
x.fee = 8
Ticket.fee = 5
print(x.fee, y.fee)
  1. 5 5
  2. 8 5
  3. 8 3
  4. 3 5
Show answerHide answer
Answer: B
Assigning x.fee creates an instance attribute that shadows the class value. y has no fee of its own, so after the class update it finds 5 on Ticket. Expecting 5 5 overlooks the separate value stored on x, which the class assignment does not replace.

Question 9 of 10

What does this code print?

class Job:
    count = 0
    def __init__(self):
        self.count += 1
a = Job()
Job.count = 10
b = Job()
print(a.count, b.count, Job.count)
  1. 1 11 10
  2. 11 11 10
  3. 1 1 10
  4. 1 11 11
Show answerHide answer
Answer: A
Creating a reads class count 0 and stores instance count 1. After the class changes to 10, creating b stores its own 11; neither initializer updates Job.count. The result 1 11 11 treats the augmented instance assignment as a class counter update.

Question 10 of 10

What does this code print?

class Batch:
    def __init__(self, size):
        if size <= 0:
            raise ValueError("size must be positive")
        self.size = size
batch = Batch(6)
try:
    batch = Batch(0)
except ValueError:
    pass
print(batch.size)
  1. 0
  2. None
  3. 6
  4. ValueError
Show answerHide answer
Answer: C
The call Batch(0) raises before it returns, so the assignment to batch never takes place. The exception is caught, and batch still refers to the earlier valid object with size 6. A failed creation call does not replace the caller’s existing reference with a partial result.
Set 2: Methods, state changes, and properties10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What does this code print?

class Tank:
    def __init__(self):
        self.level = 5
    def add(self, amount):
        self.level += amount
a = Tank()
b = a
result = b.add(4)
print(a.level, result)
  1. 5 None
  2. 9 9
  3. 9 None
  4. 5 9
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What argument does Python supply as self when the program calls panel.area()?
  1. The Panel class object.
  2. The object held by panel.
  3. The value returned by area.
  4. The first attribute in panel.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

What change lets the caller calculate double?

class Pace:
    def __init__(self, minutes):
        self.minutes = minutes
    def seconds(self):
        print(self.minutes * 60)
p = Pace(3)
double = p.seconds() * 2
  1. Call p.seconds without parentheses.
  2. Pass p explicitly to p.seconds(p).
  3. Return self from the initializer.
  4. Return the product inside seconds.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A constructor requires a list of readings and rejects any other type before storing it. What exception best describes a caller passing the string "12,14"?
  1. TypeError
  2. ValueError
  3. AttributeError
  4. AssertionError
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What does this code print?

import copy
class Log:
    def __init__(self):
        self.entries = []
a = Log()
b = copy.copy(a)
b.entries.append("open")
b.entries = ["closed"]
print(a.entries, b.entries, a is b)
  1. [] ['closed'] False
  2. ['closed'] ['closed'] True
  3. ['open'] ['open'] False
  4. ['open'] ['closed'] False
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A session rejects names that are blank after outside spaces are removed. Which initializer body enforces that rule for name?
  1. if not name: raise ValueError("name required")

    self.name = name.strip()

  2. name = name.strip()

    if not name: raise ValueError("name required")

    self.name = name

  3. self.name = name.strip()

    if not name: raise ValueError("name required")

  4. if name == "": raise ValueError("name required")

    self.name = name

Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Why does assigning to reading.value repeatedly call this setter?

class Reading:
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, number):
        self.value = number
  1. The assignment uses the property name again.
  2. The getter reads a different attribute name.
  3. The setter needs an explicit return value.
  4. The decorator should name the class instead.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A counter must stay nonnegative. Its remove(n) method must reject nonpositive amounts and removals larger than the current value. Which sequence leaves the state unchanged on either rejection?
  1. Subtract n; check its sign; check the new value.
  2. Check the sign; subtract n; check the new value.
  3. Check the sign; check available value; subtract n.
  4. Check available value; subtract n; check the sign.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

What does this code print?

class Dial:
    def __init__(self, value):
        self.value = value
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, number):
        if not 0 <= number <= 9:
            raise ValueError("out of range")
        self._value = number
d = Dial(4)
try:
    d.value = 12
except ValueError:
    pass
print(d.value)
  1. 12
  2. 4
  3. 9
  4. None
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A class computes area with a read-only property from width and height. An object has width 6 and height 2; its width is then assigned 8. What does reading obj.area return?
  1. 12
  2. 6
  3. 16
  4. 8
Show answerHide answer

Save this lesson to see this answer.

Set 3: Readable displays and useful operators10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A class defines both __str__ and __repr__. Which method supplies an instance’s text when it appears inside a printed list?
  1. __str__
  2. __init__
  3. __len__
  4. __repr__
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What does this code print?

class Tag:
    def __init__(self, text):
        self.text = text
    def __repr__(self):
        return f"Tag({self.text!r})"
t = Tag("ok")
print(t)
  1. Tag('ok')
  2. ok
  3. Tag(ok)
  4. 'ok'
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Why does str(marker) raise TypeError when its __str__ method only calls print(self.code)?
  1. The method name has too many underscores.
  2. The printed code must use single quotes.
  3. The method returns None instead of a string.
  4. The caller must invoke marker.__str__() directly.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A mutable Label class defines equality by its text and defines no __hash__. Two labels contain the same text. What happens when they are put in a set?
  1. They form two entries because their identities differ.
  2. Creating the set raises an unhashable-type error.
  3. They form one entry because their text matches.
  4. Creating the set converts their text to keys.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A Mark class defines only __lt__, comparing numeric scores, and no other custom comparisons. For a with score 2 and b with score 7, what happens?
  1. a > b is false; a <= b is true.
  2. a > b raises; a <= b is true.
  3. a > b raises; a <= b also raises.
  4. a > b is false; a <= b raises.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A Vector defines __mul__ for vector * number but no reflected multiplication method. What method should be added to support number * vector too?
  1. __rmul__
  2. __add__
  3. __lt__
  4. __len__
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A class defines __eq__ using code and __hash__ as hash(self.code). Its objects are used as dictionary keys. What design change makes the key behavior reliable?
  1. Change the hash whenever any lookup fails.
  2. Use id(self) as the hash for every object.
  3. Keep code unchanged while an object is a key.
  4. Compare identity while keeping the code hash.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A Queue class defines __len__ as the number of pending jobs and defines no __bool__. With no pending jobs, what are len(q) and bool(q)?
  1. 0 and True
  2. 0 and False
  3. None and False
  4. 1 and True
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

What does the final line print?

class Offset:
    def __init__(self, value):
        self.value = value
    def __add__(self, other):
        return Offset(self.value + other.value)
a = Offset(6)
b = a
a = a + Offset(4)
print(a.value, b.value, a is b)
  1. 10 6 False
  2. 10 10 True
  3. 6 10 False
  4. 10 10 False
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

Rank.__lt__ orders records by priority. One report must instead sort those records by creation time without changing their usual order. What approach fits?
  1. Replace __lt__ with a time comparison for every record.
  2. Give each record a new class for the report’s time order.
  3. Change each record’s priority to its creation time first.
  4. Call sorted with a key function returning creation time.
Show answerHide answer

Save this lesson to see this answer.

Set 4: Objects that collaborate and designs that stay clear10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What total does the code print?

class Line:
    def __init__(self, rate, count):
        self.rate = rate
        self.count = count
    def cost(self):
        return self.rate * self.count
x = Line(4, 3)
a = [x]
b = [x, Line(2, 5)]
a[0].count = 6
print(sum(line.cost() for line in b))
  1. 22
  2. 34
  3. 24
  4. 28
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A Workshop stores Tool objects in self.tools. What does workshop.tools[0].label read?
  1. The workshop’s own label.
  2. The labels of all stored tools.
  3. The first stored tool’s label.
  4. The number of stored labels.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A basket computes its total by calling line.cost() on each stored line. A volume discount changes how each line’s cost is calculated. Where should that rule go?
  1. In the basket’s list initialization.
  2. In the line class’s cost method.
  3. In every caller that prints a basket.
  4. In the basket’s loop after each call.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A collection’s find(name) returns an object on success and None on failure. What caller code handles both cases before reading result.name?
  1. Read result.name, then check result is None.
  2. Use if result.name: before printing the name.
  3. Use if type(result) is None: before reading.
  4. Use if result is not None: before reading.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A Position.midpoint(other) method returns a new position whose coordinates are the averages. If a is at (2, 8) and b at (10, 4), what does c = a.midpoint(b) leave?
  1. c at (6, 6); a and b unchanged.
  2. a at (6, 6); c refers to a.
  3. b at (6, 6); c refers to b.
  4. c at (12, 12); a and b unchanged.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

send_to(other, n) calls self.remove(n) and then other.add(n). remove rejects an amount above the sender’s stock before changing anything; add accepts positive amounts. Starting with sender stock 9 and receiver stock 4, a send of 3 succeeds, then a send of 8 is attempted and its exception is caught. What stocks remain?
  1. Sender 9, receiver 4.
  2. Sender 6, receiver 15.
  3. Sender 6, receiver 7.
  4. Sender -2, receiver 15.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A transfer subtracts stock from the sender, then calls the receiver’s add method. The receiver rejects the amount before changing its own stock. No rollback code exists, and the caller catches the exception. What state remains?
  1. Sender unchanged; receiver unchanged.
  2. Sender decreased; receiver unchanged.
  3. Sender decreased; receiver increased.
  4. Sender unchanged; receiver increased.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A script converts one input temperature and prints the result once. It needs no saved state or repeated object behavior. What structure is clearest?
  1. A class for each possible temperature.
  2. A class holding every arithmetic step.
  3. Several objects with a shared result.
  4. A function that returns the conversion.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A rental system has many bikes. Every bike has a label and availability state, and the same checked rent and return actions. What is a useful class plan?
  1. One Bike class with state and checked methods.
  2. A class for every label with copied action code.
  3. One bike object with all labels as aliases.
  4. A class containing only print statements.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

The shelf must reject a third book and keep its two existing books. After the caught error below, what assertion exposes the faulty implementation?

class Shelf:
    def __init__(self):
        self.limit = 2
        self.books = []
    def add(self, title):
        self.books.append(title)
        if len(self.books) > self.limit:
            raise ValueError("shelf full")
shelf = Shelf()
shelf.add("Atlas")
shelf.add("Birch")
try:
    shelf.add("Cove")
except ValueError:
    pass
  1. assert len(shelf.books) == 3
  2. assert shelf.limit == 2
  3. assert len(shelf.books) == 2
  4. assert shelf.books[-1] == "Cove"
Show answerHide answer

Save this lesson to see this answer.

Set 5: Apply the ideas in new situations10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What does this code print?

class Kit:
    def __init__(self, parts=None):
        self.parts = [] if parts is None else list(parts)
source = [[1]]
k = Kit(source)
source.append([2])
source[0].append(3)
print(k.parts)
  1. [[1]]
  2. [[1, 3], [2]]
  3. [[1, 3]]
  4. [[1], [2]]
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

For a built-in list items, what does isinstance(items, list) return?
  1. The list’s number of items.
  2. The names of its list methods.
  3. The class object named list.
  4. The Boolean value True.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

What does this code print?

class Tray:
    labels = []
a = Tray()
b = Tray()
a.labels.append("red")
b.labels = ["blue"]
Tray.labels.append("green")
print(a.labels, b.labels)
  1. ['red', 'green'] ['blue']
  2. ['red', 'green'] ['blue', 'green']
  3. ['red'] ['blue', 'green']
  4. ['blue', 'green'] ['blue', 'green']
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A meter’s setter rejects negative values before storing _reading. Its initializer assigns directly to self._reading, so Meter(-2) succeeds. What change makes creation use the same check as later public assignments?
  1. Add a return statement to the getter.
  2. Assign self.reading = reading in __init__.
  3. Assign self.reading = reading inside the setter.
  4. Remove the property and keep _reading public.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A Key compares equality only by name, and its hash also uses name. Two different instances start with the name "north"; one becomes a dictionary key. Why must that key’s name remain stable?
  1. Changing a name changes both objects’ identities.
  2. All instance attributes must be immutable in Python.
  3. Dictionary keys are automatically copied on insertion.
  4. A changed hash can prevent lookup of the stored key.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Two clubs each have a separate members list, but both lists contain the same Member instance. One club changes that member’s name. The clubs now need independent member names. What change is needed?
  1. Create a separate member object for each club.
  2. Copy only one of the outer members lists.
  3. Assign another variable to the shared member.
  4. Move members into the club class body.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A message class has a useful __str__, but a printed list of messages still shows default object descriptions. What addition improves those list entries?
  1. A __len__ returning the text length.
  2. An __init__ returning the text value.
  3. A __repr__ returning useful message text.
  4. An extra call to print inside __str__.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

a.combine(b) is intended to leave both inputs unchanged. Its body calls self.parts.extend(other.parts) and then returns self. What behavior does that implementation actually have?
  1. It creates a new container with copied parts.
  2. It changes a and returns that same object.
  3. It changes b and returns that same object.
  4. It leaves both unchanged and returns None.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

Inside class Vault, setup assigns self.__code = "x". What explains why vault.__code fails while vault._Vault__code can work?
  1. The code is encrypted during initialization.
  2. The underscores make the value a class attribute.
  3. The getter is called only with parentheses.
  4. Name mangling changes the stored attribute name.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A class’s take method uses assert n > 0 as its only check on caller input. The rule must hold even when Python runs with optimization enabled. What change is needed?
  1. Return the assertion result to the caller.
  2. Move the assertion into the calling loop.
  3. Use an explicit condition that raises an exception.
  4. Place the assertion after the state update.
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

62 Key Terms, each with its definition

A

Accessor
An accessor is a method that reports information about an object’s state without intentionally changing that state.
A rectangle’s area() accessor reads its width and height and returns a number without resizing it.
Alias
An alias is another name referring to an existing object, so changes to a shared mutable object can be seen through either name.
After backup = entries, appending through backup also changes the list reached through entries.
Assertion
An assertion is a development check that raises AssertionError if its condition is false; Python can omit it in optimized execution.
After a deposit, assert acct.balance == 75 checks behavior; rejected caller input still needs explicit validation.
Attribute
An attribute is a named value reached through an object or class with dot notation, such as a book’s stored title.
Assigning book.title = "Emma" stores a title, which can then be read by evaluating book.title.
Attribute lookup
Attribute lookup is the process of finding a named attribute; ordinary instance data can be found before a class value.
If p has no own limit, reading p.limit can find the shared limit value defined on its class.

B

Backing attribute
A backing attribute stores the value managed by a property under a separate name, preventing recursive property access.
A score getter reads _score; storing to score inside its own setter would repeatedly call that setter.
Behavior test
A behavior test creates objects, performs an action, and checks an observable result, including state preserved after rejected input.
A withdrawal test catches the expected error and checks that the balance and history still match their earlier values.
Bound method
A bound method is a method object that remembers both the function and the instance to supply when the method is called.
r.area obtains a bound method; adding parentheses as r.area() runs it and returns the calculated area.

C

Class
A class defines a Python type, providing a common definition for the attributes and behavior of its instances.
One Book class can create many books, each storing a different title while sharing the method definitions.
Class attribute
A class attribute is stored on the class and can be found by instances that do not have a shadowing attribute of the same name.
A class-wide MAX_TOPPINGS setting can supply the default limit read through many different pizza objects.
Class design
Class design chooses the types, state, actions, and validity rules needed to model a problem clearly with cooperating objects.
Planning a library starts with books and members, then places a borrowing limit in a checkout operation.
Composition
Composition builds a larger object from other objects stored in its attributes or collections, modeling a has-a relationship.
A library can store book objects, with each book keeping its title and current borrower information.
Container
A container is an object that holds references to other objects, allowing them to be stored and processed together.
A cart’s items list holds item objects; following cart.items[0] reaches its first stored item.

D

Deep copy
A deep copy copies an object and its nested objects, separating nested mutable state that a shallow copy can leave shared.
If an account stores a transaction list, copy.deepcopy(account) also copies that list, so appending to the copy’s history leaves the original alone.
Default argument
A default argument is a value used when a caller omits a parameter, evaluated when the function or method is defined.
With balance=0 in the initializer, Account("Rae") starts with zero without supplying a balance argument.
Delegation
Delegation passes part of a task to another object’s method, letting that object apply the rules for its own data.
A cart delegates each subtotal to its item and then adds the returned amounts into the cart total.
Dot notation
Dot notation is the syntax that places a period between an object or class and the name of an attribute being accessed.
In cart.items, the name before the dot identifies the object and the name after it selects its items attribute.

E

Encapsulation
Encapsulation keeps an object’s data and the behavior that manages it together, giving callers a clear interface for valid changes.
An account’s deposit method checks the amount and updates internal storage without callers repeating the rule.

H

Hashability
Hashability allows an object to serve as a set member or dictionary key using a stable hash consistent with its equality rule.
A class defining __eq__ without __hash__ becomes unhashable, even if its instances work in a list.

I

Identity
Identity distinguishes one object from every other object; the is operator tests whether two references reach that same object.
Two separately created lists can satisfy a == b while a is b is false because their identities differ.
Initialization
Initialization sets the starting state of a newly created object, usually by storing arguments and initial values as attributes.
An account initializer can store its owner, accept a starting balance, and create an empty transaction list.
Instance
An instance is one object belonging to a class, holding the particular state associated with that object.
The calls Book() and Book() create two separate book instances even though both have the same type.
Instance attribute
An instance attribute is stored on one particular object, giving that instance its own data under the attribute’s name.
Two pizzas can each store a different size and a separate toppings list through assignments on self.
Instance method
An instance method is a function defined in a class that receives the calling object, conventionally through its first parameter, self.
For r.area(), Python supplies r to the method so it can multiply that rectangle’s width and height.
Internal attribute
An internal attribute is intended for use within a class’s implementation, commonly signaled by one leading underscore in its name.
In an account class, callers read a public balance property while the class’s own methods manage the stored _balance.
Invariant
An invariant is a rule that must remain true for an object’s state to be valid, including after successful changes.
A score class can require every score to stay between 0 and 100 during setup and later assignments.

K

Key function
A key function extracts the value a sorting operation should compare for each item, selecting an order for that particular task.
A report can call sorted(records, key=by_date) without changing the record class’s usual comparison method.

M

Main guard
A main guard checks whether a module is being run directly, keeping startup actions from running when the module is imported.
Place main() under if __name__ == "__main__": so another program can import the classes without starting it.
Method
A method is a function provided by a class that can be called on an object to work with that object or its data.
Calling "cedar".upper() runs a string method, while scores.append(7) runs a method on a list.
Mutability
Mutability is the ability of an object to change its contents while remaining the same object with the same identity.
Appending to a list changes the existing list; applying upper() to a string returns a new string.
Mutable default
A mutable default is a changeable default object that can be reused across calls, causing unintended sharing if stored or changed.
If items=[] is stored directly, two objects created without that argument may refer to the same list.
Mutator
A mutator is a method that changes an existing object’s state, often checking inputs before updating related attributes.
A checked withdrawal mutator can reduce the balance and record a transaction only after the amount passes validation.

N

Name mangling
Name mangling rewrites class-body names with two leading underscores and no double trailing underscores to include the class name.
Inside Acc, self.__balance becomes _Acc__balance, which remains accessible under the mangled name.
None
None is Python’s single value for an absent or unavailable result, also useful for a relationship with no object assigned yet.
A book can store borrower = None until a member borrows it, so code checks before reading a borrower’s name.
NotImplemented
NotImplemented is a special return value indicating that a comparison or numeric method does not support the supplied operands.
A point’s equality method can return NotImplemented for a string, letting Python handle the unsupported pair.

O

Object
An object is a Python value with an identity, a type, and a state, together with the operations supported by its type.
The string "hello" and the list [2, 5] are both objects, though their types offer different operations.
Object argument
An object argument passes a reference to an instance into a function or method, allowing the called code to use that object.
The other parameter in p.midpoint(q) refers to q, whose coordinates the method reads to calculate its result.
Operator overloading
Operator overloading defines what an operator means for a class by implementing the special method associated with that operation.
A vector’s __add__ can make a + b produce a new vector formed by adding their corresponding coordinates.

P

Programmer representation
A programmer representation is the unambiguous text returned by __repr__, used for inspection and inside container displays.
A book’s representation can show "Book('Emma', 'Jane Austen', 1815)", including quoted text fields.
Property
A property provides method-controlled access through attribute syntax, allowing a read to compute a result or an assignment to run checks.
Reading student.score can call a getter, and assigning student.score = 90 can invoke a validating setter.

R

Receiver
The receiver is the object before the dot in an instance method call, passed automatically as the method’s self argument.
In sender.transfer_to(receiver, 5), sender receives the transfer call; the account argument is the destination.
Reference
A reference connects a name or stored value to an object, allowing several names or containers to reach the same object.
Placing one item in two cart lists shares that item, so changing its price can affect both calculated totals.
Reflected operation
A reflected operation lets the right operand handle an operation when the left operand cannot support the pair.
For 3 * vector, Python can use the vector’s __rmul__ when integer multiplication cannot handle that vector.
Representation fallback
The representation fallback lets the default string conversion use a custom __repr__ when a class defines no custom __str__.
Adding only __repr__ can improve both print(point) and the point’s appearance inside a printed list.
Responsibility
A class’s responsibility is the clear job its data and methods serve, helping decide which behaviors belong together.
An item calculates its own subtotal, while a cart organizes items and adds those subtotals into a total.
Return value
A return value is the object a call supplies to its surrounding expression, available for assignment, calculation, or testing.
Returning the area lets r1.area() + r2.area() add results, while printing alone leaves None as each result.

S

Search result
A search result can be the existing matching object or a special value such as None indicating that no match was found.
The caller checks found is not None before reading found.name from a cart’s item search.
self
The parameter self conventionally receives the current instance, letting a method read or change that object’s attributes.
In self.owner = owner, the left side stores data on the instance and the right side reads a local parameter.
Setter
A setter is the method invoked by assignment to a property, often validating the proposed value before storing it separately.
A score setter that rejects 105 before assigning _score leaves a previously valid score unchanged.
Shadowing
Shadowing occurs when an instance’s own ordinary data attribute hides a class attribute of the same name during lookup.
After p.limit = 8, changing Pizza.limit does not replace the separate limit already stored on p.
Shallow copy
A shallow copy creates a new outer object while retaining references to its existing nested objects, which can remain shared.
If an account stores a transaction list, the account made by copy.copy(account) can still share that list with the original.
Special method
A special method has a name with two underscores on each side and supplies behavior for Python syntax or built-in operations.
Python requests __str__ when converting an object to its readable string form and __len__ for its length.
State
An object’s state is the current data held in its attributes, which methods can inspect and update as the program runs.
After an accepted deposit to an account that keeps a history, the account’s state includes both its new balance and the added history entry.
String representation
A string representation is the readable text returned by __str__, normally intended for users of the program.
A point can display as "(3, 4)" when printed directly, leaving its numeric coordinate attributes unchanged.

T

Truthiness
Truthiness is the Boolean interpretation of an object in a condition; without __bool__, a defined zero length makes it false.
An empty playlist with __len__ and no custom __bool__ causes if playlist: to skip its body.
Type
A type is the class of an object, defining which operations it supports and how values of that kind behave.
type([2, 5]) returns the list class, and isinstance([2, 5], list) confirms that the value is a list.
TypeError
A TypeError reports that an operation received an inappropriate type or was called with arguments its definition does not accept.
Passing text to a numeric width comparison raises TypeError because that comparison cannot order text and zero.

V

Validation
Validation checks whether incoming data or a requested change satisfies the rules a class requires before accepting it.
A rectangle validates a numeric width by rejecting zero or negative values before storing the dimension.
Value equality
Value equality compares objects by meaningful data, commonly defined with __eq__, instead of requiring the same object identity.
Two separate points at the same coordinates can compare equal and match during a list membership test.
ValueError
A ValueError reports that an argument has an acceptable type but its value is outside what the operation accepts.
A numeric width of -2 can raise ValueError with the message "width must be positive".

#

__init__
The __init__ method initializes an instance after Python creates it and must return None when initialization completes.
Calling Counter(5) passes the new instance and 5 to its initializer before returning the configured counter.
__lt__
The __lt__ method implements less-than comparison, supplying an ordering that operations such as sorted can use.
A score-based __lt__ lets a list of student objects sort from lowest score to highest score.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started