==================================================== Exercises ==================================================== This page contains four take-home exercises that reinforce the concepts from Lecture 7. Each exercise asks you to **write code from scratch** based on a specification -- no starter code is provided. All files should be created inside your ``lecture7/`` workspace folder. .. dropdown:: Exercise 1 -- Robot Hierarchy :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice single and hierarchical inheritance, ``super()``, method overriding, and runtime type inspection with ``isinstance()`` and ``issubclass()``. .. raw:: html
**Specification** Create a file ``lecture7/robot_hierarchy.py`` that implements the following. Each class and method must include type hints and a Google-style docstring. 1. **``Robot`` base class** (provided skeleton -- implement it fully): - ``__init__(self, name: str, battery: int = 100)`` - Instance attributes: ``_name`` (``str``), ``_battery`` (``int``) - ``perform_task(self, task_name: str) -> None`` that prints ``" performing: "`` and decreases ``_battery`` by 10. If ``_battery < 10``, print ``" needs recharging!"`` and do not perform the task. - ``recharge(self) -> None`` that sets ``_battery`` to 100 and prints ``" fully recharged!"``. - ``__repr__(self) -> str`` that returns ``"Robot(name='', battery=)"``. 2. **``MobileRobot(Robot)``**: - ``__init__(self, name: str, max_speed: float, terrain_type: str, battery: int = 100)`` that calls ``super().__init__()`` then sets ``_max_speed`` (``float``, m/s) and ``_terrain_type`` (``str``). - ``move(self, direction: str) -> None`` that prints ``" moving at up to m/s"``. - ``__repr__(self) -> str`` that returns ``"MobileRobot(name='', battery=, max_speed=)"``. 3. **``ManipulatorRobot(Robot)``**: - ``__init__(self, name: str, arm_reach: float, payload_capacity: float, battery: int = 100)`` that calls ``super().__init__()`` then sets ``_arm_reach`` (``float``, m) and ``_payload_capacity`` (``float``, kg). - ``move(self, direction: str) -> None`` that prints ``" repositioning "``. - ``pick_up(self, obj: str) -> None`` that prints ``" picking up: "``. - ``deliver(self, obj: str, zone: str) -> None`` that prints ``" delivering to zone "``. - ``__repr__(self) -> str`` that returns ``"ManipulatorRobot(name='', battery=, arm_reach=)"``. 4. In the ``if __name__ == "__main__"`` block: .. code-block:: python if __name__ == "__main__": scout = MobileRobot("Scout", max_speed=1.5, terrain_type="indoor") arm = ManipulatorRobot("Arm-1", arm_reach=0.8, payload_capacity=2.0) scout.move("north") scout.perform_task("navigate to zone B") arm.move("left") arm.pick_up("widget-42") arm.deliver("widget-42", "dropoff") print(scout) print(arm) # isinstance checks print(isinstance(scout, MobileRobot)) # True print(isinstance(scout, Robot)) # True print(isinstance(scout, ManipulatorRobot)) # False # issubclass checks print(issubclass(MobileRobot, Robot)) # True print(issubclass(ManipulatorRobot, Robot)) # True **Expected output:** .. code-block:: text Scout moving north at up to 1.5 m/s Scout performing: navigate to zone B Arm-1 repositioning left Arm-1 picking up: widget-42 Arm-1 delivering widget-42 to zone dropoff MobileRobot(name='Scout', battery=90, max_speed=1.5) ManipulatorRobot(name='Arm-1', battery=100, arm_reach=0.8) True True False True True .. raw:: html
**Deliverables** - ``lecture7/robot_hierarchy.py`` - The program must run without errors and produce output matching the expected format above. - Every class and method must include type hints and a Google-style docstring. .. dropdown:: Exercise 2 -- Abstract Robot Interface :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice defining abstract base classes with the ``abc`` module, enforcing interface contracts with ``@abstractmethod``, mixing abstract and concrete methods, and verifying that Python raises ``TypeError`` when an abstract class is instantiated directly. .. raw:: html
**Specification** Create a file ``lecture7/robot_abstract.py`` that implements the following. Each class and method must include type hints and a Google-style docstring. 1. **``Robot`` abstract base class** (inherits from ``ABC``): - ``__init__(self, name: str, battery: int = 100)`` that sets ``_name`` and ``_battery``. - ``@abstractmethod move(self, direction: str) -> None`` - Concrete ``perform_task(self, task_name: str) -> None`` that prints ``" performing: "`` and decreases ``_battery`` by 10. If ``_battery < 10``, print ``" needs recharging!"`` and do not perform the task. - Concrete ``recharge(self) -> None`` that sets ``_battery`` to 100 and prints ``" fully recharged!"``. 2. **``MobileRobot(Robot)``**: - ``__init__(self, name: str, max_speed: float, terrain_type: str, battery: int = 100)`` that calls ``super().__init__()`` then sets ``_max_speed`` and ``_terrain_type``. - ``move(self, direction: str) -> None`` that prints ``" moving at up to m/s"``. 3. **``ManipulatorRobot(Robot)``**: - ``__init__(self, name: str, arm_reach: float, payload_capacity: float, battery: int = 100)`` that calls ``super().__init__()`` then sets ``_arm_reach`` and ``_payload_capacity``. - ``move(self, direction: str) -> None`` that prints ``" repositioning "``. 4. In the ``if __name__ == "__main__"`` block: .. code-block:: python if __name__ == "__main__": # Confirm Robot cannot be instantiated directly try: r = Robot("Base") except TypeError as e: print(f"TypeError: {e}") scout = MobileRobot("Scout", max_speed=1.5, terrain_type="indoor") arm = ManipulatorRobot("Arm-1", arm_reach=0.8, payload_capacity=2.0) scout.move("north") scout.perform_task("navigate to zone B") arm.move("left") arm.perform_task("pick widget") arm.recharge() # inherited concrete method **Expected output:** .. code-block:: text TypeError: Can't instantiate abstract class Robot without an implementation for abstract method 'move' Scout moving north at up to 1.5 m/s Scout performing: navigate to zone B Arm-1 repositioning left Arm-1 performing: pick widget Arm-1 fully recharged! .. raw:: html
**Deliverables** - ``lecture7/robot_abstract.py`` - The program must run without errors and produce output matching the expected format above. - Every class and method must include type hints and a Google-style docstring. .. dropdown:: Exercise 3 -- ``__slots__`` and Memory Comparison :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice using ``__slots__`` to restrict instance attributes, measure the memory savings over regular instances, and extend a slotted class through inheritance. .. raw:: html
**Specification** Create a file ``lecture7/robot_slots.py`` that implements the following. Each class and method must include type hints and a Google-style docstring. 1. **``Pose`` class** (without ``__slots__``): - ``__init__(self, x: float, y: float, heading: float)`` that sets ``_x``, ``_y``, and ``_heading``. - Read-only ``@property`` for each attribute: ``x``, ``y``, ``heading``. - ``__repr__(self) -> str`` that returns ``"Pose(x=, y=, heading=)"``. 2. **``PoseSlotted`` class** (identical behavior to ``Pose`` but declares ``__slots__ = ("_x", "_y", "_heading")``): - Same ``__init__``, properties, and ``__repr__`` as ``Pose``. - Verify that assigning a dynamic attribute (e.g., ``pose._extra = 1``) raises ``AttributeError``. 3. **Memory comparison**: create 1000 instances of each class and compare total memory use. For ``Pose``, total memory per instance is ``sys.getsizeof(instance) + sys.getsizeof(instance.__dict__)``. For ``PoseSlotted``, total memory per instance is ``sys.getsizeof(instance)`` only. Print the difference. 4. **``StampedPose(PoseSlotted)``**: - Adds ``_timestamp: float`` via ``__slots__ = ("_timestamp",)``. Do not redeclare ``_x``, ``_y``, or ``_heading``. - ``__init__(self, x: float, y: float, heading: float, timestamp: float)`` that calls ``super().__init__(x, y, heading)`` then sets ``_timestamp``. - Read-only ``@property timestamp``. - ``__repr__(self) -> str`` that returns ``"StampedPose(x=, y=, heading=, timestamp=)"``. 5. In the ``if __name__ == "__main__"`` block: .. code-block:: python if __name__ == "__main__": import sys # Verify dynamic attribute restriction ps = PoseSlotted(1.0, 2.0, 0.0) try: ps._extra = "dynamic" except AttributeError as e: print(f"AttributeError: {e}") # Memory comparison poses = [Pose(float(i), float(i), 0.0) for i in range(1000)] slotted = [PoseSlotted(float(i), float(i), 0.0) for i in range(1000)] pose_mem = sum(sys.getsizeof(p) + sys.getsizeof(p.__dict__) for p in poses) slotted_mem = sum(sys.getsizeof(p) for p in slotted) print(f"Pose total (1000 instances): {pose_mem} bytes") print(f"PoseSlotted total (1000 instances): {slotted_mem} bytes") print(f"Memory saved: {pose_mem - slotted_mem} bytes") # StampedPose sp = StampedPose(1.0, 2.0, 0.5, timestamp=1712345678.0) print(sp) print(sp.x, sp.y, sp.heading, sp.timestamp) **Expected output (values are approximate):** .. code-block:: text AttributeError: 'PoseSlotted' object has no attribute '_extra' Pose total (1000 instances): 280000 bytes PoseSlotted total (1000 instances): 56000 bytes Memory saved: 224000 bytes StampedPose(x=1.0, y=2.0, heading=0.5, timestamp=1712345678.0) 1.0 2.0 0.5 1712345678.0 .. note:: Exact byte counts will vary by Python version and platform. The important result is that ``PoseSlotted`` uses significantly less memory than ``Pose``. .. raw:: html
**Deliverables** - ``lecture7/robot_slots.py`` - The program must run without errors and produce output consistent with the expected format above. - Every class and method must include type hints and a Google-style docstring. .. dropdown:: Exercise 4 -- Protocols and Structural Subtyping :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice defining a ``typing.Protocol``, implementing it in independent classes without inheritance, writing a polymorphic function that accepts any conforming type, and using ``@runtime_checkable`` for ``isinstance()`` checks. .. raw:: html
**Specification** Create a file ``lecture7/robot_protocols.py`` that implements the following. Each class and method must include type hints and a Google-style docstring. 1. **``Executable`` protocol** (``@runtime_checkable``): - ``execute(self, robot_name: str) -> bool`` 2. **``PickTask``** (no shared base class with ``DeliverTask``): - ``execute(self, robot_name: str) -> bool`` that prints ``" picks an object"`` and returns ``True``. 3. **``DeliverTask``** (no shared base class with ``PickTask``): - ``execute(self, robot_name: str) -> bool`` that prints ``" delivers to destination"`` and returns ``True``. 4. **``SleepTask``**: a class with no ``execute()`` method (add ``sleep(self, duration: float) -> None`` instead). Used to confirm that it does not satisfy ``Executable``. 5. **``dispatch(task: Executable, robot_name: str) -> bool``**: a module-level function that calls ``task.execute(robot_name)`` and returns its result. 6. In the ``if __name__ == "__main__"`` block: .. code-block:: python if __name__ == "__main__": pick = PickTask() deliver = DeliverTask() sleep = SleepTask() dispatch(pick, "Scout") dispatch(deliver, "Arm-1") # isinstance checks via @runtime_checkable print(isinstance(pick, Executable)) # True print(isinstance(deliver, Executable)) # True print(isinstance(sleep, Executable)) # False **Expected output:** .. code-block:: text Scout picks an object Arm-1 delivers to destination True True False .. raw:: html
**Deliverables** - ``lecture7/robot_protocols.py`` - The program must run without errors and produce output matching the expected format above. - Every class and method must include type hints and a Google-style docstring.