{"id":"inheritance-isinstance","slug":"isinstance","title":"Class hierarchies and isinstance","text":"Inheritance can go on for more than one level. A derived class can be the base class of another class, and a base class can have many derived classes. Together they form a **class hierarchy**, which you can draw like a family tree. Here bicycles and cars are vehicles, and an electric car is a special kind of car:\n\n```text\nVehicle\n    Bicycle\n    Car\n        ElectricCar\n```\n\nAn `ElectricCar` inherits everything from `Car`, including everything `Car` inherited from `Vehicle`:\n\n```python\nclass Vehicle:\n    def __init__(self, name):\n        self.name = name\n\n    def describe(self):\n        return f\"{self.name} moves\"\n\n\nclass Bicycle(Vehicle):\n    def describe(self):\n        return f\"{self.name} is pedaled\"\n\n\nclass Car(Vehicle):\n    def honk(self):\n        return f\"{self.name}: beep beep\"\n\n\nclass ElectricCar(Car):\n    def __init__(self, name, battery):\n        super().__init__(name)\n        self.battery = battery\n\n    def charge(self):\n        self.battery = 100\n\n\nspark = ElectricCar(\"Spark\", 35)\nprint(spark.describe())\nprint(spark.honk())\nspark.charge()\nprint(spark.battery)\n```\n\n```output\nSpark moves\nSpark: beep beep\n100\n```\n\nTo find `describe`, Python looks in `ElectricCar`, then in `Car`, then in `Vehicle`, and uses the first one it finds. `super().__init__(name)` in `ElectricCar` runs the constructor `Car` inherited, the one in `Vehicle`.\n\n### isinstance\n\nSometimes a program needs to know what kind of object it has. The function **`isinstance(value, class)`** tells you: it returns `True` if the value is an object of that class, or of a class derived from it. It fits the \"is a\" test: an electric car is a car, and it's a vehicle too. A list can hold objects of different classes of one hierarchy, and `isinstance` picks out the ones you need:\n\n```python\nclass Vehicle:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Bicycle(Vehicle):\n    pass\n\n\nclass Car(Vehicle):\n    pass\n\n\nclass ElectricCar(Car):\n    pass\n\n\nspark = ElectricCar(\"Spark\")\nprint(isinstance(spark, ElectricCar))\nprint(isinstance(spark, Car))\nprint(isinstance(spark, Vehicle))\nprint(isinstance(spark, Bicycle))\n\ngarage = [Car(\"Rusty\"), Bicycle(\"Fixie\"), spark, ElectricCar(\"Volt\")]\nfor vehicle in garage:\n    if isinstance(vehicle, Car):\n        print(vehicle.name, \"needs a parking space\")\n```\n\n```output\nTrue\nTrue\nTrue\nFalse\nRusty needs a parking space\nSpark needs a parking space\nVolt needs a parking space\n```\n\nThe electric cars count as cars, so they need a parking space too. The bicycle is a vehicle but not a car, so it's left out. Checking `type(vehicle) == Car` instead would leave out the electric cars as well: `type` gives only the object's own class, while `isinstance` follows the hierarchy.\n\n`isinstance` works with Python's own types too:\n\n```pycon\n>>> isinstance(42, int)\nTrue\n>>> isinstance(\"42\", int)\nFalse\n>>> isinstance([1, 2], list)\nTrue\n```\n\nEvery class you write is part of a hierarchy as well. A class with no base class in parentheses inherits Python's built-in class `object`. That's where the methods you never wrote come from, such as the plain `__str__` that prints an object's address.\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Media:\n    pass\n\n\nclass Video(Media):\n    pass\n\n\nclass Movie(Video):\n    pass\n\n\nclass Song(Media):\n    pass\n\n\nfilm = Movie()\ntune = Song()\nprint(isinstance(film, Video))\nprint(isinstance(film, Song))\nprint(isinstance(film, Media))\nprint(isinstance(tune, Video))\n","solution":"class Media:\n    pass\n\n\nclass Video(Media):\n    pass\n\n\nclass Movie(Video):\n    pass\n\n\nclass Song(Media):\n    pass\n\n\nfilm = Movie()\ntune = Song()\nprint(isinstance(film, Video))\nprint(isinstance(film, Song))\nprint(isinstance(film, Media))\nprint(isinstance(tune, Video))\n","hints":["Draw the hierarchy: `Video` and `Song` are derived from `Media`, and `Movie` is derived from `Video`.","`isinstance` is `True` for the object's own class and for every class above it in the hierarchy: a movie is a video and a media item, but not a song."],"tests":[{"name":"Output","stdin":"","expect":"True\nFalse\nTrue\nFalse","hidden":false}],"require":[],"forbid":[],"lines":["True","False","True","False"],"more":[{"task":"A media library holds movies, clips and songs. Movies and clips are both videos. Write a function `video_titles(items)` that gets a list of these objects and returns a list of the titles of all the videos, in the order they are in the list. For example,\n\n```python\nitems = [Movie(\"Arrival\"), Song(\"Yellow\"), Clip(\"Cat on a piano\"), Video(\"Holiday 2025\")]\nprint(video_titles(items))\n```\n\nshould print:\n\n```\n['Arrival', 'Cat on a piano', 'Holiday 2025']\n```\n","kind":"code","seed":"class Media:\n    def __init__(self, title):\n        self.title = title\n\n\nclass Video(Media):\n    pass\n\n\nclass Movie(Video):\n    pass\n\n\nclass Clip(Video):\n    pass\n\n\nclass Song(Media):\n    pass\n\n\ndef video_titles(items):\n    pass\n\n\nif __name__ == \"__main__\":\n    items = [Movie(\"Arrival\"), Song(\"Yellow\"), Clip(\"Cat on a piano\")]\n    print(video_titles(items))\n","solution":"class Media:\n    def __init__(self, title):\n        self.title = title\n\n\nclass Video(Media):\n    pass\n\n\nclass Movie(Video):\n    pass\n\n\nclass Clip(Video):\n    pass\n\n\nclass Song(Media):\n    pass\n\n\ndef video_titles(items):\n    titles = []\n    for item in items:\n        if isinstance(item, Video):\n            titles.append(item.title)\n    return titles\n\n\nif __name__ == \"__main__\":\n    items = [Movie(\"Arrival\"), Song(\"Yellow\"), Clip(\"Cat on a piano\")]\n    print(video_titles(items))\n","hints":["Go through the list with a `for` loop and collect the titles of the right items into a new list.","Movies and clips are videos, so one check is enough: `isinstance(item, Video)` is `True` for all three kinds of video.","`type(item) == Video` wouldn't do: it's `False` for a movie."],"tests":[{"name":"Test 1","stdin":"","call":"items = [Movie(\"Arrival\"), Song(\"Yellow\"), Clip(\"Cat on a piano\"), Video(\"Holiday 2025\")]\nprint(video_titles(items))\n","expect":"['Arrival', 'Cat on a piano', 'Holiday 2025']","hidden":false},{"name":"Test 2","stdin":"","call":"items = [Song(\"Clocks\"), Song(\"Fix You\"), Clip(\"Goal!\"), Song(\"Viva\"), Movie(\"Up\")]\nprint(video_titles(items))\nprint(video_titles([Song(\"Paradise\")]))\n","expect":"['Goal!', 'Up']\n[]","hidden":true}],"require":[{"pattern":"\\bisinstance\\s*\\(","message":"Check each item's kind with `isinstance`."}],"forbid":[]},{"task":"Build a small class hierarchy of your own on top of `Character`:\n\n- `Wizard` inherits `Character`. Its constructor takes a name, health and mana. Its method `spell_cost()` returns 10. Its method `cast_spell()` uses up `spell_cost()` mana and returns `NAME casts a spell`. If the wizard has less mana than a spell costs, nothing is used up and it returns `NAME is out of mana`. A wizard prints as a character does, followed by `, MANA mana`.\n- `Archmage` inherits `Wizard`. An archmage's spells cost only 5 mana: override `spell_cost()`, and nothing else.\n\nFor example,\n\n```python\nozma = Archmage(\"Ozma\", 40, 12)\nprint(ozma.cast_spell())\nprint(ozma.cast_spell())\nprint(ozma.cast_spell())\nprint(ozma)\nprint(isinstance(ozma, Character))\n```\n\nshould print:\n\n```\nOzma casts a spell\nOzma casts a spell\nOzma is out of mana\nOzma (40 health), 2 mana\nTrue\n```\n","kind":"code","seed":"class Character:\n    def __init__(self, name, health):\n        self.name = name\n        self.health = health\n\n    def __str__(self):\n        return f\"{self.name} ({self.health} health)\"\n\n\n# Write the classes Wizard and Archmage here.\n\n\nif __name__ == \"__main__\":\n    ozma = Archmage(\"Ozma\", 40, 12)\n    print(ozma.cast_spell())\n    print(ozma)\n","solution":"class Character:\n    def __init__(self, name, health):\n        self.name = name\n        self.health = health\n\n    def __str__(self):\n        return f\"{self.name} ({self.health} health)\"\n\n\nclass Wizard(Character):\n    def __init__(self, name, health, mana):\n        super().__init__(name, health)\n        self.mana = mana\n\n    def spell_cost(self):\n        return 10\n\n    def cast_spell(self):\n        if self.mana < self.spell_cost():\n            return f\"{self.name} is out of mana\"\n        self.mana -= self.spell_cost()\n        return f\"{self.name} casts a spell\"\n\n    def __str__(self):\n        return super().__str__() + f\", {self.mana} mana\"\n\n\nclass Archmage(Wizard):\n    def spell_cost(self):\n        return 5\n\n\nif __name__ == \"__main__\":\n    ozma = Archmage(\"Ozma\", 40, 12)\n    print(ozma.cast_spell())\n    print(ozma)\n","hints":["Write `Wizard` first and try it on its own: a constructor that calls `super().__init__(name, health)` and sets `self.mana`, then `spell_cost`, `cast_spell` and `__str__`.","In `cast_spell`, compare `self.mana` with `self.spell_cost()`, not with 10: then an archmage's own `spell_cost` is used automatically.","`__str__` can return `super().__str__() + f\", {self.mana} mana\"`.","`Archmage` needs only one method: `spell_cost`, returning 5. It inherits the constructor, `cast_spell` and `__str__` from `Wizard`."],"tests":[{"name":"Test 1","stdin":"","call":"ozma = Archmage(\"Ozma\", 40, 12)\nprint(ozma.cast_spell())\nprint(ozma.cast_spell())\nprint(ozma.cast_spell())\nprint(ozma)\nprint(isinstance(ozma, Character))\n","expect":"Ozma casts a spell\nOzma casts a spell\nOzma is out of mana\nOzma (40 health), 2 mana\nTrue","hidden":false},{"name":"Test 2","stdin":"","call":"elva = Wizard(\"Elva\", 30, 25)\nprint(elva.cast_spell())\nprint(elva.cast_spell())\nprint(elva.cast_spell())\nprint(elva)\nprint(elva.spell_cost())\nprint(isinstance(elva, Archmage))\n","expect":"Elva casts a spell\nElva casts a spell\nElva is out of mana\nElva (30 health), 5 mana\n10\nFalse","hidden":true},{"name":"Test 3","stdin":"","call":"rune = Archmage(\"Rune\", 12, 5)\nprint(rune.cast_spell())\nprint(rune.cast_spell())\nprint(rune)\nprint(isinstance(rune, Wizard))\n","expect":"Rune casts a spell\nRune is out of mana\nRune (12 health), 0 mana\nTrue","hidden":true}],"require":[{"pattern":"class\\s+Wizard\\s*\\(\\s*Character\\s*\\)","message":"`Wizard` should inherit `Character`."},{"pattern":"class\\s+Archmage\\s*\\(\\s*Wizard\\s*\\)","message":"`Archmage` should inherit `Wizard`."},{"pattern":"def\\s+cast_spell\\b","max":1,"message":"Write `cast_spell` only once, in `Wizard`; `Archmage` overrides only `spell_cost`."}],"forbid":[]}]}