Section 13.6. Instance Attributes


13.6. Instance Attributes

Instances have only data attributes (methods are strictly class attributes) and are simply data values that 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.

13.6.1. "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__().

Core Note: Instance attributes

Being able to create an instance attribute "on-the-fly" is one of the great features of Python classes, initially (but gently) shocking those coming from C++ or Java in which all attributes must be explicitly defined/ declared first.

Python is not only dynamically typed but also allows for such dynamic creation of object attributes during run-time. It is a feature that once used may be difficult to live without. Of course, we should mention to the reader that one much be cautious when creating such attributes.

One pitfall is when such attributes are created in conditional clauses: if you attempt to access such an attribute later on in your code, that attribute may not exist if the flow had not entered that conditional suite. The moral of the story is that Python gives you a new feature you were not used to before, but if you use it, you need to be more careful, too.


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 of 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. One caveat is that default arguments should be immutable objects; mutable objects like lists and dictionaries act like static data, maintaining their contents with each method call.

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.

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.

1 class HotelRoomCalc(object): 2     'Hotel room rate calculator' 3 4     def __init__(self, rt, sales=0.085, rm=0.1): 5          '''HotelRoomCalc default arguments: 6          sales tax == 8.5% and room tax == 10%''' 7          self.salesTax = sales 8          self.roomTax = rm 9          self.roomRate = rt 10 11    def calcTotal(self, days=1): 12        'Calculate total; default to daily rate' 13        daily = round((self.roomRate * 14            (1 + self.roomTax + self.salesTax)), 2) 15         return float(days) * daily 

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 the cost of 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:

   >>> 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, applies 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).

__init__() 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(object):     ...          pass     >>> mc = MyClass()     >>> mc     <__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     ...     >>> mc = MyClass()     initialized     Traceback (innermost last):       File "<stdin>", line 1, in ?         mc = MyClass()     TypeError: __init__() should return None


13.6.2. 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(object):     ...     pass     >>> c = C()     >>> c.foo = 'roger'     >>> c.bar = 'shrubber'     >>> dir(c)     ['__class__', '__delattr__', '__dict__', '__doc__',     '__getattribute__', '__hash__', '__init__', '__module__',     '__new__', '__reduce__', '__reduce_ex__', '__repr__',     '__setattr__', '__str__', '__weakref__', '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'}


13.6.3. 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(object):    # 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'>


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'     >>> 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 dictionaryno class attributes or special attributes.

Core Style: Modifying __dict__

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 objectsbut that is another tale for another time.


13.6.4. Built-in Type Attributes

Built-in types are classes, too... do they have the same attributes as classes? (The same goes for instances.) We can use dir() on built-in types just like for any other object to get a list of their attribute names:

    >>> x = 3+0.14j     >>> x.__class__     <type 'complex'>     >>> dir(x)     ['__abs__', '__add__', '__class__', '__coerce__',     '__delattr__', '__div__', '__divmod__', '__doc__', '__eq__',     '__float__', '__floordiv__', '__ge__', '__getattribute__',     '__getnewargs__', '__gt__', '__hash__', '__init__',     '__int__', '__le__', '__long__', '__lt__', '__mod__',     '__mul__', '__ne__', '__neg__', '__new__', '__nonzero__',     '__pos__', '__pow__', '__radd__', '__rdiv__', '__rdivmod__',     '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__',     '__rmod__', '__rmul__', '__rpow__', '__rsub__',     '__rtruediv__', '__setattr__', '__str__', '__sub__',     '__truediv__', 'conjugate', 'imag', 'real']     >>>     >>> [type(getattr(x, i))  for i in ('conjugate', 'imag',     'real')]     [<type 'builtin_function_or_method'>, <type 'float'>,     <type 'float'>]


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

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


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

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


13.6.5. Instance Attributes versus 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 barely like that of automatic vs. static variables, but this is just a vague analogy . . . do not read too much into it, especially if you are not familiar with auto and static variables.)

Classes and instances are both namespaces. Classes are namespaces for class attributes. Instances are namespaces for instance attributes.

There are a few aspects of class attributes and 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, access to c.version fails for the instance, and then Python initiates a search for the name version first in the instance, then the class, and then the base classes in the inheritance free. In this case, it is found in the class:

    >>> class C(object):     # define class     ...     version = 1.2    # static member     ...     >>> c = C()                    # instantiation     >>> C.version           # access via class     1.2     >>> c.version            # access via instance     1.2     >>> C.version += 0.1     # update (only) via class     >>> C.version            # class access     1.3     >>> c.version            # instance access, which     1.3                               # also reflected change


However, 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 will create an instance attribute that "shadows" access to the class attribute, effectively hiding it from scope until or unless that shadow is removed.

Use Caution When Accessing Class Attribute with Instance

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, interesting side effects can occur. (This is true for both classic and new-style classes.)

    >>> class Foo(object):     ...     x = 1.5     ...     >>> foo = Foo()     >>> foo.x     1.5     >>> foo.x = 1.7      # try to update class attr     >>> foo.x            # looks good so far...     1.7     >>> Foo.x            # nope, just created a new inst attr     1.5


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. What would happen if we delete this new reference? To find out, we will use the del statement on c.version.

    >>> del foo.x        # delete instance attribute     >>> foo.x            # can now access class attr again     1.5


So by assigning an instance attribute with the same name as a class attribute, we effectively "hide" the class attribute, but once we remove the instance attribute, we can "see" the class one again. Now let us try to update the class attribute again, but this time, we will just try an innocent increment:

    >>> foo.x += .2     # try to increment class attr     >>> foo.x     1.7     >>> Foo.x           # nope, same thing     1.5


It is still a "no go." We again created a new instance attribute while leaving the original class attribute intact. (For those who have or want a deeper understanding of Python: the attribute was already in the class's dictionary [__dict__]. With the assignment, one is now added to the instance's __dict__.) The expression on the right-hand side of the assignment evaluates the original class variable, adds 0.2 to it, and assigns it to a newly created instance attribute. Note that the following is an equivalent assignment, but it may provide more clarification:

    foo.x = Foo.x + 0.2


But... all of this changes if the class attribute is mutable:

    >>> class Foo(object):     ...     x = {2003: 'poe2'}     ...     >>> foo = Foo()     >>> foo.x     {2003: 'poe2'}     >>> foo.x[2004] = 'valid path'     >>> foo.x     {2003: 'poe2', 2004: 'valid path'}     >>> Foo.x                # it works!!!     {2003: 'poe2', 2004: 'valid path'}     >>> del foo.x            # no shadow so cannot delete     Traceback (most recent call last):       File "<stdin>", line 1, in ?         del foo.x     AttributeError: x     >>>


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 attribute changes are reflected across all instances:

    >>> class C(object):     ...     spam = 100        # class attribute     ...     >>> c1 = C()             # create an instance     >>> c1.spam                      # access class attr thru inst.     100     >>> 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 += 200        # update class attribute again     >>> c2.spam                      # verify that attribute changed     400


Core Tip: Use a class attribute to modify itself (not an instance attribute)

As we have seen above, it is perilous to try and modify a class attribute by using an instance attribute. The reason is because instances have their own set of attributes, and there is no clear way in Python to indicate that you want to modify the class attribute of the same name, e.g., there is no global keyword like there is when setting a global inside a function (instead of a local variable of the same name). Always modify a class attribute with the class name, not an instance.




Core Python Programming
Core Python Programming (2nd Edition)
ISBN: 0132269937
EAN: 2147483647
Year: 2004
Pages: 334
Authors: Wesley J Chun

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net