4

I have the following code:

class Car(object):
 def __init__(self, my_id):
 self.my_id = my_id
 self.color = color
 self.brand = brand
 self.get_color()
 self.get_brand()
 def get_color():
 return requests.get('https://example.com/{}/color'.format(self.my_id))
 def get_brand():
 return requests.get('https://example.com/{}/brand'.format(self.my_id))
def get_description(car):
 return 'My {} is {}'.format(car.brand, car.color)
def get_color(car):
 return 'My car is {}'.format(car.color)
def main():
 c = Car(123)
 print get_description(c)

It works quite well by first initializing a Car object that gathers all necessary data, and then allowing me to work with that data through first class functions.

However, I'd like to move all those functions into a separate class Description. This class, because it uses a bunch of data from Car, would be initialized as:

class Description(object):
 def __init__(self, car):
 self.car = car
 self.description_text = description_text
 self.color_text = color_text

where the car instance is passed as an argument to the Description object on instantiation.

I was told, however, that this approach is not the best since the two classes are now tightly coupled.

How should this example be reworked to use two classes? Should Description inherit the data from Car? Should I instantiate Description by passing all data from Car?

Aaron Hall
6,0034 gold badges28 silver badges48 bronze badges
asked Mar 11, 2016 at 14:20
3
  • What's the API of the Description class, and how do you plan on using these classes? The obvious alternative to me is to use pure methods like Description.getColor(car) where the car is an argument and not a piece of state, but that's not always the best move. (also, resource requests are considered off-topic here, so I've edited out that last part of your question) Commented Mar 11, 2016 at 15:33
  • The planned use is to create an instance of car, which is loaded with all data. Then create a Description of said instance, and call various methods of that instance. Sorry about the resource request (and to risk another OT question, where should have I asked this?). Commented Mar 11, 2016 at 15:46
  • You can ask for resources on Reddit and other kinds of less strict Q&A sites. You can also try the site chat. By the way, how does my answer work for you? Commented Mar 11, 2016 at 15:47

1 Answer 1

3

Reworking the classes with a mixin and properties

How should this example be reworked to use two classes? Should Description inherit the data from Car? Should I instantiate Description by passing all data from Car?

This is actually a great use-case for the has-a relationship of a mixin and properties. Don't prefix methods that execute immediately with get, use @property instead.

class Description_Mixin(object):
 """mixin assumes you have a brand and color"""
 @property
 def brand_and_color_description(self):
 return 'My {} is {}'.format(self.brand, self.color)
 @property
 def type_and_color_description(self):
 return 'My {} is {}'.format(type(self).__name__.lower(), self.color)

Don't use properties for the request methods, use regular methods to signify that this is a function that can (and will) take time to complete. Here, the code semantically says that the car "has-a" description.:

class Car(Description_Mixin):
 def __init__(self, my_id):
 self.my_id = my_id
 self.color = self.get_color()
 self.brand = self.get_brand()
 def get_color(self):
 return requests.get('https://example.com/{}/color'.format(self.my_id))
 def get_brand(self):
 return requests.get('https://example.com/{}/brand'.format(self.my_id))

And usage:

def main():
 c = Car(123)
 print c.brand_and_color_description

And you can reuse your mixin for other types that need those kinds of descriptions.

Follow up - Lazy loading the attributes:

If I want to have cli options that only return specific parts of the description, there is no need to load up all the resources for Car. For example, if I want to, based on a cli option, only get a car's type_and_color_description, there is no need to call get_brand(). Is there a way to modify this code to account for that (i.e. not assume I have all the data from Car)?

This is a matter of logistics. Here's an implementation that does this, but it makes the return of the color and brand attributes slow on the first retrieve:

class Car(Description_Mixin):
 def __init__(self, my_id):
 self.my_id = my_id
 @property
 def color(self):
 try:
 return self._color
 except AttributeError:
 self._color = requests.get(
 'https://example.com/{}/color'.format(self.my_id))
 return self._color
 @property
 def brand(self)
 try:
 return self._brand
 except AttributeError:
 self._brand = requests.get(
 'https://example.com/{}/brand'.format(self.my_id)) 
 return self._brand
answered Mar 11, 2016 at 15:43
2
  • This is perfect. Thank you! And one more follow-up Q, if I want to have cli options that only return specific parts of the description, there is no need to load up all the resources for Car. For example, if I want to, based on a cli option, only get a car's type_and_color_description, there is no need to call get_brand(). Is there a way to modify this code to account for that (i.e. not assume I have all the data from Car)? Commented Mar 11, 2016 at 15:51
  • @mart1n I think I got your followup. Commented Mar 11, 2016 at 16:12

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.