6

I want to preface this question by apologizing for its length, especially the code samples; however, I believe I have included the minimum necessary code to illustrate the differences in approaches.

I have a class with a number of instance attributes, and each of these attributes has some respective attributes.

For example, let's consider a GUI application where there are a number of attributes, and each attribute has a respective layout and field. It is often necessary to perform some action upon each layout or field as a group.

I have come up with 3 ways to organize the class which may satisfy the conditions above:

Naive Approach

class EditUserProfile(AbstractApplication):

def __init__(self, username, fav_food):
    self.username = username
    self.username_layout = Layout()
    self.username_field = Field()

    self.fav_food = fav_food
    self.fav_food_layout = Layout()
    self.fav_food_field = Field()

    self.captureUsername()
    self.updateLayouts()

def captureUsername(self):
    if __name__ == '__main__':
        if self.username_field.is_changed():
            self.username = self.username_field.text

        # Now I have to type out the mouthful `self.username_field`
        # every time I want to access the field
        # even though `self.username_field` is the only field in
        # the scope of this method.
        # I could do the same thing as the dict solution below:
        #    field = self.username_field
        # But that also kind of goes against Python convention

def updateLayouts(self):

    print("Doing something to Username Layout")
    self.username_layout.doSomething()

    print("Doing something to Favorite Food Layout")
    self.fav_food_layout.doSomething()

    # I now have to manually go through each layout and do the same thing, or I could do this:

    for attr, value in self.__dict__.viewitems()
        if attr.endswith("_layout") and isinstance(value, Layout):
            print("Doing something to %s Layout" % attr)  # Not equivalent to below answers
                                                          # Would need self.[attr]_display_name attributes
            self.value.doSomething()

Advantages and Issues with the Naive Approach

This is the simplest approach to implement, and in simple cases may be fine. In this example, which only has username and fav_food it's not a big deal. However, this approach does not scale well.

This approach leads to some serious attribute bloat, and attributes with potentially very long names.

It's also a serious hassle to iterate through all the layouts or fields. The method of iterating through __dict__ used here is dependent upon the naming of the attributes- both that all desired layouts end with _layout, and that no undesired attributes end with _layout.


Dict Approach

This is the approach that I am currently using, but I began to suspect that I might be crazy and this might be a bad solution.

class EditUserProfile(AbstractApplication):

def __init__(self, username, fav_food):
    self.username = username
    self.fav_food = fav_food

    self.layouts = {}
    self.fields = {}

    self.layouts["Username"] = Layout()
    self.layouts["Favorite Food"] = Layout()

    self.fields["Username"] = Field()
    self.fields["Favorite Food"] = Field()

    self.captureUsername()
    self.updateLayouts()

def captureUsername(self):
    field = self.fields["Username"]

    if field.is_changed():
        self.username = field.text

    # Do a bunch of other stuff with field

def updateLayouts(self):

    for key, value in self.layouts.viewitems():
        print("Doing something to %s Layout" % key)
        value.doSomething()

Advantages and Issues with the Dict Approach

I came to this approach out of a need to easily group together the related attributes, such as fields and layouts in this example.

In addition, this approach leads to nice, short names when accessing an attribute in limited scope, such as in captureUsername.

This approach allows fairly straight-forward iteration that is sure to iterate over all of and only the desired attributes.

In terms of disadvantages, this approach is, to my knowledge, fairly unconventional. It could confuse people who read the code in the future.

The method of accessing an attribute in the dict, by providing a string, is a bit un-ideal. IDE's won't catch you if you make a mistake typing the key, nor will they provide auto-completion. You have to be very diligent about naming the keys consistently, or this approach could become a mess.


Object Oriented Approach

I actually came to this approach while I was thinking about this question and typing it into StackExchange. I'm not sure why I didn't think of it before. This is probably the most conventional approach; however, I do feel it has some disadvantages over the Dict Approach.

class EditUserProfile(AbstractApplication):

def __init__(self, username, fav_food):
    self.username = UserProfileItem(username, "Username", Layout(), Field())
    self.fav_food = UserProfileItem(fav_food, "Favorite Food", Layout(), Field())

    self.captureUsername()
    self.updateLayouts()

def captureUsername(self):

    # This still feels a little verbose, but is broken up more nicely
    if self.username.field.is_changed():
        self.username.item = self.username.field.text

    # Do a bunch of other stuff with self.username.field

