19

I am new to Python having come from mainly Java programming.

I am currently pondering over how classes in Python are instantiated.

I understand that __init__(): is like the constructor in Java. However, sometimes python classes do not have an __init__() method which in this case I assume there is a default constructor just like in Java?

Another thing that makes the transition from Java to python slightly difficult is that in Java you have to define all the instance fields of the class with the type and sometimes an initial value. In python all of this just seems to disappear and developers can just define new fields on the fly.

For example I have come across a program like so:

class A(Command.UICommand):
    FIELDS = [
        Field( 'runTimeStepSummary', BOOL_TYPE)
        ]

    def __init__(self, runTimeStepSummary=False):
        self.runTimeStepSummary = runTimeStepSummary

    """Other methods"""

    def execute(self, cont, result):
        self.timeStepSummaries = {}
        """ other code"""

The thing that confuses (and slightly irritates me) is that this A class does not have a field called timeStepSummaries yet how can a developer in the middle of a method just define a new field? or is my understanding incorrect?

So to be clear, my question is in Python can we dynamically define new fields to a class during runtime like in this example or is this timeStepSummaries variable an instance of a java like private variable?

EDIT: I am using python 2.7

David Moles
  • 48,006
  • 27
  • 136
  • 235
SeekingAlpha
  • 7,489
  • 12
  • 35
  • 44
  • 2
    Presumably the methods that appear to be missing are defined on the parent class. And pythons scoping rules are vastly different from Java's. Anyone can just declare a new variable or field and it will be created (in most cases). This is all stuff that's pretty basic to python so I would definitely recommend reading a book about it. Personally I used "dive in to python." Just Google around for python introductions. Good luck! – scott_fakename Feb 02 '14 at 23:41
  • 1
    @scott_fakename: Thank you for the book reference. – SeekingAlpha Feb 02 '14 at 23:46
  • classes work rather differently in python 2 vs 3 so you should specify which we're talking about here. – roippi Feb 03 '14 at 00:06
  • @roippi: I am using python 2.7. This is now added in my original question above. – SeekingAlpha Feb 03 '14 at 00:09

3 Answers3

26

I understand that __init__(): is like the constructor in Java.

To be more precise, in Python __new__ is the constructor method, __init__ is the initializer. When you do SomeClass('foo', bar='baz'), the type.__call__ method basically does:

def __call__(cls, *args, **kwargs):
    instance = cls.__new__(*args, **kwargs)
    instance.__init__(*args, **kwargs)
    return instance

Generally, most classes will define an __init__ if necessary, while __new__ is more commonly used for immutable objects.

However, sometimes python classes do not have an init() method which in this case I assume there is a default constructor just like in Java?

I'm not sure about old-style classes, but this is the case for new-style ones:

>>>> object.__init__
<slot wrapper '__init__' of 'object' objects>

If no explicit __init__ is defined, the default will be called.

So to be clear, my question is in Python can we dynamically define new fields to a class during runtime like in this example

Yes.

>>> class A(object):
...     def __init__(self):
...         self.one_attribute = 'one'
...     def add_attr(self):
...         self.new_attribute = 'new'
...

>>> a = A()
>>> a.one_attribute
'one'
>>> a.new_attribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'new_attribute'
>>> a.add_attr()
>>> a.new_attribute
'new'

Attributes can be added to an instance at any time:

>>> a.third_attribute = 'three'
>>> a.third_attribute
'three'

However, it's possible to restrict the instance attributes that can be added through the class attribute __slots__:

>>> class B(object):
...     __slots__ = ['only_one_attribute']
...     def __init__(self):
...         self.only_one_attribute = 'one'
...     def add_attr(self):
...         self.another_attribute = 'two'
...

>>> b = B()
>>> b.add_attr()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in add_attr
AttributeError: 'B' object has no attribute 'another_attribute'

