< BACKMake Note | BookmarkCONTINUE >
156135250194107072078175030179198180025031194137176049106218111004231200013193077175125255

Instance Attributes

Instances have only data attributes (methods are strictly class attributes) and are simply data values which you want to be associated with a particular instance of any class and are accessible via the familiar dotted-attribute notation. These values are independent of any other instance or of the class it was instantiated from. When an instance is deallocated, so are its attributes.

"Instantiating" Instance Attributes (or, Creating a Better Constructor)

Instance attributes can be set any time after an instance has been created, in any piece of code that has access to the instance. However, one of the key places where such attributes are set is in the constructor, __init__().

Constructor First Place to Set Instance Attributes

The constructor is the earliest place that instance attributes can be set because __init__() is the first method called after instance objects have been created. There is no earlier opportunity to set instance attributes. Once __init__() has finished execution, the instance object is returned, completing the instantiation process.

Default Arguments Provide Default Instance Setup

One can also use __init__() along with default arguments to provide an effective way in preparing an instance for use in the real world. In many situations, the default values represent the most common cases for setting up instance attributes, and such use of default values precludes them from having to be given explicitly to the constructor. We also outlined some of the general benefits of default arguments in Section 11.5.2.

Example 13.1 shows how we can use the default constructor behavior to help us calculate some sample total room costs for lodging at hotels in some of America's large metropolitan areas.

The main purpose of our code is to help someone figure out the daily hotel room rate, including any state sales and room taxes. The default is for the general area around San Francisco, which has an 8.5% sales tax and a 10% room tax. The daily room rate has no default value, thus it is required for any instance to be created.

The setup work is done after instantiation by __init__() in lines 4–8, and the other core part of our code is the calcTotal() method, lines 10–14. The job of __init__() is to set the values needed to determine the total base room rate of a hotel room (not counting room service, phone calls, or other incidental items). calcTotal() is then used to either determine the total daily rate or for an entire stay if the number of days is provided. The round() built-in function is used to round the calculation to the closest penny (two decimal places). Here is some sample usage of this class:

Example 13.1. Using Default Arguments with Instantiation (hotel.py)

Class definition for a fictitious hotel room rate calculator. The __init__() constructor method initializes several instance attributes. A calcTotal() method is used to determine either a total daily room rate, or the total room cost for an entire stay.

 <$nopage>
001 1  class HotelRoomCalc:
002 2      'Hotel room rate calculator'
003 3
004 4      def __init__(self, rt, sales=0.085, rm=0.1):
005 5          '''HotelRoomCalc default arguments:
006 6          sales tax == 8.5% and room tax == 10%'''
007 7          self.salesTax = sales
008 8          self.roomTax = rm
009 9          self.roomRate = rt
010 10
011 11     def calcTotal(self, days=1):
012 12         'Calculate total; default to daily rate'
013 13         daily = round((self.roomRate * \
014 14             (1 + self.roomTax + self.salesTax)), 2)
015 15         return float(days) * daily
016  <$nopage>
							
>>> sfo = HotelRoomCalc(299)                     # new instance
>>> sfo.calcTotal()                              # daily rate
354.32
>>> sfo.calcTotal(2)                             # 2-day rate
708.64
>>> sea = HotelRoomCalc(189, 0.086, 0.058)       # new instance
>>> sea.calcTotal()
216.22
>>> sea.calcTotal(4)
864.88
>>> wasWkDay = HotelRoomCalc(169, 0.045, 0.02)   # new instance
>>> wasWkEnd = HotelRoomCalc(119, 0.045, 0.02)   # new instance
>>> wasWkDay.calcTotal(5) + wasWkEnd.calcTotal() # 7-day rate
1026.69

						

The first two hypothetical examples were San Francisco, which used the defaults, and then Seattle, where we provided different sales tax and room tax rates. The final example, Washington, D.C., extended the general usage by calculating a hypothetical longer stay: a five-day weekday stay plus a special rate for one weekend day, assuming a Sunday departure to return home.

