3

I've just started dipping my feet into OOP.

Is it considered bad practice to have classes that reference attributes that depend on another function being called and thus may not exist (version 1)? I'm self-taught so trying to gauge what is considered better quality. Any mistakes are easily fixed by just making sure I call functions in order?

I could make the function add_extra_attribute explicitly take a variable (something like version 2), but this would be more verbose and making add_attribute and add_new_attribute work together could be more confusing.

Cheers for reading!

Version 1

class test:
    def __init__(self):
        self.attribute = 'hello'
    def add_attribute(self):
        self.new_attribute = self.attribute + 'there'
    def add_extra_attribute(self):
        self.extra_attribute = self.new_attribute + 'you'
t = test()
t.add_attribute() #if I comment this out it will AttributeError
t.add_extra_attribute()

Version 2

class test:
    def __init__(self):
        self.attribute = 'hello'
    def add_attribute(self):
        self.new_attribute = self.attribute + 'there'
    def add_extra_attribute(self, new_attribute):
        self.extra_attribute = self.attribute + new_attribute + 'you'
t = test()
t.add_attribute()
t.add_extra_attribute('there')
2
  • 4
  • 1
    Generally, don't do this for reasons others explained. But, I'd like to add, don't think of classes/objects as of data bags (things where you can store and access some data) - think of them rather as of little bundles of related functions, or maybe as of little computers that do some small computation for you, or are able to figure out some small problem, or do some small management task. You don't use them so much because of the data they store, but because of what they can do - on their own, and when composed together. Jul 28, 2022 at 1:40

3 Answers 3

5

This is called temporal coupling1, 2, 3. Call the methods in the correct order and everything is fine. But nothing in the API hints that this is needed so this design is most effective at hazing the new coder.

If torturing the younger generation isn't for you then please do subject us to the more verbose option that makes it's dependencies clear. Some might argue that the entire practice of functional programming exists just to squeeze these evil timing issues out of our code.

You can fix this even in OOP centric languages. Just make us pass in what is needed. Sometimes that's called reference passing. Sometimes people want to feel fancy and call it Dependency Injection. Whatever you call it I like it when error messages tell me a clear story about what I forgot.

5

Dynamic creation of attributes is not a bad practice "per se". It only becomes a bad practice when one uses this technique for no good reason.

When you are going to add some attributes to an object dynamically, you will usually have cases when they are added, and when they are not - otherwise you would have already added them all in the constructor. Now any code that deals with those extra attributes will be required to check if the attribute is there or not, which will complicate things, especially when compared to code which can safely assume the existence of certain attributes.

When you have some "optional" data to be managed in an object, the more obvious way is to add, for example, a dynamic container like a dictionary as an attribute in the constructor and keep the optional values in there. This way, your code makes the distinction between static and dynamic attributes explicit, which increases clarity and hence decreases the chance for errors.

Nevertheless there are certainly use cases for dynamic attributes, like objects which are generated according to some external meta data source, and which will then have to be processed using further techniques like introspection and/or reflection. However, these are advanced techniques, and when you are an OOP beginner trying to implement some standard applications based on standard OOP modeling, I would recommend to start your initial design without them.

For more background information, I recommend this 11 years old SO post:

Is adding attributes dynamically frowned upon in Python?

3

I think @candied_orange's answer already touches the core - using a variable before it is initialized is asking for trouble.

There are (at least) two common ways of dealing with this:

  1. Initialize all attributes in the constructor, so there can't be uninitialized variables. This is often the cleanest approach.
  2. Use lazy initialization. When you always access an attribute through an accessor method, this method can check whether it is initialized and set a default value if not. This is especially nice to do in Python where accessor methods can mimic attribute access.

However, if you have true temporal coupling this in itself might not help, as the second method may depend on the first one being called before. The best option is to avoid such constructs. If you need to populate an object at the start of its lifecycle, you might want to consider a builder instead which takes the attribute values (in arbitrary order) and populates the real object in the correct order in the final build() step.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.