2

I have a design issue in Python, but I'm not very advanced neither in the language, nor in design, nor in Exceptions.

Here is what I want: I have a class with attributes like name, budget, ... and I want to be impossible to create object with lets say name shorter than 3 symbols and budget < 0. Also I want when changing some of this values, they to be checked again. If you try to create an object which doesn't meet this conditions, I want exception to be thrown.

Here is what I tried:

def __init__(self, name, budget):
    try:
        self.set_name(name)
        self.set_budget(budget)
    except Exception as e:
        print(e)
        return None

def set_name(name):
    if len(name) < 3:
        raise Exception("short name")
    else:
        self.__name = name

But here I have two problems :( The first one is that even when I try to create object with the name 'a' for example it IS created :( and I don't want invalid objects to be created. The second problem is that I have print in my init method and I don't want to have any I/O functions in it. But then how to get the message? How to get from the constructor what is the reason for not creating the object?

Also, this is for a very simple task and I don't want to overdo it with sophisticated and too long and hard solution :(

Can you please help me? Thank you very much in advance! :)

Faery
  • 4,552
  • 10
  • 50
  • 92

1 Answers1

1

this is for a very simple task and I don't want to overdo it with sophisticated and too long and hard solution

This is the cause of so much gloom and doom in the IT world right now ahem Heartbleed but okay, we'll keep it simple :)

You want to confirm that len(name) >= 3 and budget >= 0, is that right? Let's do it then:

class Dummy(object):
    def __init__(self, name, budget):
        self.name = name
        self.budget = budget
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,value):
        self.validatename(value)
        self.__name = value
    @property
    def budget(self):
        return self.__budget
    @budget.setter
    def budget(self,value):
        self.validatebudget(value)
        self.__budget = value

    def validatename(self,value):
        if len(value) < 3:
            raise Exception("Some descriptive error here")
    def validatebudget(self,value):
        if isinstance(value,str):
            value = float(value) # this is more common than you'd think
        if value < 0:
            raise Exception("Some descriptive error here")

This is creating a function to validate each name and budget, which throw exceptions (intentionally uncaught in the class, though you could certainly prompt for that if you want!) if the value is wrong. This is not the best way to do it, but as per your quote at the top of my answer, it's the simplest way. It also utilizes python properties (SO question related here) to define a custom functions to handle getting and setting name and budget. Let's run an example:

>>> testobj = Dummy("Adam",100000)
# no error
>>> testobj.name
"Adam"
>>> testobj.budget
100000
>>> testobj.name = "Brian"
>>> testobj.name
"Brian"
>>> testobj.name = "Ed"
Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    testobj.name = "Ed"
  File "<pyshell#34>", line 12, in name
    self.validatename(value)
  File "<pyshell#34>", line 24, in validatename
    raise Exception("Some descriptive error here")
Exception: Some descriptive error here
Community
  • 1
  • 1
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • Thank you very much! It seems like a good solution. Only if you have the time and if you'd like to, could please tell me the best solution in your oppinion. :) I may trade simplicity for good design and elegance :) – Faery Apr 19 '14 at 19:51
  • 1
    @Faery I don't really have time to play with an implementation and I haven't used decorators enough to rattle off a solution without testing it. I would implement `validatename` and `validatebudget` as using some sort of a decorator on the `name.setter` and `budget.setter` that reprompts the user if the input isn't accepted. Might also be possible (again, I'm not very good at decorator syntax) to have a generic `validate` decorator function that accepts a `lambda` describing a filter (e.g. `lambda x: len(x) >= 3` for `validatename`). I'd play with decorators a lot with stuff like this. – Adam Smith Apr 19 '14 at 20:29
  • 1
    @Faery decorators are something a big more advanced, but probably not outside of your scope if you spend a little time researching it. If this is something you're interested in **knowing** rather than just something you're interested in **doing**, I would recommend checking it out. The `@property` line and the `@name.setter` line are examples of decorators at work. The `object` class has those two decorator functions built in and they work as described. – Adam Smith Apr 19 '14 at 20:31
  • 1
    Thank you very much! :) I don't need exact implementation, just the word decorator is enough! I will check it. :) Thank you once again for the useful advice. – Faery Apr 19 '14 at 20:34
  • 1
    @Faery I made a small change in the `__init__` function that makes the whole thing a bit easier on the eyes. I wasn't sure if it would work and didn't have time to test until now (I'm glad my untested code worked as intended!) – Adam Smith Apr 19 '14 at 20:41