1

My goal is to set default values of mutable type (list) for several object attributes. Per the discussion here, I set the default values of those attributes to None in the arguments of __init__, then I assign them the value [] in __init__ if they took on their default value. Here's a representation of this:

class Wat(object):
    def __init__(self, arg1=None, arg2=None, arg3=None):
        for arg in arg1, arg2, arg3:
            if arg == None:
                arg = []
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def give(self, thing):
        self.arg1.append(thing)
        self.arg2.append(thing)
        self.arg3.append(thing)

wat = Wat()
wat.give(5)

However, this throws the following exception:

AttributeError: 'NoneType' object has no attribute 'append'.

I don't see how wat.arg1, wat.arg2, and wat.arg3 can still have type None at the time wat.give(5) is called -- they should have been reassigned to [] in __init__. It must have something to do with the way I coded the assignment, because this works:

class Wat(object):
    def __init__(self, arg1=None, arg2=None, arg3=None):
        if arg1 == None:
            arg1 = []
        if arg2 == None:
            arg2 = []
        if arg3 == None:
            arg3 = []
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def give(self, thing):
        self.arg1.append(thing)
        self.arg2.append(thing)
        self.arg3.append(thing)

wat = Wat()
wat.give(5)

To my mind, the two syntaxes are functionally equivalent, but you can imagine how the latter syntax becomes very tedious and aesthetically displeasing as the number of arguments assigned in this way increases.

What's wrong with the former syntax? How can I fix it? Thanks.

EDIT: Several of you have identified the problem, but I still don't have a satisfying solution. @roippi suggested an efficient method for assigning the variables one at a time, but suppose I have 100 of these variables. How do I do this assignment in the fewest lines of code? That was the original purpose of the loop. Thanks folks.

Community
  • 1
  • 1
user3029121
  • 35
  • 1
  • 6

5 Answers5

3
for arg in arg1, arg2, arg3:
   if arg == None:
       arg = []

In this loop you're creating new references to the objects pointed by arg1, arg2, arg3 and after checking their value against None you're assigning those variables(arg) to new object, so the older objects are still unchanged. So, it is roughly equivalent to:

>>> lis = [1, 2, 3]
>>> x = lis[0]  #now `x` points to lis[0]
>>> x = []      #now `x` points to a different object, this won't affect lis[0]
>>> lis
[1, 2, 3]
>>> x = lis[1]
>>> x = []
>>> lis
[1, 2, 3]
...

Note that it is better to use arg is None rather than arg == None.

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
2

EDIT: Several of you have identified the problem, but I still don't have a satisfying solution. @roippi suggested an efficient method for assigning the variables one at a time, but suppose I have 100 of these variables. How do I do this assignment in the fewest lines of code? That was the original purpose of the loop. Thanks folks.

Whenever someone asks this question, inevitably they're using the wrong data structure.

For example, you could be using the **kwargs method to capture all keyword arguments passed to your Wat object. You can do what you want with them from there, treating kwargs as a normal dict.

class Wat(object):
    def __init__(self, **kwargs):
        self.arg1 = kwargs.get('arg1', [])
        self.arg2 = kwargs.get('arg2', [])
        self.arg3 = kwargs.get('arg3', [])

or

class Wat(object):
    def __init__(self, **kwargs):
        self.kwdict = kwargs

Or some combination of them:

class Wat(object):
    def __init__(self, **kwargs):
        self.kwdict = kwargs
        self.kwdict['arg1'] = self.kwdict.get('arg1',[])
        self.kwdict['arg2'] = self.kwdict.get('arg2',[])
        self.kwdict['arg3'] = self.kwdict.get('arg3',[])

It doesn't even seem you care about the argument names, so you could just use *args:

class Wat(object):
    def __init__(self, *args):
        self.args = list(args)
        while len(self.args) < 3:
            self.args.append([])

etc.

roippi
  • 25,533
  • 4
  • 48
  • 73
1

It's because you are assigning the empty list to the loop variable, which changes on every iteration, i.e. arg is not the same variable as arg1 yet they share the same reference at the beginning of the iteration.

Maciej Gol
  • 15,394
  • 4
  • 33
  • 51
1

If all of your variables have this same behaviour, then you can use setattr to set attributes in loop, and locals() to access your values. I use inspect to get all variables names, you can hard code them, or evaluate, or get with any reasonable mean:

import inspect

class Wat(object):
    def __init__(self, arg1=None, arg2=None, arg3=None):
        arg_spec, _v, _k, _d = inspect.getargspec(self.__init__)
        for k in arg_spec[1:]:
            value = locals()[k]
            value = [] if value is None else value
            setattr(self, k, value)

    def give(self, thing):
        self.arg1.append(thing)
        self.arg2.append(thing)
        self.arg3.append(thing)

>>> wat = Wat(arg2=[1,2])
>>> wat.give(5)
>>> wat2 = Wat()
>>> wat2.give(6)
>>> wat.__dict__
{'arg1': [5], 'arg2': [1, 2, 5], 'arg3': [5]}
>>> wat2.__dict__
{'arg1': [6], 'arg2': [6], 'arg3': [6]}
alko
  • 46,136
  • 12
  • 94
  • 102
0

How about

def default(x, fn):
    return fn() if x is None else x

class Wat(object):
    def __init__(self, arg1=None, arg2=None, arg3=None):
        self.arg1 = default(arg1, list)
        self.arg2 = default(arg2, list)
        self.arg3 = default(arg3, list)
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99