I l@ve RuBoard Previous Section Next Section

2.9 General Object Properties

Now that we've seen all of Python's built-in types, let's take a quick look at some of the properties they share. Some of this section is a review of ideas we've already seen at work.

2.9.1 Type Categories Revisited

Table 2.11 classifies all the types we've seen, according to the type categories we introduced earlier. As we've seen, objects share operations according to their category—for instance, strings, lists, and tuples all share sequence operations. As we've also seen, only mutable objects may be changed in place. You can change lists and dictionaries in place, but not numbers, strings, or tuples.[11] Files only export methods, so mutability doesn't really apply (they may be changed when written, but this isn't the same as Python type constraints).

[11] You might think that number immutability goes without saying, but that's not the case in every programming language. For instance, some early versions of FORTRAN allowed users to change the value of an integer constant by assigning to it. This won't work in Python, because numbers are immutable; you can rest assured that 2 will always be 2.

Table 2.11. Object Classifications

Object type

Category

Mutable?

Numbers

Numeric

No

Strings

Sequence

No

Lists

Sequence

Yes

Dictionaries

Mapping

Yes

Tuples

Sequence

No

Files

Extension

N/A

Why You Will Care: Operator Overloading

Later, we'll see that objects we implement ourselves with classes can pick and choose from these categories arbitrarily. For instance, if you want to provide a new kind of specialized sequence object that is consistent with built-in sequences, code a class that overloads things like indexing, slicing, and concatenation:

class MySequence:
    def __getitem__(self, index):
        # called on self[index], for x in self, x in self
    def __getslice__(self, low, high):
        # called on self[low:high]
    def __add__(self, other):
        # called on self + other

and so on. You can also make the new object mutable or not, by selectively implementing methods called for in-place change operations (e.g., __setitem__ is called on self[index]=value assignments). Although this book isn't about C integration, it's also possible to implement new objects in C, as C extension types. For these, you fill in C function pointer slots to choose between number, sequence, and mapping operation sets. Python built-in types are really precoded C extension types; like Guido, you need to be aware of type categories when coding your own.

2.9.2 Generality

We've seen a number of compound object types (collections with components). In general:

Because they support arbitrary structures, Python's compound object types are good at representing complex information in a program. For instance, the following interaction defines a tree of nested compound sequence objects; to access its components, we string as many index operations as required. Python evaluates the indexes from left to right, and fetches a reference to a more deeply nested object at each step. (This may be a pathologically complicated data structure, but it illustrates the syntax used to access nested objects in general.)

>>> L = ['abc', [(1, 2), ([3], 4)], 5]
>>> L[1]
[(1, 2), ([3], 4)]
>>> L[1][1]
([3], 4)
>>> L[1][1][0]
[3]
>>> L[1][1][0][0]
3

2.9.3 Shared References

We mentioned earlier that assignments always store references to objects, not copies. In practice, this is usually what you want. But because assignments can generate multiple references to the same object, you sometimes need to be aware that changing a mutable object in place may affect other references to the same object in your program. For instance, in the following, we create a list assigned to X and another assigned to L that embeds a reference back to list X. We also create a dictionary D that contains another reference back to list X:

>>> X = [1, 2, 3]
>>> L = ['a', X, 'b']
>>> D = {'x':X, 'y':2}

At this point, there are three references to the list we created first: from name X, from the list assigned to L, and from the dictionary assigned to D. The situation is sketched in Figure 2.2.

Figure 2.2. Shared object references
figs/lpy_0202.gif

Since lists are mutable, changing the shared list object from any of the three references changes what the other two reference:

>>> X[1] = 'surprise'         # changes all three references!
>>> L
['a', [1, 'surprise', 3], 'b']
>>> D
{'x': [1, 'surprise', 3], 'y': 2}

One way to understand this is to realize that references are a higher-level analog of pointers in languages such as C. Although you can't grab hold of the reference itself, it's possible to store the same reference in more than one place

2.9.4 Comparisons, Equality, and Truth

All Python objects also respond to the comparisons: test for equality, relative magnitude, and so on. Unlike languages like C, Python comparisons always inspect all parts of compound objects, until a result can be determined. In fact, when nested objects are present, Python automatically traverses data structures and applies comparisons recursively. For instance, a comparison of list objects compares all their components automatically:

>>> L1 = [1, ('a', 3)]         # same value, unique objects
>>> L2 = [1, ('a', 3)]
>>> L1 == L2, L1 is L2         # equivalent?, same object?
(1, 0)

Here, L1 and L2 are assigned lists that are equivalent, but distinct objects. Because of the nature of Python references, there are two ways to test for equality:

The == operator tests value equivalence

Python performs an equivalence test, comparing all nested objects recursively

The is operator tests object identity

Python tests whether the two are really the same object (i.e., live at the same address).

In our example, L1 and L2 pass the == test (they have equivalent values because all their components are equivalent), but fail the is check (they are two different objects). As a rule of thumb, the == operator is used in almost all equality checks, but we'll see cases of both operators put to use later in the book. Relative magnitude comparisons are applied recursively to nested data structures too:

>>> L1 = [1, ('a', 3)]
>>> L2 = [1, ('a', 2)]
>>> L1 < L2, L1 == L2, L1 > L2     # less, equal, greater: a tuple of results
(0, 0, 1)

Here, L1 is greater than L2 because the nested 3 is greater than 2. Notice that the result of the last line above is really a tuple of three objects—the results of the three expressions we typed (an example of a tuple without its enclosing parentheses). The three values represent true and false values; in Python as in C, an integer represents false and an integer 1 represents true. Unlike C, Python also recognizes any empty data structure as false and any nonempty data structure as true. Table 2.12 gives examples of true and false objects in Python.

Table 2.12. Example Object Truth Values

Object

Value

"spam"

True

""

False

[]

False

{}

False

1

True

0.0

False

None

False

Python also provides a special object called None (the last item in Table 2.12), which is always considered to be false. None is the only value of a special data type in Python; it typically serves as an empty placeholder, much like a NULL pointer in C. In general, Python compares the types we've seen in this chapter, as follows:

In later chapters, we'll see other object types that can change the way they get compared. For instance, class instances are compared by address by default, unless they possess special comparison protocol methods.

2.9.5 Python's Type Hierarchies

Finally, Figure 2.3 summarizes all the built-in object types available in Python and their relationships. In this chapter, we've looked at the most prominent of these; other kinds of objects in Figure 2.3 either correspond to program units (e.g., functions and modules), or exposed interpreter internals (e.g., stack frames and compiled code).

Figure 2.3. Built-in type hierarchies
figs/lpy_0203.gif

The main point we'd like you to notice here is that everything is an object type in a Python system and may be processed by your Python programs. For instance, you can pass a stack frame to a function, assign it to a variable, stuff it into a list or dictionary, and so on. Even types are an object type in Python: a call to the built-in function type(X) returns the type object of object X. Besides making for an amazing tongue-twister, type objects can be used for manual type comparisons in Python.

I l@ve RuBoard Previous Section Next Section