{"id":"objects-as-attributes-imports","slug":"imports","title":"When you need an import","text":"In the challenges on this site, all the classes of a program are usually in one file, `main.py`. A class can use any other class in the same file just by its name. **You don't need an `import` for a class in the same file.**\n\nA common mistake is to copy an `import` line from an example where the classes were in separate files. Python then looks for a file that isn't there, and stops before it even gets to your classes:\n\n```python\nfrom author import Author\n\n\nclass Author:\n    def __init__(self, name):\n        self.name = name\n\n\nprint(Author(\"Ana Lind\").name)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 1, in <module>\n    from author import Author\nModuleNotFoundError: No module named 'author'\n```\n\nThe fix is to delete the `import` line: the class `Author` is right there in the file.\n\n### Classes in a file of their own\n\nAs a program grows, its classes often move into files of their own. Each file is a module, just like the modules you wrote yourself in part 7, and a file that uses a class from another file has to import it. Here the class `Product` is in the file `shop.py`, and `main.py` imports it:\n\n```python\nfrom shop import Product\n\ncart = [Product(\"Tea\", 3.5), Product(\"Bread\", 2.25)]\nfor item in cart:\n    print(item)\n\n# ==== shop.py ====\nclass Product:\n    def __init__(self, name, price):\n        self.name = name\n        self.price = price\n\n    def __str__(self):\n        return f\"{self.name}: {self.price}\"\n```\n\n```output\nTea: 3.5\nBread: 2.25\n```\n\n`from shop import Product` means: in the file `shop.py`, find the name `Product`. Without that line, `main.py` would stop with `NameError: name 'Product' is not defined`, because the class isn't in `main.py`.\n\n### Python's own modules\n\nClasses from Python's standard library live in other files too, so they need an import as well. An object from the `datetime` module can be an attribute like any other object:\n\n```python\nfrom datetime import date\n\n\nclass Loan:\n    def __init__(self, title, due):\n        self.title = title\n        self.due = due\n\n    def is_late(self, today):\n        return today > self.due\n\n\nloan = Loan(\"World Atlas\", date(2026, 3, 14))\nprint(loan.due)\nprint(loan.is_late(date(2026, 3, 10)))\nprint(loan.is_late(date(2026, 3, 20)))\n```\n\n```output\n2026-03-14\nFalse\nTrue\n```\n\nSo the rule is short: you need an `import` for code that is in another file, either a module of Python's own or a file of yours. A class in the same file never needs one.\n","task":"The program below has everything in one file, but it stops with a `ModuleNotFoundError`. Fix it so that it prints:\n\n```\nHamlet: row 7, seat 15\n```\n","kind":"code","seed":"from seat import Seat\n\n\nclass Seat:\n    def __init__(self, row, number):\n        self.row = row\n        self.number = number\n\n\nclass Ticket:\n    def __init__(self, show, seat):\n        self.show = show\n        self.seat = seat\n\n    def __str__(self):\n        return f\"{self.show}: row {self.seat.row}, seat {self.seat.number}\"\n\n\nprint(Ticket(\"Hamlet\", Seat(7, 15)))\n","solution":"class Seat:\n    def __init__(self, row, number):\n        self.row = row\n        self.number = number\n\n\nclass Ticket:\n    def __init__(self, show, seat):\n        self.show = show\n        self.seat = seat\n\n    def __str__(self):\n        return f\"{self.show}: row {self.seat.row}, seat {self.seat.number}\"\n\n\nprint(Ticket(\"Hamlet\", Seat(7, 15)))\n","hints":["Read the error message. Which line does it point at, and which file is Python looking for?","The class `Seat` is in this same file, so the program doesn't need to import it. Delete the `import` line."],"tests":[{"name":"Output","stdin":"","expect":"Hamlet: row 7, seat 15","hidden":false}],"require":[],"forbid":[{"pattern":"\\bimport\\b","message":"Everything is in this one file, so the program needs no `import`."}],"more":[{"task":"This program has two files. The classes are in `garden.py`, and `main.py` uses them, but `main.py` stops with a `NameError`. Add what `main.py` is missing (and don't change `garden.py`), so the program prints:\n\n```\nHerb bed: Basil, Mint, Thyme\n```\n","kind":"code","seed":"# ==== main.py ====\nbed = Bed(\"Herb bed\")\nbed.plant(Plant(\"Basil\"))\nbed.plant(Plant(\"Mint\"))\nbed.plant(Plant(\"Thyme\"))\nprint(bed)\n\n# ==== garden.py ====\nclass Plant:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Bed:\n    def __init__(self, name):\n        self.name = name\n        self.plants = []\n\n    def plant(self, plant):\n        self.plants.append(plant)\n\n    def __str__(self):\n        names = []\n        for plant in self.plants:\n            names.append(plant.name)\n        return f\"{self.name}: {', '.join(names)}\"\n","solution":"# ==== main.py ====\nfrom garden import Bed, Plant\n\nbed = Bed(\"Herb bed\")\nbed.plant(Plant(\"Basil\"))\nbed.plant(Plant(\"Mint\"))\nbed.plant(Plant(\"Thyme\"))\nprint(bed)\n\n# ==== garden.py ====\nclass Plant:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Bed:\n    def __init__(self, name):\n        self.name = name\n        self.plants = []\n\n    def plant(self, plant):\n        self.plants.append(plant)\n\n    def __str__(self):\n        names = []\n        for plant in self.plants:\n            names.append(plant.name)\n        return f\"{self.name}: {', '.join(names)}\"\n","hints":["The classes `Bed` and `Plant` are in another file, `garden.py`, so `main.py` has to import them.","One line at the top of `main.py` can import both: `from garden import ...`, with the two names separated by a comma.","`from garden import Bed, Plant`. (Or `import garden`, and then write `garden.Bed` and `garden.Plant`.)"],"tests":[{"name":"Output","stdin":"","expect":"Herb bed: Basil, Mint, Thyme","hidden":false}],"require":[],"forbid":[]},{"task":"A gym membership is valid until its expiry date. Write the class `Membership`:\n\n- The constructor takes the member's name and the expiry date (a `date` object) and keeps them in the attributes `name` and `expires`.\n- `valid_on(self, day)` returns `True` if the membership is valid on the given day (the expiry day itself still counts), and `False` otherwise.\n- `days_left(self, day)` returns the number of days from the given day to the expiry date (0 on the expiry day itself).\n\n`date` comes from Python's `datetime` module, so your program needs an import. For example, this code:\n\n```python\ncard = Membership(\"Sami\", date(2026, 6, 30))\nprint(card.valid_on(date(2026, 6, 1)))\nprint(card.days_left(date(2026, 6, 1)))\nprint(card.valid_on(date(2026, 7, 1)))\n```\n\nprints:\n\n```\nTrue\n29\nFalse\n```\n","kind":"code","seed":"class Membership:\n    pass\n\n\nif __name__ == \"__main__\":\n    card = Membership(\"Sami\", date(2026, 6, 30))\n    print(card.valid_on(date(2026, 6, 1)))\n    print(card.days_left(date(2026, 6, 1)))\n    print(card.valid_on(date(2026, 7, 1)))\n","solution":"from datetime import date\n\n\nclass Membership:\n    def __init__(self, name, expires):\n        self.name = name\n        self.expires = expires\n\n    def valid_on(self, day):\n        return day <= self.expires\n\n    def days_left(self, day):\n        return (self.expires - day).days\n\n\nif __name__ == \"__main__\":\n    card = Membership(\"Sami\", date(2026, 6, 30))\n    print(card.valid_on(date(2026, 6, 1)))\n    print(card.days_left(date(2026, 6, 1)))\n    print(card.valid_on(date(2026, 7, 1)))\n","hints":["Start the program with `from datetime import date`, so that `date(...)` works.","Dates can be compared with `<` and `<=`: an earlier date is smaller. The membership is valid when `day <= self.expires`.","Subtracting two dates gives a time difference whose `.days` is the number of days: `(self.expires - day).days`."],"tests":[{"name":"Test 1","stdin":"","call":"card = Membership(\"Sami\", date(2026, 6, 30))\nprint(card.valid_on(date(2026, 6, 1)))\nprint(card.days_left(date(2026, 6, 1)))\nprint(card.valid_on(date(2026, 7, 1)))\n","expect":"True\n29\nFalse","hidden":false},{"name":"Test 2","stdin":"","call":"card = Membership(\"Ines\", date(2027, 1, 15))\nprint(card.name, card.expires)\nprint(card.valid_on(date(2027, 1, 15)))\nprint(card.days_left(date(2027, 1, 15)))\nprint(card.days_left(date(2026, 12, 25)))\nprint(card.valid_on(date(2027, 1, 16)))\n","expect":"Ines 2027-01-15\nTrue\n0\n21\nFalse","hidden":true}],"require":[],"forbid":[]}]}