{ "cells": [ { "cell_type": "markdown", "id": "7b79a60a", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# DS2500 Lesson10\n", "\n", "Feb 14, 2023 <3\n", "\n", "### Content:\n", "- inheritance\n", " - this is the big one for today\n", "- polymorphism\n", "\n", "### Admin:\n", "- hw2 due Friday\n", "- lab3 due tomorrow \n", " - lab digest tomorrow @ 1030\n", " - any quick questions?\n" ] }, { "cell_type": "markdown", "id": "568e219d", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Review: Class Method & Class Attributes\n", "\n", "They're associated with the whole class, not any particular instances.\n", "\n", "- example **class method**: `TimeDelta.from_string()`\n", " - builds a new `TimeDelta` from a string\n", " - its a class method because its not associated with any particular `TimeDelta` object\n", " \n", " \n", " \n", "- example **class attribute**: `SillyClass.how_many`\n", " - this attribute counts how many total instances of `SillyClass` have been built\n", " - its a class attribute because its not associated with any particular `SillyClass` object\n", "\n" ] }, { "cell_type": "markdown", "id": "76bbd7d8", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Purely hypothetically speaking (on a purely hypothetical hw2 ...) \n", "You're tasked with building a `MonopolyPropertyHand` \n", "- tracks an individual players monopoly properties\n", "\n", "Where would you store information about how many properties of each group (e.g. Dark Purple, Light Blue, Purple, Orange) are required to obtain a monopoly in that group?\n", "- is the value relevant to a particular player's properties (attribute) or\n", "- is the value constant & relevant to all player's properties (class attribute)\n" ] }, { "cell_type": "markdown", "id": "e94193e5", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## In Class Activity A\n", "\n", "1. Add an `Employee.input_time()` method which adds income for time worked by an employee. \n", "2. Complete the `Employee.compute_tax()` method below. Specifications are given in the docstring below. For **every** `Employee`:\n", " - income_tax_threshold = 100\n", " - income_tax_rate = .1\n", " \n", "You are given test cases for the completed `Employee` class just below. Be sure to study these test cases to ensure you understand the expected behavior before implementing the methods.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "4e9ce3a7", "metadata": {}, "outputs": [], "source": [ "class Employee: \n", " \"\"\" stores accounting information for a single employee\n", " \n", " Attributes:\n", " name (str): employee name\n", " rate_day (float): how much money an employee makes in a day\n", " income (float): total amount owed to the employee\n", " \"\"\"\n", " # see compute_tax() method for details\n", " income_tax_threshold = 100\n", " income_tax_rate = .1\n", "\n", " def __init__(self, name, rate_day, income=0):\n", " self.name = name\n", " self.rate_day = rate_day\n", " self.income = income\n", " \n", " def __repr__(self):\n", " return f'Employee(name={self.name}, rate_day={self.rate_day}, income={self.income})'\n", " \n", " def input_time(self, day):\n", " \"\"\" adds income for time worked by an employee\n", " \n", " Args:\n", " day (float): number of days worked by employee\n", " \"\"\"\n", " self.income += day * self.rate_day\n", " \n", " def compute_tax(self):\n", " \"\"\" computes taxes the employee owes\n", " \n", " - no taxes are paid on the first Employee.income_tax_threshold \n", " dollars an employee makes.\n", " - any more income is taxed at a rate of Employee.income_tax_rate\n", " \n", " For example, when income_tax_threshold =100 and income_tax_rate=.1:\n", " - an employee whose income is 40 has a tax of 0\n", " - an employee whose income is 101 has a tax of .1\n", " - (1 dollar above threshold taxed at a rate of .1)\n", " \n", " Returns:\n", " tax (float): how much tax should be paid by employee\n", " \"\"\"\n", " taxable_income = max(self.income - Employee.income_tax_threshold, 0)\n", " return taxable_income * Employee.income_tax_rate" ] }, { "cell_type": "markdown", "id": "33c01210", "metadata": {}, "source": [ "# Test Cases: `Employee`\n", "\n", "These test cases travel with a herd of giraffes:\n", "\n", "🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "f5c64f9b", "metadata": {}, "outputs": [], "source": [ "# tests: __init__() and __repr__()\n", "bob_employee = Employee(name='Bob Lastname', rate_day=3)\n", "assert str(bob_employee) == 'Employee(name=Bob Lastname, rate_day=3, income=0)'\n", "\n", "# test: input_time()\n", "bob_employee.input_time(day=100)\n", "assert str(bob_employee) == 'Employee(name=Bob Lastname, rate_day=3, income=300)'\n", "\n", "# test: compute_tax()\n", "assert bob_employee.compute_tax() == 20\n", "\n", "# test: compute_tax where income is less than taxable threshold\n", "mo_employee = Employee(name='Mo Lastname', rate_day=10) \n", "assert mo_employee.compute_tax() == 0\n", "\n", "# test: input_time with a different rate\n", "mo_employee.input_time(day=18)\n", "assert str(mo_employee) == 'Employee(name=Mo Lastname, rate_day=10, income=180)'\n" ] }, { "cell_type": "markdown", "id": "9874b5a8", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# How can we extend the `Employee` class?\n", "\n", "We want to make a similar class `EmployeeCommission` which manages an employee who\n", "- has all the attributes and methods of `Employee` above\n", "- also has income because they make a percentage of all the sales they complete\n", " - e.g. agents at a car dealership or real estate agents\n", " \n", "Looks like we'll need to add a few things to the `Employee` class...\n", "\n", "```python\n", "\"\"\"\n", "Attributes:\n", " rate_sale (float): percentage of a sale an employee makes as\n", " commission. (e.g. if rate_sale=.2, then employee makes\n", " 20% of their sales as income)\n", "\"\"\"\n", "```\n", "\n", "and a method:\n", "\n", "```python\n", "def input_sales(self, sales):\n", " \"\"\" add commission income to employee due to sales\n", "\n", " Args:\n", " sales (float): total amount sold by employee\n", " \"\"\"\n", " self.income += sales * self.rate_sale\n", "```\n", "\n", "\n" ] }, { "cell_type": "markdown", "id": "6597133c", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Lets do this a *not-so-great way at first...\n", "\n", "... copy-pasting our way to the finish line!\n", "\n", "*please don't actually write code like this\n", "\n", "#### note to self (and students): \n", "Don't forget to modify `__repr__()` to reflect that this is a new class `EmployeeCommissionWET`\n", "- (I'll explain the 'WET' acronym shortly)\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "7d65ac6c", "metadata": {}, "outputs": [], "source": [ "class EmployeeCommissionWET: \n", " \"\"\" stores accounting information for a single employee\n", " \n", " Attributes:\n", " name (str): employee name\n", " rate_day (float): how much money an employee makes in a day\n", " income (float): total amount owed to the employee\n", " rate_sale (float): percentage of a sale an employee makes as\n", " commission. (e.g. if rate_sale=.2, then employee makes\n", " 20% of their sales as income)\n", " \"\"\"\n", " # see compute_tax() method for details\n", " income_tax_threshold = 100\n", " income_tax_rate = .1\n", "\n", " def __init__(self, name, rate_day, rate_sale, income=0):\n", " self.name = name\n", " self.rate_day = rate_day\n", " self.income = 0\n", " self.rate_sale = rate_sale\n", " \n", " def __repr__(self):\n", " return f'EmployeeCommissionWET(name={self.name}, rate_day={self.rate_day}, rate_sale={self.rate_sale}, income={self.income})'\n", " \n", " def input_sales(self, sales):\n", " \"\"\" add commission income to employee due to sales\n", "\n", " Args:\n", " sales (float): total amount sold by employee\n", " \"\"\"\n", " self.income += sales * self.rate_sale\n", "\n", " def input_time(self, day):\n", " \"\"\" adds income for time worked by an employee\n", " \n", " Args:\n", " day (float): number of days worked by employee\n", " \"\"\"\n", " self.income += day * self.rate_day\n", " \n", " def compute_tax(self):\n", " \"\"\" computes taxes the employee owes\n", " \n", " - no taxes are paid on the first Employee.income_tax_threshold \n", " dollars an employee makes.\n", " - any more income is taxed at a rate of Employee.income_tax_rate\n", " \n", " For example, when income_tax_threshold =100 and income_tax_rate=.1:\n", " - an employee whose income is 40 has a tax of 0\n", " - an employee whose income is 101 has a tax of .1\n", " - (1 dollar above threshold taxed at a rate of .1)\n", " \n", " Returns:\n", " tax (float): how much tax should be paid by employee\n", " \"\"\"\n", " taxable_income = max(self.income - EmployeeCommissionWET.income_tax_threshold, 0)\n", " return taxable_income * EmployeeCommissionWET.income_tax_rate" ] }, { "cell_type": "code", "execution_count": null, "id": "086dd305", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "cd7835eb", "metadata": {}, "source": [ "# Test Cases: `EmployeeCommission`\n", "\n", "These test cases travel around with a horde of zombies:\n", "\n", "🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟\n" ] }, { "cell_type": "code", "execution_count": 23, "id": "d177d9f9", "metadata": {}, "outputs": [], "source": [ "com_employee = EmployeeCommissionWET(name='a-car-salesman', rate_day=1, rate_sale=.5)\n", "assert str(com_employee) == 'EmployeeCommissionWET(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=0)'\n", "\n", "# they have income due to commission sales\n", "com_employee.input_sales(sales=100)\n", "assert str(com_employee) == 'EmployeeCommissionWET(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=50.0)'\n", "\n", "# and they also do all the things a normal Employee can do too!\n", "com_employee.input_time(day=100)\n", "assert str(com_employee) == 'EmployeeCommissionWET(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=150.0)'\n", "assert com_employee.compute_tax() == 5" ] }, { "cell_type": "markdown", "id": "ff92e691", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Why is repeating code a bad idea?\n", "\n", "1. Its complex:\n", " - Bugs hide in complexity\n", "1. Its invites ambiguity\n", " - Which, of these two identical methods, am I running again?\n", "1. We'll eventually need to change this repeated code and when we do we'll forget to change it in both places\n", "1. Requires readers read twice as much software to understand the same thing\n", "1. Code size grows quickly with every repetition of a repetition ... quickly gets out of hand!\n", "\n", "## DRY Principle: \"Don't Repeat Yourself\" **\n", "\n", " Every part of your program (e.g. a piece of information or behavior) should be stored in one place.\n", "\n", "[Wikipedia has a few other fun acronymns like DRY:](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)\n", "- a fun acronym / metaphor but a terrible idea : \"WET\"\n", " - Write Everything Twice\n", " - We Enjoy Typing\n", " - Waste Everyone's Time\n" ] }, { "cell_type": "markdown", "id": "dab9e32b", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### So how can we avoid repeating `Employee` code when building `EmployeeCommission`?\n", "\n", "# Inheritance (making a subclass)\n", "\n", "We can define a new class definition which \"inherits\" all the attributes / methods of another. \n", "\n", "In particular the `EmployeeCommission` class has access to all the attributes:\n", "- name\n", "- rate_day\n", "- income\n", "\n", "and methods \n", "- `__init__()`\n", "- `__repr__()`\n", "- `input_time()`\n", "- `compute_tax()`\n", "\n", "of the `Employee` class.\n" ] }, { "cell_type": "code", "execution_count": 24, "id": "5d74b2c3", "metadata": {}, "outputs": [], "source": [ "class EmployeeCommission(Employee):\n", " pass\n" ] }, { "cell_type": "markdown", "id": "3217a2cf", "metadata": {}, "source": [ "This statement will have the format:\n", "\n", "```python\n", "class SubClass(SuperClass):\n", "\n", "```\n", "\n", "where\n", "\n", "\n", "- a **super class** is the class which is inherited from \n", " - e.g. `Employee`\n", " - also known as: base class, parent class\n", "- a **sub class** is the class which inherits the behavior\n", " - e.g. `EmployeeCommission`\n", " - also known as: child class\n", " \n", " \n" ] }, { "cell_type": "markdown", "id": "df98dd79", "metadata": {}, "source": [ "# Test Cases: `Employee`\n", "\n", "Here they are again, along with their giraffes:\n", "\n", "🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒🦒\n", "\n", "The only difference is that we're applying them to a an `EmployeeCommission` instead of an `Employee`\n", "\n", "#### notice: \n", "since we inherit the `__repr__()` of `Employee`, the string representation isn't quite appropriate for our new `EmployeeCommission` class, we'll need to fix this somehow\n" ] }, { "cell_type": "code", "execution_count": 25, "id": "03ae2390", "metadata": {}, "outputs": [], "source": [ "# tests: __init__() and __repr__()\n", "bob_employee = EmployeeCommission(name='Bob Lastname', rate_day=3)\n", "assert str(bob_employee) == 'Employee(name=Bob Lastname, rate_day=3, income=0)'\n", "\n", "# test: input_time()\n", "bob_employee.input_time(day=100)\n", "assert str(bob_employee) == 'Employee(name=Bob Lastname, rate_day=3, income=300)'\n", "\n", "# test: compute_tax()\n", "assert bob_employee.compute_tax() == 20\n", "\n", "# test: compute_tax where income is less than taxable threshold\n", "mo_employee = Employee(name='Mo Lastname', rate_day=10) \n", "assert mo_employee.compute_tax() == 0\n", "\n", "# test: input_time with a different rate\n", "mo_employee.input_time(day=18)\n", "assert str(mo_employee) == 'Employee(name=Mo Lastname, rate_day=10, income=180)'\n" ] }, { "cell_type": "code", "execution_count": null, "id": "c2594101", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "227745aa", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# The subclass may include its own attributes / methods too\n" ] }, { "cell_type": "code", "execution_count": 31, "id": "a5d905b0", "metadata": {}, "outputs": [], "source": [ "class EmployeeCommission(Employee):\n", " \"\"\" subclass of Employee, also manages income due to sales / commission\n", " \n", " Attributes:\n", " rate_sale (float): percentage of a sale an employee makes as\n", " commission. (e.g. if rate_sale=.2, then employee makes\n", " 20% of their sales as income)\n", " \"\"\"\n", " def __init__(self, name, rate_day, rate_sale, income=0):\n", " # these 3 lines are a bit redundant, right? (see Employee.__init__())\n", " # (more to come on this later)\n", " self.name = name\n", " self.rate_day = rate_day\n", " self.income = income\n", " \n", " self.rate_sale = rate_sale\n", " \n", " # for teaching purposes only\n", " print('This is EmployeeCommission.__init__()')\n", " \n", " def __repr__(self):\n", " return f'EmployeeCommission(name={self.name}, rate_day={self.rate_day}, rate_sale={self.rate_sale}, income={self.income})'\n", " \n", " def input_sales(self, sales):\n", " \"\"\" add commission income to employee due to sales\n", " \n", " Args:\n", " sales (float): total amount sold by employee\n", " \"\"\"\n", " self.income += sales * self.rate_sale\n" ] }, { "cell_type": "markdown", "id": "a04771d2", "metadata": {}, "source": [ "## Subclass methods...\n", "- are only available to the subclass (not the superclass)\n", "- will \"replace\" the corresponding superclass, if they have the same name\n", " - e.g. `__init__()` and `__repr__()` above\n" ] }, { "cell_type": "code", "execution_count": 29, "id": "43d8198a", "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Employee' object has no attribute 'input_sales'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[29], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Employee has no method `input_sales()`, but its subclass EmployeeCommission does\u001b[39;00m\n\u001b[1;32m 2\u001b[0m some_employee \u001b[38;5;241m=\u001b[39m Employee(name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124masdf\u001b[39m\u001b[38;5;124m'\u001b[39m, rate_day\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m \u001b[43msome_employee\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minput_sales\u001b[49m(\u001b[38;5;241m100\u001b[39m)\n", "\u001b[0;31mAttributeError\u001b[0m: 'Employee' object has no attribute 'input_sales'" ] } ], "source": [ "# Employee has no method `input_sales()`, but its subclass EmployeeCommission does\n", "some_employee = Employee(name='asdf', rate_day=1)\n", "some_employee.input_sales(100)\n" ] }, { "cell_type": "code", "execution_count": 32, "id": "44979b92", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is EmployeeCommission.__init__()\n" ] }, { "data": { "text/plain": [ "EmployeeCommission(name=asdf, rate_day=1, rate_sale=0.4, income=0)" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# calling EmployeeCommission uses the \"replaced\" constructor EmployeeCommission.__init__()\n", "some_employee_com = EmployeeCommission(name='asdf', rate_day=1, rate_sale=.4)\n", "some_employee_com\n" ] }, { "cell_type": "code", "execution_count": 35, "id": "f2dec47a", "metadata": {}, "outputs": [], "source": [ "some_employee_com.input_time(10)" ] }, { "cell_type": "code", "execution_count": 33, "id": "cb155944", "metadata": {}, "outputs": [], "source": [ "# calling Employee.__init__() does not access EmployeeCommission.__init__()\n", "some_employee = Employee(name='asdf', rate_day=1)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4a0dd822", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b7dc18eb", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "249ee943", "metadata": {}, "source": [ "# Test Cases: `EmployeeComission`\n", "\n", "🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟🧟\n" ] }, { "cell_type": "code", "execution_count": 34, "id": "2fb5b006", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is EmployeeCommission.__init__()\n" ] } ], "source": [ "com_employee = EmployeeCommission(name='a-car-salesman', rate_day=1, rate_sale=.5)\n", "assert str(com_employee) == 'EmployeeCommission(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=0)'\n", "\n", "# they have income due to commission sales\n", "com_employee.input_sales(sales=100)\n", "assert str(com_employee) == 'EmployeeCommission(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=50.0)'\n", "\n", "# and they also do all the things a normal Employee can do too!\n", "com_employee.input_time(day=100)\n", "assert str(com_employee) == 'EmployeeCommission(name=a-car-salesman, rate_day=1, rate_sale=0.5, income=150.0)'\n", "assert com_employee.compute_tax() == 5\n" ] }, { "cell_type": "markdown", "id": "5cd5037f", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Specifying a particular class's method explicitly:\n", "\n", "Given `Employee.__init__()` below:\n", "```python\n", "class Employee: \n", " def __init__(self, name, rate_day, income=0):\n", " self.name = name\n", " self.rate_day = rate_day\n", " self.income = income\n", "```\n", "\n", "should we be \"repeating ourself\" in `EmployeeCommission.__init__()`?\n", "\n", "```python\n", "class EmployeeCommission(Employee)\n", " def __init__(self, name, rate_day, rate_sale, income=0):\n", " # these 3 lines are a bit redundant, right? (see Employee.__init__())\n", " self.name = name\n", " self.rate_day = rate_day\n", " self.income = income\n", " \n", " self.rate_sale = rate_sale\n", "```\n", "\n", "### You can specify a particular class which \"owns\" your method via `Employee.__init__()`\n" ] }, { "cell_type": "code", "execution_count": 24, "id": "352d0476", "metadata": {}, "outputs": [], "source": [ "class EmployeeCommission(Employee):\n", " \"\"\" subclass of Employee, also manages income due to sales / commission\n", " \n", " Attributes:\n", " rate_sale (float): percentage of a sale an employee makes as\n", " commission. (e.g. if rate_sale=.2, then employee makes\n", " 20% of their sales as income)\n", " \"\"\"\n", " def __init__(self, name, rate_day, rate_sale, income=0): \n", " # notice: to call __init__ with this syntax, we pass self\n", " Employee.__init__(self=self, name=name, rate_day=rate_day, income=income)\n", " \n", " self.rate_sale = rate_sale\n", " \n", " def __repr__(self):\n", " return f'EmployeeCommission(name={self.name}, rate_day={self.rate_day}, rate_sale={self.rate_sale}, income={self.income})'\n", " \n", " def input_sales(self, sales):\n", " \"\"\" add commission income to employee due to sales\n", " \n", " Args:\n", " sales (float): total amount sold by employee\n", " \"\"\"\n", " self.income += sales * self.rate_sale\n" ] }, { "cell_type": "markdown", "id": "8cc70f44", "metadata": {}, "source": [ "### (++)\n", "\n", "Its a bit better to use `super()` in place of the particular super class:\n", "- e.g.use `super().__init__()` instead of `Employee.__init__()`\n", " - in this case, they do the same thing\n", "- [here's why](https://stackoverflow.com/questions/222877/what-does-super-do-in-python-difference-between-super-init-and-expl)\n", "- it likely won't be a problem for you in the near future (certainly DS2500) ... so we simplify a bit\n" ] }, { "cell_type": "markdown", "id": "dd93e6e3", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# In Class Activity B\n", "\n", "1. Complete the `EmployeeWithActive` class definition below by:\n", " - adding a `activate()` method which sets attribute `active=True`\n", " - adding a `deactivate()` method which sets attribute `active=False`\n", " - adding a `EmployeeWithActive.input_time()` method which `assert()`s `active=True` before inputting time\n", " - we shouldn't repeat the code in `Employee.input_time()` ...\n", " - ... is there a way we can specify a method from a particular class?\n", " \n", "2. Ensure your code works by writing a few quick test cases which validate the behavior\n", " \n", "3. Check-your-understanding questions:\n", " - After defining `EmployeeWithActive`, does `Employee` now have attribute `active`? Why or why not?\n", " - From your implementation of `EmployeeWithActive`:\n", "```python\n", "employee_with_active = EmployeeWithActive(name='asdf', rate_day=1)\n", "employee_with_active.input_time(day=10)\n", "```\n", " - how/when is `EmployeeWithActive.input_time()` called immediately above?\n", " - how/when is `Employee.input_time()` called immediately above?\n" ] }, { "cell_type": "code", "execution_count": 37, "id": "2e4e3b23", "metadata": {}, "outputs": [], "source": [ "class EmployeeWithActive(Employee):\n", " \"\"\" subclass of Employee, also includes active state\n", " \n", " checks to ensure only active employees `input_time()`, otherwise\n", " throws an error\n", " \n", " Attributes:\n", " active (bool): True if employee is active\n", " \"\"\"\n", " def __init__(self, name, rate_day, active=True, income=0): \n", " # notice: to call __init__ with this syntax, we pass self\n", " Employee.__init__(self=self, name=name, rate_day=rate_day, income=income)\n", " \n", " self.active = active\n", " \n", " def __repr__(self):\n", " return f'EmployeeWithActive(name={self.name}, rate_day={self.rate_day}, active={self.active}, income={self.income})'\n", " \n", " def activate(self):\n", " \"\"\" activates employee \"\"\"\n", " self.active = True\n", " \n", " def deactivate(self):\n", " \"\"\" deactivates employee \"\"\"\n", " self.active = False\n", " \n", " def input_time(self, day):\n", " assert self.active, 'inative employees cannot input new time'\n", " return Employee.input_time(self, day)\n", " \n" ] }, { "cell_type": "code", "execution_count": 40, "id": "ab8b51a9", "metadata": {}, "outputs": [], "source": [ "x = EmployeeWithActive('asdf', rate_day=10)" ] }, { "cell_type": "code", "execution_count": 41, "id": "3da2de7e", "metadata": {}, "outputs": [], "source": [ "x.deactivate()" ] }, { "cell_type": "code", "execution_count": 42, "id": "50eec03d", "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "inative employees cannot input new time", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[42], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minput_time\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m12\u001b[39;49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[37], line 28\u001b[0m, in \u001b[0;36mEmployeeWithActive.input_time\u001b[0;34m(self, day)\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minput_time\u001b[39m(\u001b[38;5;28mself\u001b[39m, day):\n\u001b[0;32m---> 28\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mactive, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124minative employees cannot input new time\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 29\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m Employee\u001b[38;5;241m.\u001b[39minput_time(\u001b[38;5;28mself\u001b[39m, day)\n", "\u001b[0;31mAssertionError\u001b[0m: inative employees cannot input new time" ] } ], "source": [ "x.input_time(12)" ] }, { "cell_type": "markdown", "id": "5048c6b8", "metadata": {}, "source": [ "(Ramp up to polymorphism ...)\n", "\n", "# We can store a function as a variable\n" ] }, { "cell_type": "code", "execution_count": 45, "id": "eeb876b7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def double_it(x):\n", " return x * 2\n", "\n", "def triple_it(x):\n", " return x * 3\n", "\n", "this_is_a_variable = double_it\n", "\n", "this_is_a_variable\n" ] }, { "cell_type": "code", "execution_count": 46, "id": "ae1cdd7b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "20" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "this_is_a_variable(10)\n" ] }, { "cell_type": "code", "execution_count": 28, "id": "3e4a90b2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "this_is_a_variable = double_it\n", "this_is_a_variable\n" ] }, { "cell_type": "code", "execution_count": 29, "id": "2bb8ae6f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "20" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "this_is_a_variable(10)\n" ] }, { "cell_type": "markdown", "id": "86c44052", "metadata": {}, "source": [ "### Notice\n", "\n", "The reason we can \"get away\" with swapping `double_it()` and `triple_it()` is that they have the same inputs and outputs.\n" ] }, { "cell_type": "markdown", "id": "a0cf1b54", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Why would you want to store a function as a variable?\n", "\n", "We can pass a function around as another variable. Consider the `play()` function below, which plays two players against each other in our triple-or-nothing game:\n", "\n", "```python\n", "def play(fnc0, fnc1, jar_init=1, max_round=10):\n", " \"\"\" plays a game of triple or nothing \n", " \n", " Args:\n", " fnc0 (fnc): triple or nothing strategy (player0)\n", " fnc1 (fnc): triple or nothing strategy (player1)\n", " jar_init (float): initial jar\n", " max_round (int): maximum round to play before \n", " stopping game\n", " \n", " Returns:\n", " df (pd.DataFrame): dataframe describing round\n", " by round of triple or nothing \n", " \"\"\"\n", " # init\n", " jar = jar_init\n", " coin_total0 = 0\n", " coin_total1 = 0\n", " frac_hist0 = list()\n", " frac_hist1 = list()\n", " \n", " row_dict_list = list()\n", " for round_idx in range(max_round):\n", " \n", " ######### NOTICE BELOW: ################\n", " # fnc0 and fnc1 \"play\" triple or nothing\n", " ########################################\n", " # get frac per player\n", " frac0, name0 = fnc0(jar=jar, \n", " round_idx=round_idx, \n", " frac_hist_opp=frac_hist1, \n", " frac_hist_self=frac_hist0)\n", " frac1, name1 = fnc1(jar=jar, \n", " round_idx=round_idx, \n", " frac_hist_opp=frac_hist0, \n", " frac_hist_self=frac_hist1)\n", " ######### NOTICE END ####################\n", " #########################################\n", " \n", " # update jar & assign coins per round\n", " jar, new_coin0, new_coin1 = update_round(jar, frac0, frac1)\n", " \n", " # update coin_total\n", " coin_total0 += new_coin0\n", " coin_total1 += new_coin1\n", " \n", " # update history\n", " frac_hist0.append(frac0)\n", " frac_hist1.append(frac1)\n", " \n", " # build row of output dataframe\n", " row_dict = pd.Series({'round_idx': round_idx,\n", " 'frac0': frac0,\n", " 'frac1': frac1,\n", " 'new_coin0': new_coin0,\n", " 'new_coin1': new_coin1,\n", " 'coin_total0': coin_total0,\n", " 'coin_total1': coin_total1})\n", " row_dict_list.append(row_dict)\n", " \n", " if jar == 0:\n", " # game ends early\n", " break\n", " \n", " # aggregate rows into dataframe\n", " df = pd.concat(row_dict_list, axis=1).transpose()\n", " df['name0'] = name0\n", " df['name1'] = name1\n", " \n", " return df\n", "```\n", "\n", "# Notice:\n", "The strategies might be different, but the inputs and the outputs are the same. Every possible `fnc0` or `fnc1` must obey the interface of the `triple_nothing_player()` below:\n", "\n", "```python\n", "def triple_nothing_player(jar, round_idx, frac_hist_opp, frac_hist_self):\n", " \"\"\" produces a fraction in a game of triple or nothing\n", " \n", " frac_hist objects are lists of previous fraction history in\n", " the game. For example, frac_hist = [0, .01, .2] indicates\n", " a player had fraction 0 in the first round, .01 in the second\n", " and .2 in the third\n", " \n", " Args:\n", " jar (float): value of the jar in current round\n", " round_idx (int): current round index (0 for\n", " first round, 9 for final round)\n", " frac_hist_opp (list): a history of all fractions input\n", " by opposing player\n", " frac_hist_self (list): a history of all fractions input \n", " by self\n", " \n", " Returns:\n", " frac (float): fraction for current round\n", " name (str): a unique name given to your strategy\n", " \"\"\"\n", " # dummy fnc (defines interface)\n", " pass\n", "```\n" ] }, { "cell_type": "markdown", "id": "2585116a", "metadata": {}, "source": [ "# Polymorphism\n", "\n", "(greek for \"many-shaped\")\n", "\n", "By keeping the inputs / outputs constant we can utilize corresponding functions (or methods in different classes).\n", "\n", "#### Polymorphism example from hw3:\n", "Notice that even though objects in `shape_tup` have more than 1 class (`Circle` and `Rectangle`), each class has a `.plot()` method with the same interface (no inputs or outputs). How convenient!\n", "\n", "```python\n", "# define shapes\n", "circ0 = Circle(radius=3)\n", "circ1 = Circle(pos_tuple=(4, 4), radius=2, color='b', alpha=.4)\n", "rect0 = Rectangle(pos_tuple=(-5, -1), height=8, width=2, color='g', alpha=.4)\n", "rect1 = Rectangle(pos_tuple=(-6, -5), height=11, width=9, color='r', alpha=.2)\n", "\n", "# collect them in a tuple \n", "shape_tup = circ0, circ1, rect0, rect1\n", "\n", "# build new figure and plot each shape\n", "fig, ax = get_new_figure()\n", "for shape in shape_tup:\n", " shape.plot(ax)\n", "```\n", "\n", "\n" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 5 }