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.

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.[17] 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).

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

Generality

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

  • Lists, dictionaries, and tuples can hold any kind of object.

  • Lists, dictionaries, and tuples can be arbitrarily nested.

  • Lists and dictionaries can dynamically grow and shrink.

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

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.

Shared object references

Figure 2-2. Shared object references

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

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:

  • Numbers are compared by relative magnitude.

  • Strings are compared lexicographically, character-by-character (“abc” < “ac”).

  • Lists and tuples are compared by comparing each component, from left to right.

  • Dictionaries are compared as though comparing sorted (key, value) lists.

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.

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

Built-in type hierarchies

Figure 2-3. Built-in type hierarchies

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.



[17] 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset