eSurfsnake
eSurfsnake

Reputation: 689

How to avoid creating a class attribute by accident

I know the motto is "we're all consenting adults around here."

but here is a problem I spent a day on. I got passed a class with over 100 attributes. I had specified one of them was to be called "run_count". The front-end had a place to enter run_count.

Somehow, the front-end/back-end package people decided to call it "run_iterations" instead.

So, my problem is I am writing unit test software, and I did this:

passed_parameters.run_count = 100
result = do_the_thing(passed_parameters)
assert result == 99.75

Now, the problem, of course, is that Python willingly let me set this "new" attribute called "run_count". But, after delving 10 levels down into the code, I discover that the function "do_the_thing" (obviously) never looks at "run_count", but uses "passed_paramaters.run_iterations" instead.

Is there some simple way to avoid allowing yourself to create a new attribute in a class, or a new entry in a dictionary, when you naievely assume you know the attribute name (or the dict key), and accidentally create a new entry that never gets looked at?

In an ideal world, no matter how dynamic, Python would allow you to "lock" and object or instance of one. Then, trying to set a new value for an attribute that doesn't exist would raise an attribute error, letting you know you are trying to change something that doesn't exist, rather than letting you create a new attribute that never gets used.

Upvotes: 1

Views: 200

Answers (1)

JoshuaCS
JoshuaCS

Reputation: 2624

Use __setattr__, and check the attribute exists, otherwise, throw an error. If you do this, you will receive an error when you define those attributes inside __init__, so you have to workaround that situation. I found 4 ways of doing that. First, define those attributes inside the class, that way, when you try to set their initial value they will already be defined. Second, call object.__setattr__ directly. Third, add a fourth boolean param to __setattr__ indicating whether to bypass checking or not. Fourth, define the previous boolean flag as class-wide, set it to True, initialize the fields and set the flag back to False. Here is the code:

Code

class A:
  f = 90
  a = None

  bypass_check = False

  def __init__(self, a, b, c, d1, d2, d3, d4):
    # 1st workaround
    self.a = a

    # 2nd workaround
    object.__setattr__(self, 'b', b)

    # 3rd workaround
    self.__setattr__('c', c, True)

    # 4th workaround
    self.bypass_check = True 
    self.d1 = d1
    self.d2 = d2
    self.d3 = d3
    self.d4 = d4
    self.bypass_check = False

  def __setattr__(self, attr, value, bypass=False):
    if bypass or self.bypass_check or hasattr(self, attr):
      object.__setattr__(self, attr, value)
    else:
      # Throw some error
      print('Attribute %s not found' % attr)

a = A(1, 2, 3, 4, 5, 6, 7)
a.f = 100
a.d1 = -1
a.g = 200
print(a.f, a.a, a.d1, a.d4)

Output

Attribute g not found
100 1 -1 7

Upvotes: 5

Related Questions