Do not forget that all the flexibility you get with functions, such as default arguments, apply to methods as well. The use of variable-length arguments is another good feature to use with instantiation (based on an application's needs, of course).

Constructor Should Return None

As you are now aware, invoking a class object with the function operator creates a class instance, which is the object returned on such an invocation, as in the following example:

							
>>> class MyClass:
…        pass
>>> myInstance = MyClass()
>>> myInstance
<__main__.MyClass instance at 95d390>

						

If a constructor is defined, it should not return any object because the instance object is automatically returned after the instantiation call. Correspondingly, __init__() should not return any object (or return None); otherwise, there is a conflict of interest because only the instance should be returned. Attempting to return any object other than None will result in a TypeError exception:

							
>>> class MyClass:
…   def __init__(self):
…       print 'initialized'
…       return 1
…
>>> myInstance = MyClass()
initialized
Traceback (innermost last):
  File "<stdin>", line 1, in ?
    myInstance = MyClass()
TypeError: __init__() should return None

						

Determining Instance Attributes

The dir() built-in function can be used to show all instance attributes in the same manner that it can reveal class attributes:

						
>>> class C:
…        pass
>>> c = C()
>>> c.foo = 'roger'
>>> c.bar = 'shrubber'
>>> dir(c)
['bar', 'foo']

					

Similar to classes, instances also have a __dict__ special attribute (also accessible by calling vars() and passing it an instance), which is a dictionary representing its attributes:

						
>>> c.__dict__
{'foo': 'roger', 'bar': 'shrubber'}

					

Special Instance Attributes

Instances have only two special attributes (see Table 13.2). For any instance I:

Table 13.2. Special Instance Attributes

I.__class__

class from which I is instantiated

I.__dict__

attributes of I

We will now take a look at these special instance attributes using the class C and its instance c:

						
>>> class C:     # define class
…     pass
…
>>> c = C()                    # create instance
>>> dir(c)                     # instance has no attributes
[]
>>> c.__dict__                 # yep, definitely no attributes
{}
>>> c.__class__                # class that instantiated us
<class __main__.C at 948230>

					

As you can see, c currently has no data attributes; but we can add some and recheck the __dict__ attribute to make sure they have been added properly:

						
>>> c.foo = 1
>>> c.bar = 'SPAM'
>>> '%d can of %s please' % (c.foo, c.bar)
'1 can of SPAM please'
>>> dir(c)
['bar', 'foo']
>>> c.__dict__
{'foo': 1, 'bar': 'SPAM'}

					

The __dict__ attribute consists of a dictionary containing the attributes of an instance. The keys are the attribute names, and the values are the attributes' corresponding data values. You will only find instance attributes in this dictionary—no class attributes nor special attributes.

NOTE

Although the __dict__ attributes for both classes and instances are mutable, it is recommended that you not modify these dictionaries unless or until you know exactly what you are doing. Such modification contaminates your OOP and may have unexpected side effects. It is more acceptable to access and manipulate attributes using the familiar dotted-attribute notation. One of the few cases where you would modify the __dict__ attribute directly is when you are overriding the __setattr__ special method. Implementing __setattr__() is another adventure story on its own, full of traps and pitfalls such as infinite recursion and corrupted instance objects—but that is another tale for another time.


Built-in Type Attributes

Built-in types also have attributes, and although they are technically not class instance attributes, they are sufficiently similar to get a brief mention here. Type attributes do not have an attribute dictionary like classes and instances (__dict__), so how do we figure out what attributes built-in types have? The convention for built-in types is to use two special attributes, __methods__ and __members__, to outline any methods and/or data attributes. Complex numbers are one example of a built-in type with both methods and attributes, so we will use its __methods__ and __members__ to help us hunt down its attributes:

						
>>> aComplex = (1+2j)    # create a complex number
>>> type(aComplex)       # display its type
<type 'complex'>
>>> aComplex.__members__ # reveal its data attributes
['imag', 'real']
>>> aComplex.__methods__ # reveal its methods
['conjugate']

					

Now that we know what kind of attributes a complex number has, we can access the data attributes and call its methods:

						
>>> aComplex.imag
2.0
>>> aComplex.real
1.0
>>> aComplex.conjugate()
(1-2j)

					

Attempting to access __dict__ will fail because that attribute does not exist for built-in types:

						
>>> aComplex.__dict__
Traceback (innermost last):
  File "<stdin>", line 1, in ?
AttributeError: __dict__

					

Our final remark for this section is to note that the __members__ and __methods__ special attributes is simply a convention. New types defined in external or third-party extension modules may not choose to implement them, although it is highly recommended.

Instance Attributes vs. Class Attributes

We first described class data attributes in Section 13.4.1. As a brief reminder, class attributes are simply data values associated with a class and not any particular instances like instance attributes are. Such values are also referred to as static members because their values stay constant, even if a class is invoked due to instantiation multiple times. No matter what, static members maintain their values independent of instances unless explicitly changed. Comparing instance attributes to class attributes is almost like the comparison between automatic and static variables, if you are familiar with these concepts from other languages.

There are a few aspects of class attributes versus instance attributes that should be brought to light. The first is that you can access a class attribute with either the class or an instance, provided that the instance does not have an attribute with the same name.

Access to Class Attributes

Class attributes can be accessed via a class or an instance. In the example below, when class C is created with the version class attribute, naturally access is allowed using the class object, i.e., C.version. When instance c is created, Python provides a default, read-only instance attribute which is an alias to the class attribute, i.e., c.version:

							
>>> class C:                       # define class
…       version = 1.0              # static member
…
>>> c = C()                        # instantiation
>>> C.version                      # access via class
1.0
>>> c.version                      # access via instance
1.0
>>> C.version = C.version + .1     # update (only) via class
>>> C.version                      # class access
1.1
>>> c.version                      # instance access, which
1.1                                # also reflected change

						

However, access via to a class attribute via an instance attribute is strictly read-only (see below for what happens if you try to update one), so we can only update the value when referring to it using the class, as in the C.version increment statement above. Attempting to set or update the class attribute using the instance name is not allowed and will create an instance attribute.

Assignment Creates Local Instance Attribute

Any type of assignment of a local attribute will result in the creation and assignment of an instance attribute, just like a regular Python variable. If a class attribute exists with the same name, it is overridden in the instance:

							
>>> dir(C)
['__doc__', '__module__', 'version']
>>> dir(c)
[]
>>> c.version = 100      # attempt to update class attr
>>> c.version
100
>>> C.version            # nope, class attr unchanged
1.1
>>> dir(c)               # confirm new instance attr created
['version']

						

In the above code snippet, a new instance attribute named version is created, overriding the reference to the class attribute. However, the class attribute itself is unscathed and still exists in the class domain and can still be accessed as a class attribute, as we can see above.

To confirm that a new instance attribute was added, the call to dir() in the above code snippet reveals no attributes for instance c, while class C had three attributes (__doc__, __module__, and version). Calling dir() again on c after the assignment yields one new attribute, version.

What would happen if we delete this new reference? To find out, we will use the del statement on c.version.

							
>>> del c.version        # delete instance attribute
>>> dir(c)
[]
>>> c.version            # can now access class attr again
1.1

						

Now let us try to update the class attribute again, but this time, we will just try an innocent increment:

							
>>> c.version = c.version + 1.0
>>> c.version
2.1
>>> dir(c)
['version']
>>> C.version
1.1

						

It is still a "no go." We again created a new instance attribute while leaving the original class attribute intact. The expression on the right-hand side of the assignment evaluates the original class variable, adds 1.0 to it, and assigns it to a newly-created instance attribute. Note that the following is an equivalent assignment, but it may perhaps provide more clarification:

							
c.static = C.static + 1.0
						
Class Attributes More Persistent

Static members, true to their name, hang around while instances (and their attributes) come and go (hence independent of instances). Also, if a new instance is created after a class attribute has been modified, the updated value will be reflected:

							
>>> class C:
…       spam = 100           # class attribute
…
>>> c1 = C()                  # create an instance
>>> c1.spam                   # access class attr thru inst.
100
>>> C.spam = C.spam + 100     # update class attribute
>>> C.spam                    # see change in attribute
200
>>> c1.spam                   # confirm change in attribute
200
>>> c2 = C()                  # create another instance
>>> c2.spam                   # verify class attribute
200
>>> del c1                    # remove one instance
>>> C.spam = C.spam + 200     # update class attribute again
>>> c2.spam                   # verify that attribute changed
400

						


Last updated on 9/14/2001
Core Python Programming, © 2002 Prentice Hall PTR

< BACKMake Note | BookmarkCONTINUE >

© 2002, O'Reilly & Associates, Inc.