(It's probably important to note that __slots__ is primarily intended as a memory optimization - by not requiring an object have a dictionary for storing attributes - rather than as a form of run-time modification prevention.)

Matthew Trevor
  • 14,354
  • 6
  • 37
  • 50
7

Attributes of Python objects are generally stored in a dictionary, just like the ones you create with {}. Since you can add new items to a dictionary at any time, you can add attributes to an object at any time. And since any type of object can be stored in a dictionary without previous declaration of type, any type of object can be stored as an attribute of an object.

In short, my_object.abc = 42 is (often) just a shorthand for my_object.__dict__["abc"] = 42.

It is possible to define objects without a __dict__ by defining the __slots__ attribute, or to override certain special methods and store attributes in some other way, though most of the time you shouldn't do that.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • It isn't really true that setting an attribute is "just a shorthand" for modifying the dict. Data descriptors take precedence over instance attributes, which requires a full search of the `__mro__` before it can move on to the instance dict (if present). Also, the dict of a class is wrapped by a proxy that prevents direct modification, so the statement is untrue of objects in general. – Eryk Sun Feb 03 '14 at 05:44
  • You are technically correct... the best kind of correct. I'll cop to oversimplifying for the sake of a newbie. – kindall Feb 03 '14 at 15:53
  • I figured that's what you were doing, since I'm sure you know Python very well. I'm just OCD enough to think it needs the footnote. – Eryk Sun Feb 03 '14 at 23:12
0

This answer pertains to new-style Python classes, which subclass object. New-style classes were added in 2.2, and they're the only kind of class available in PY3.

>>> print object.__doc__
The most base type

The class itself is an instance of a metaclass, which is usually type:

>>> print type.__doc__
type(object) -> the object's type
type(name, bases, dict) -> a new type

Per the above docstring, you can instantiate the metaclass directly to create a class:

>>> Test = type('Test', (object,), {'__doc__': 'Test class'})
>>> isinstance(Test, type)
True
>>> issubclass(Test, object)
True
>>> print Test.__doc__
Test class

Calling a class is handled by the metaclass __call__ method, e.g. type.__call__. This in turn calls the class __new__ constructor (typically inherited) with the call arguments in order to create an instance. Then it calls __init__, which may set instance attributes.

Most objects have a __dict__ that allows setting and deleting attributes dynamically, such as self.value = 10 or del self.value. It's generally bad form to modify an object's __dict__ directly, and actually disallowed for a class (i.e. a class dict is wrapped to disable direct modification). If you need to access an attribute dynamically, use the built-in functions getattr, setattr, and delattr.

The data model defines the following special methods for customizing attribute access: __getattribute__, __getattr__, __setattr__, and __delattr__. A class can also define the descriptor protocol methods __get__, __set__, and __delete__ to determine how its instances behave as attributes. Refer to the descriptor guide.

When looking up an attribute, object.__getattribute__ first searches the object's class and base classes using the C3 method resolution order of the class:

>>> Test.__mro__
(<class '__main__.Test'>, <type 'object'>)

Note that a data descriptor defined in the class (e.g. a property or a member for a slot) takes precedence over the instance dict. On the other hand, a non-data descriptor (e.g. a function) or a non-descriptor class attribute can be shadowed by an instance attribute. For example:

>>> Test.x = property(lambda self: 10)
>>> inspect.isdatadescriptor(Test.x)
True
>>> t = Test()
>>> t.x
10
>>> t.__dict__['x'] = 0
>>> t.__dict__
{'x': 0}
>>> t.x
10

>>> Test.y = 'class string'
>>> inspect.isdatadescriptor(Test.y)
False
>>> t.y = 'instance string'
>>> t.y
'instance string'

Use super to proxy attribute access for the next class in the method resolution order. For example:

>>> class Test2(Test):
...     x = property(lambda self: 20)
... 
>>> t2 = Test2()
>>> t2.x
20
>>> super(Test2, t2).x
10
Eryk Sun
  • 33,190
  • 5
  • 92
  • 111