1

A Garage holds a list of Cars.
Each Car can only belong to one Garage.
Garages can add/remove cars.
Cars can move Garages.

Garages need to keep track of which Cars they have.
Cars need to store which Garage they are in.

I have three files:

car.py

import garage
class Car:
 def __init__(self, garage: garage.Garage):
 self.garage = garage
 self.garage.add_car(self)
 def print_garage(self):
 print(f"This car's garage is {self.garage}")
 def move_garage(self, to_garage: garage.Garage):
 self.garage.remove_car(self)
 self.garage = to_garage
 self.garage.add_car(self)

garage.py

import car
class Garage:
 def __init__(self):
 self.car_list = []
 def add_car(self, car: car.Car):
 self.car_list.append(car)
 def remove_car(self, car: car.Car):
 self.car_list.remove(car)

sandbox.py

from car import Car
from garage import Garage
new_garage = Garage()
new_garage2 = Garage()
new_car = Car(
 garage=new_garage
)
new_car.move_garage(
 to_garage=new_garage2
)

In its current state I get this error

Exception has occurred: AttributeError
partially initialized module 'car' has no attribute 'Car' (most likely due to a circular import)

I've tried using a whole variety of 'import car', 'from car import Car', 'from car import *' on both classes, and have tried importing them differently from within sandbox.py.

I've tried having everything in the same file, but as both Car and Garage rely on each other, this does not fly.

I'm aware circular dependencies are normally a bad thing, but I haven't managed to find an alternative that is usable for this type of project design. It seems to come up in quite a few of the projects that I work on so i'm sure there's something I'm not seeing!

Edit (wasn't clear on the question/discussion): I'm looking for a way to structure a project like this to meet the above constraints. Especially the ones that let me use the Garage type inside Car, and the Car type inside Garage. I want to do this so I can (amoung other things) ensure that arguments are of the correct type, like this for example in Garage:

if not isinstance(car, car.Car): raise Exception()
asked Nov 2, 2022 at 16:57
2
  • "I've tried having everything in the same file, but as both Car and Garage rely on each other, this does not fly.". Did you try replacing the unnecessary car: car.Car without typing? So just car, garage as a paramater name is enough. Removing those you can place it in a single file. Commented Nov 2, 2022 at 17:15
  • Yep but I need to ensure that the argument is of type Car. I can see that removing the type hinting will mean that car won't have to be imported, but I might need to add something like 'if not isinstance(car, car.Car): raise Exception()', so it would need to know what Car is :/ Commented Nov 2, 2022 at 17:31

1 Answer 1

1

All the relations between your classes are architectual concers. But in your case you can check out forward references.

From pep docs

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

That being said, you should pass your class path as literal string e.g:

class Car:
 def __init__(self, garage: 'garage.Garage'): # or 'Garage' if its in same file.
 self.garage = garage
 self.garage.add_car(self)
 # other attrs and methods..
answered Nov 2, 2022 at 17:13
Sign up to request clarification or add additional context in comments.

5 Comments

Amazing! Would there be any way to have access to the Garage type though? For instance to raise an error if an arg is supplied to Car which is not of type Garage?
Also, you've mentioned architectural concerns. I'm sure this is the overwhelming opinion as I can see that this is not how this is supposed to be achieved. Do you have any thoughts on a different way to structure the project to achieve the same goals?
Firstly, you can checktype(garage) which will give the string representation of instance that is passed. Second I was just trying to focus on the coding part of the question which I thought is your problem. Its a different topic but to me simple aggregation will do just fine in this small project you can check questions about designs or even ask it in different question(unless it exists) to see others opinions. Also dont forget the respond to answer/s if you find it helpful.
I guess the crux of the question is how to I check for the Car type within Garage AND check for the Garage type within Car. I would like to use something like type/isinstance to ENSURE that the passed argument is of the correct type, these don't accept the type-hint-style strings it seems. It feels like a basic tenant of OOP, having every class being able to interact with every other class. But I can't see how its possible implement something like this without it leading to circular imports.
To avoid circular imports, you might consider something like interfaces. In python there is also protocol which uses duck typing. Your garage has no real dependency on Car, it could also be a Vehicule (where Car is also a Vehicule).

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.