3

Why is this piece of code allowed, and is it good practice to use it? I've had plenty of Java experience, making initializing variables in Python look very informal.

class MyClass(object):
    def __init__(object):
        pass            #no mention of x here
    def setX(self,x):
        self.x = x      #why is this allowed
    def getX(self):
        return self.x
user217285
  • 346
  • 3
  • 16
  • I think you're going to have to be a bit more specific here... What exactly are you struggling with? Are you wondering *why* you can add an attribute to a class instance when it wasn't "declared" in the constructor? (Note, some would even argue that `__init__` isn't actually a constructor at all ... I think they'd call it an "initializer") – mgilson Aug 05 '15 at 05:51
  • Yes, that is exactly my question. I feel as if this is very bad programming practice, because the other methods will not know whether $x$ is defined or not. Sorry, I was trying to keep the question as short as possible and let the code/comments do the speaking. – user217285 Aug 05 '15 at 05:55
  • @Nitin: you are correct in feeling that! Subjective of course, some tools like Pylint will give a warning or error if a class accesses members that are not "formally" initialized in `__init__` – mike3996 Aug 05 '15 at 06:07
  • @Nitin -- But "Why is it allowed" and "Is it a good idea" are very different questions :-) – mgilson Aug 05 '15 at 06:13

4 Answers4

5

The question "why is this allowed?" is best answered by "why would it not be allowed?" In general, any Python object can have an attribute of any name assigned to it at any time. Unlike in languages like Java, where an object's full memory layout needs to be known at compile time, in Python object attributes are stored in dictionaries. Dictionaries can hold any number of items, so there is nothing to keep you from assigning to any attribute at any time. __init__() is special in that it is executed upon instantiation, but it in no way defines the set of attribute names permitted on an object.

There is a way to prevent willy-nilly attribute assignment, and that is with the __slots__ attribute. However, this is primarily for memory optimization; the inability to add arbitrary attributes is a side effect. And it still doesn't keep you from initializing attributes outside __init__():

class MyClass(object):
    __slots__ = ("x",)    # x is the only attribute allowed
    def setX(self,x):
        self.x = x
    def getX(self):
        return self.x

You can also write a custom __setattr__ method to filter out attributes you don't want to allow, but this is easily bypassed and in general is contrary to the Python philosophy of "we're all adults here."

It is poor programming practice to have a method that relies on state that's not available upon instantiation, but you seem to understand that.

kindall
  • 178,883
  • 35
  • 278
  • 309
1

In a lot of ways, python's a very simple language. It has no special magic that prevents you from being able to set attributes at specific times. If you want that sort of magic, you need to build it yourself.

In a lot of ways, you can begin to think of class instances (and classes themselves) as wrappers around dictionaries. In fact, both classes and instances have a __dict__ attribute:

>>> class Foo(object):
...   x = 'bar'
... 
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 'x': 'bar', '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
>>> Foo().__dict__
{}

Why it works.

Setting an attribute on a class instance actually calls the "magic" __setattr__ hook method (if present). If not present, python simply stores the value in the object's dictionary. Since the default is just a dictionary, you can set any value in the dict at any time.

Is it a good idea?

Of course, whether or not it's a good idea boils down to your design philosophy. It's usually best to put some sort of default for the value in the initializer so that the rest of your code doesn't have to guard against AttributeError that could be raised if you called getX before setX was called...


I will say that it is generally not idiomatic to write simple getters and setters in python. In your code, it's most idiomatic just to have an attribute x:

class MyClass(object):
    """This is my class.

    Attributes:
        x: the "x" attribute.
    """
    def __init__(self, x=None):
        self.x = x

Usage looks like:

m = MyClass('foo')
print(m.x)
m.x = 'bar'
print(m.x)

I think that the usual Java argument for setters and getters here is "what if you want to do other actions when you set a value -- Sure, you don't need that now, but maybe you will someday in the future ..."

Actually, Python's got you covered there too. You can always change your simple attribute into a property without breaking your interface:

class MyClass(object):
    def __init__(self, x=None):
        self._x = x
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value

And now you have all the "advantages" of a java getter/setter pair, but to an outside user, your interface hasn't changed:

m = MyClass('foo')
print(m.x)
Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
1

As said, unless you use slots it's perfectly allowed to add or remove attributes (aka fields) from an instance at any time. The simplest way to "declare" attributes is to use the __slots__ mechanism.

Whether this is a good or bad practice is a matter of opinion.

However there's another questionable construct in your code. The def __init__(object) will hide the global name object which normally is a type, but in this case will become the instance to be initialized (remember that python is very permissive - it will not force you to call the instance a method is called for for self, object is silently accepted as a name for that).

mgilson
  • 300,191
  • 65
  • 633
  • 696
skyking
  • 13,817
  • 1
  • 35
  • 57
0

In python the init method is known as an operator overloading which Python calls automatically each time an instance of your class is created so the code inside of the init is run each time a new instance is created.

So your example could be like this:

class MyClass:
    def __init__(self, x):
        self.x = x

    def setX(self, x):
        pass

    def getX(self):
        return self.x


X = MyClass('test')
print(X.getX())
Jean Guzman
  • 2,162
  • 1
  • 17
  • 27
  • Yes, I realize the code could be 'better', but I purposely wrote it like that to emphasize the initialization of the variable outside the __init__ method. Is that common? – user217285 Aug 05 '15 at 05:56
  • Nop, is not that common. In python if you want to initialize a variable at instance creation, you do it inside the __init__ method. – Jean Guzman Aug 05 '15 at 06:01