Quiz#

This quiz covers the key concepts from Lecture 6: Object-Oriented Programming I, including OOP principles, design phase (requirement analysis, business rules, noun/verb analysis), classes and objects, self, __init__, instance and class attributes, dunder methods, operator overloading, abstraction, encapsulation, @property, and exception handling (appendix).

Note

Instructions:

  • Answer all questions to the best of your ability.

  • Multiple choice questions have exactly one correct answer.

  • True/False questions require you to determine if the statement is correct.

  • Essay questions require short written responses (2-4 sentences).

  • Click the dropdown after each question to reveal the answer.


Multiple Choice#

Question 1

What is the output of the following code?

class Robot:
    def __init__(self, name: str, battery: int = 100):
        self.name = name
        self.battery = battery

scout = Robot("Scout")
print(scout.battery)
  1. None

  2. 100

  3. TypeError

  4. "Scout"

Answer

B100

The battery parameter has a default value of 100. Since no value was passed for battery, it defaults to 100.

Question 2

What does self refer to in a Python method?

  1. The class itself.

  2. The module the class is defined in.

  3. The specific instance that called the method.

  4. The parent class.

Answer

C – The specific instance that called the method.

When you call obj.method(arg), Python translates this to ClassName.method(obj, arg). The self parameter receives the instance that called the method.

Question 3

What is the output of the following code?

class Counter:
    count = 0

    def __init__(self):
        Counter.count += 1

a = Counter()
b = Counter()
c = Counter()
print(Counter.count)
  1. 0

  2. 1

  3. 3

  4. AttributeError

Answer

C3

count is a class attribute shared by all instances. Each call to __init__ increments it by 1. After three instances are created, Counter.count is 3.

Question 4

Which dunder method is called when you use print() on an object?

  1. __repr__

  2. __str__

  3. __print__

  4. __format__

Answer

B__str__

print() calls str() on the object, which invokes __str__. If __str__ is not defined, Python falls back to __repr__.

Question 5

What should a dunder method return when it does not know how to handle the other operand’s type?

  1. None

  2. False

  3. raise NotImplementedError

  4. NotImplemented

Answer

DNotImplemented

Returning the singleton NotImplemented (not raising NotImplementedError) signals to Python that the operation is not supported for this type combination. Python then tries the reflected operation on the other operand.

Question 6

What is the output of the following code?

class Sensor:
    def __init__(self, sensor_type: str, range_m: float):
        self.sensor_type = sensor_type
        self.range_m = range_m

    def __eq__(self, other):
        if isinstance(other, Sensor):
            return self.range_m == other.range_m
        return NotImplemented

lidar = Sensor("lidar", 50.0)
camera = Sensor("camera", 50.0)
print(lidar == camera)
  1. False

  2. True

  3. NotImplemented

  4. TypeError

Answer

BTrue

The __eq__ method compares sensors by range_m. Both sensors have range_m = 50.0, so they are considered equal regardless of their sensor_type.

Question 7

What happens when you assign a value to a property that has no setter defined?

  1. The value is silently ignored.

  2. AttributeError is raised.

  3. The value is assigned to a new instance attribute with the same name.

  4. TypeError is raised.

Answer

BAttributeError is raised.

If a property has only a getter (no @property_name.setter), attempting to assign a value raises AttributeError: property 'name' of 'ClassName' object has no setter.

Question 8

What is the difference between an instance attribute and a class attribute?

  1. Instance attributes are defined with self; class attributes are defined with cls.

  2. Instance attributes belong to a specific object; class attributes are shared by all instances.

  3. Instance attributes are private; class attributes are public.

  4. There is no difference; the terms are interchangeable.

Answer

B – Instance attributes belong to a specific object; class attributes are shared by all instances.

Instance attributes are created in __init__ using self.attr = value and each object gets its own copy. Class attributes are defined in the class body (outside any method) and are shared across all instances.

Question 9

What is the output of the following code?

class Robot:
    def __init__(self, name: str):
        self._name = name
        self._battery = 100

    @property
    def battery(self) -> int:
        return self._battery

    @battery.setter
    def battery(self, value: int):
        if not isinstance(value, int) or not (0 <= value <= 100):
            raise ValueError("Invalid battery")
        self._battery = value

scout = Robot("Scout")
scout.battery = 80
print(scout.battery)
  1. 100

  2. 80

  3. ValueError

  4. AttributeError

Answer

B80

scout.battery = 80 triggers the setter, which validates that 80 is a valid integer in [0, 100] and assigns it. scout.battery then triggers the getter and returns 80.

Question 10

In the noun/verb analysis technique, what do nouns and verbs typically map to in OOP?

  1. Nouns map to methods; verbs map to attributes.

  2. Nouns map to classes or attributes; verbs map to methods.

  3. Nouns map to modules; verbs map to functions.

  4. Nouns map to variables; verbs map to operators.