def updateLayouts(self):

    # You could go through the layout attributes manually,
    # or you could use the class' __dict__ attribute, as below.
    #
    # This still seems less than ideal, as you have to iterate
    # through all of the class' attributes, which could be many,
    # and then check the type.

    for attr, value in self.__dict__.viewitems():
        if isinstance(value, UserProfileItem):
            print("Doing something to %s Layout" % value.name)
            value.layout.doSomething()

class UserProfileItem(object):
    def __init__(self, item, name, layout, field):
        self.item = item
        self.name = name
        self.layout = layout
        self.field = field

Advantages and Disadvantages of the Object Oriented Approach

In an object oriented paradigm, this approach makes quite a bit of sense. An object allows you to group together related values with ease.

The Dict Approach, however, does a somewhat better job at accessing all Field or Layout attributes as a group, and iterating through them. It gives you exactly what you need, rather than iterating through all attributes of the instance, and requires less validation.


So, in conclusion, what is the preferred approach to this problem? Is my Dict approach inherently bad? Am I correct that the Object Oriented approach is preferable? Is there another approach to the problem that I haven't thought of?

  • Kudos for a well laid out question! But I doubt you will get a satisfactory answer. Form what standpoint should the approach be better or preferred? OOP advocates will probably go with the last one, developers that have spent years in procedural programming will choose something else. In the end the best approach is the one you feel most comfortable with. – Todor Todorov Jan 31 '17 at 5:45
  • 1
    One significant improvement to your dictionary approach would be defining constants for the keys and using them instead of the sting literals. That removes a lot of the negatives associated with that and also makes things easier to test (you could validate all keys are unique in a test, for example). – enderland Feb 11 '17 at 15:22
1

Objects are very helpful in user interfaces. They help represent the natural multiple aspects of each of the components (e.g. their model, view, and controller elements), as well as their connections.

So OOP is a good starting place. You've launched into it, though, giving short shrift to some key OOP principles such as inheritance and information hiding. You also don't seem to have adopted a clear user interface strategy (whether MVC or one of its latter-day alternatives such as MVP, MVVC, etc.). Some of the long class names also tend make the code more verbose than it needs to be.

You seem to be trying to sketch out both a framework and an application at the same time, which is a tall order. In practice you'd generally want to rely on existing, well-proven frameworks (e.g. WTForms) rather than build your own. But for understanding, writing out your own can be useful.

Here are some improvements I'd make as next steps to increase clarity, abstraction, and OOP organization:

class UserProfile(ProfileApp):
    # shortens the names for clarity

    def __init__(self, username, fav_food):
        super().__init__()
        # initializing superclasses can be important

        self.username = ProfileItem("Username", username)
        self.fav_food = ProfileItem("Favorite Food", fav_food)
        # use constructors with some default behaviors

        self.captureUsername()
        self.updateLayouts()

    def captureUsername(self):
        if self.username.field.is_changed():
            self.username.item = self.username.field.text

    def updateLayouts(self):
        for attr, value in self.profileItems():
            print("Doing something to %s Layout" % value.name)
            value.layout.doSomething()
        # don't deeply inspect and introspect everywhere - instead
        # use higher level abstractions

class ProfileApp(Application):

    def profileItems(self):
        for attr, value in self.__dict__.items():
            if isinstance(value, ProfileItem):
                yield attr, value

   # now a reason to have a super-class - it can do some
   # heavy lifting and help hide information, like how 
   # profile items are iterated

class ProfileItem(object):
    def __init__(self, name, value, layout=None, field=None):
        self.name = name
        self.layout = layout or Layout()
        self.field = field or Field()
        self.field.text = str(value)

    # default some parameters for clarity
    # actually transmit default values into field objects,
    # where that information will be used
  • Thanks for the answer! I apologize that the example is a bit silly. Don't worry, I'm not trying to create my own application framework, I'm actually creating a PySide/Qt application. The example given above seemed a good way to provide a minimal code example that required 0 knowledge of external libraries. – John T Jan 31 '17 at 18:18
  • And thank you for your guidance in a OOP design. I have definitely made use of inheritance where possible. One thing that I could improve upon, and that has been bothering me, is separating the business logic from the view logic (as it should be in a MVC design). PySide has brought its own challenges in properly implementing an OOP design and following Python conventions, as it has some serious quirks. For example, if a widget goes out of scope, it is automatically garbage collected. But you bring up some very good points, and I'll evaluate my code and attempt to improve the OOP structure. – John T Jan 31 '17 at 18:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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