Answer

B – Nouns map to classes or attributes; verbs map to methods.

In noun/verb analysis, nouns from the problem description become candidate classes or attributes, verbs become candidate methods, and relational phrases (“has a”, “is a”) inform class relationships.

Question 11

What is the output of the following code?

class Robot:
    def __init__(self, name: str):
        self.name = name
        self.log: list[str] = []

    def __contains__(self, item: str):
        return item in self.log

    def __call__(self, task: str):
        self.log.append(task)

scout = Robot("Scout")
scout("pick widget")
print("pick widget" in scout)
  1. True

  2. False

  3. TypeError

  4. ["pick widget"]

Answer

ATrue

scout("pick widget") triggers __call__, which appends "pick widget" to the log. "pick widget" in scout triggers __contains__, which checks the log and returns True.

Question 12

What is the primary purpose of __init__?

  1. To create the object in memory.

  2. To initialize the object’s attributes after it has been created.

  3. To define the class’s methods.

  4. To destroy the object when it is no longer needed.

Answer

B – To initialize the object’s attributes after it has been created.

__init__ is an initializer, not a constructor. The actual object creation happens in __new__ (rarely overridden). __init__ sets up the object’s initial state by assigning values to its attributes.

Question 13

What is the output of the following code?

class Robot:
    def __init__(self, name: str, battery: int = 100):
        self.name = name
        self.battery = battery

    def __repr__(self) -> str:
        return f"Robot('{self.name}', {self.battery})"

robots = [Robot("Scout", 80), Robot("Hauler", 60)]
print(robots)
  1. [Scout (80%), Hauler (60%)]

  2. [Robot('Scout', 80), Robot('Hauler', 60)]

  3. [<Robot object>, <Robot object>]

  4. TypeError

Answer

B[Robot('Scout', 80), Robot('Hauler', 60)]

When you print a list, Python calls __repr__ on each element (not __str__). The __repr__ method returns the string that looks like a valid constructor call.

Question 14

Which of the following correctly describes operator overloading?

  1. A child class replaces a method inherited from its parent class.

  2. Teaching Python what an existing operator should do when applied to your own class.

  3. Using multiple operators in a single expression.

  4. Defining multiple functions with the same name but different parameters.

Answer

B – Teaching Python what an existing operator should do when applied to your own class.

Operator overloading lets you give meaning to operators like +, ==, and > for your custom classes by implementing the corresponding dunder methods (__add__, __eq__, __gt__).

Question 15

What naming convention signals that an attribute is non-public in Python?

  1. Prefixing with public_

  2. Suffixing with _private

  3. Prefixing with a single underscore (_name)

  4. Using all uppercase letters (NAME)

Answer

C – Prefixing with a single underscore (_name)

A single leading underscore signals “non-public by convention.” Python does not enforce this, but other developers understand that the attribute should be accessed through the provided interface (e.g., a @property) rather than directly.


True or False#

Question 16

True or False: In Python, __init__ is the constructor that creates the object in memory.

Answer

False

__init__ is an initializer, not a constructor. It sets up the object’s attributes after the object has already been created. The actual constructor is __new__, which is rarely overridden.

Question 17

True or False: Class attributes are shared by all instances of a class.

Answer

True

Class attributes are defined in the class body outside any method. All instances share the same class attribute. If you modify it through the class (ClassName.attr = value), the change is visible to all instances.

Question 18

True or False: If __str__ is not defined on a class, calling print() on an instance will raise an error.

Answer

False

If __str__ is not defined, Python falls back to __repr__. If neither is defined, Python uses the default object.__repr__, which returns something like <__main__.Robot object at 0x7f...>.

Question 19

True or False: The @property decorator allows you to access a method as if it were an attribute.

Answer

True

The @property decorator transforms a method into a read-only attribute. Combined with a setter, it provides a clean interface: obj.attr triggers the getter, and obj.attr = value triggers the setter with validation.

Question 20

True or False: In Python, a single leading underscore (_name) makes an attribute truly private and inaccessible from outside the class.

Answer

False

Python does not enforce access control. A single leading underscore is a convention that signals “non-public,” but the attribute is still accessible from outside the class. Python relies on the “consenting adults” principle.

Question 21

True or False: return NotImplemented and raise NotImplementedError serve the same purpose.

Answer

False

return NotImplemented is a signal to Python’s runtime inside dunder methods, telling Python to try the reflected operation on the other operand. raise NotImplementedError is a standard exception used in abstract or stub methods to indicate that a subclass must provide an implementation.

Question 22

True or False: Every Python class implicitly inherits from object.

Answer

True

In Python 3, all classes implicitly inherit from object if no explicit base class is specified. This is why all objects have default implementations of dunder methods like __repr__ and __eq__.

Question 23

True or False: Creating new attributes outside __init__ (e.g., inside other methods) is considered best practice.

Answer

False

All attributes should be initialized in __init__, even if they start as empty values or None. Creating attributes in other methods makes the class harder to understand and can lead to AttributeError if a method is called before the attribute is set.

Question 24

True or False: When you print a list of objects, Python calls __str__ on each element.

Answer

False

When you print a list, Python calls __repr__ on each element, not __str__. The __str__ method is only called when you use print() or str() directly on a single object.

Question 25

True or False: Abstraction and encapsulation are the same concept.

Answer

False

Abstraction is about hiding what an object does internally behind a clean interface. Encapsulation is about bundling data and methods together and controlling how the data is accessed. They are related but distinct: abstraction focuses on interface design, encapsulation focuses on data protection.


Essay Questions#

Question 26

Explain the difference between a class and an object (instance). Provide a brief example.

(2-4 sentences)

Answer Guidelines

Key points to include:

  • A class is a blueprint that defines the structure and behavior (attributes and methods) that its objects will have.

  • An object (instance) is a concrete realization of a class with its own independent state.

  • Multiple objects can be created from the same class, each with different attribute values.

  • Example: Robot is a class; scout = Robot("Scout") creates an object with its own name and battery.

Question 27

Explain the difference between ``__str__`` and ``__repr__``. When would you implement each one?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • __str__ is for human-readable output (used by print() and str()); __repr__ is for developer-oriented output (used by repr(), the REPL, and inside containers).

  • A good __repr__ should look like the code you would type to create that object, enabling a developer to reproduce it from the output.

  • If you only implement one, implement __repr__; it serves as the fallback for __str__.

  • When printing a list, Python calls __repr__ on each element, not __str__.

Question 28

Explain how the ``@property`` decorator provides encapsulation in Python. Why is it preferred over explicit getter/setter methods?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • The @property decorator allows you to add validation and computed access to attributes while preserving attribute-style syntax (obj.attr instead of obj.get_attr()).

  • The non-public attribute (_attr) stores the actual data, while the property provides the controlled interface.

  • It is preferred because it is Pythonic: the caller does not need to know that validation is happening, and you can add validation later without changing the external interface.

  • The setter can reject invalid values by raising exceptions, keeping the object in a valid state.

Question 29

Describe the five-step design workflow covered in this lecture. Why is the workflow described as iterative?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • The five steps are: requirement analysis (what the system must do), business rules (constraints and invariants), noun/verb analysis (extracting classes, attributes, and methods), modeling (UML diagrams for structure and behavior), and implementation (translating to code).

  • The workflow is iterative because your first pass will not be perfect. Implementing code often reveals gaps in the requirements or design that require revisiting earlier steps.

  • Each step informs the next: business rules shape class validation, noun/verb analysis produces the initial class structure, and modeling reveals missing relationships.

  • The design phase is not a one-time activity; it is refined throughout development.

Question 30

What is operator overloading?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • Operator overloading teaches Python what an existing operator (like + or ==) should do when applied to your class, by implementing the corresponding dunder method (e.g., __add__, __eq__).

  • Overloading example: implementing __add__ on Sensor so that lidar + camera returns a new fused sensor.


Exception Handling (Appendix)#

Question 31

What is the output of the following code?

def compute_speed(distance: float, time: float) -> float:
    return distance / time

try:
    speed = compute_speed(100.0, 0.0)
    print(f"Speed: {speed}")
except ZeroDivisionError:
    print("Cannot divide by zero")
  1. Speed: inf

  2. Cannot divide by zero

  3. ZeroDivisionError: float division by zero

  4. None

Answer

BCannot divide by zero

Dividing by 0.0 raises a ZeroDivisionError. The except block catches it and prints the error message instead of crashing.

Question 32

What is the purpose of the else clause in a try/except block?

  1. It runs when an exception is raised.

  2. It runs only when the try block completes without raising an exception.

  3. It runs regardless of whether an exception occurred.

  4. It replaces the except block for unknown exception types.

Answer

B – It runs only when the try block completes without raising an exception.

The else clause separates the code that might fail (inside try) from the code that should only run on success. The finally clause is the one that runs unconditionally.

Question 33

Which exception type should you raise when a function receives an argument of the correct type but with an invalid value (e.g., a negative battery level)?

  1. TypeError

  2. ValueError

  3. AttributeError

  4. NotImplementedError

Answer

BValueError

ValueError is used when the type is correct but the value is invalid. TypeError is used when the type itself is wrong. For example, passing -10 (an int) when only positive integers are allowed is a ValueError, but passing "full" (a str) when an int is expected is a TypeError.