We concentrated in Chapter 1 on computational processes and on the role of functions in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic), how to form compound functions through composition and control, and how to create functional abstractions by giving names to processes. We also saw that higher-order functions enhance the expressiveness of a language by enabling us to manipulate, and thereby to reason, in terms of general methods of computation. This is much of the essence of programming.
This chapter focuses on data. The techniques we investigate here will allow us to represent and manipulate information about many different domains. Due to the explosive growth of the Internet, a vast amount of structured information is freely available to all of us online, and computation can be applied to a vast range of different problems. Effective use of built-in and user-defined data types are fundamental to data processing applications.
Native Data Types¶
Every value in Python has a class that determines what type of value it is. Values that share a class also share behavior. For example, the integers 1 and 2 are both instances of the int class. These two values can be treated similarly. For example, they can both be negated or added to another integer. The built-in type function allows us to inspect the class of any value.
>>> type(2)
<class 'int'>The values we have used so far are instances of a small number of native data types that are built into the Python language. Native data types have the following properties:
There are expressions that evaluate to values of native types, called literals.
There are built-in functions and operators to manipulate values of native types.
The int class is the native data type used to represent integers. Integer literals (sequences of adjacent numerals) evaluate to int values, and mathematical operators manipulate these values.
>>> 12 + 3000000000000000000000000
3000000000000000000000012Python includes three native numeric types: integers (int), real numbers (float), and complex numbers (complex).
>>> type(1.5)
<class 'float'>
>>> type(1+1j)
<class 'complex'>Floats¶
The name float comes from the way in which real numbers are represented in Python and many other programming languages: a “floating point” representation. While the details of how numbers are represented is not a topic for this text, some high-level differences between int and float objects are important to know. In particular, int objects represent integers exactly, without any approximation or limits on their size. On the other hand, float objects can represent a wide range of fractional numbers, but not all numbers can be represented exactly, and there are minimum and maximum values. Therefore, float values should be treated as approximations to real values. These approximations have only a finite amount of precision. Combining float values can lead to approximation errors; both of the following expressions would evaluate to 7 if not for approximation.
>>> 7 / 3 * 3
7.0
>>> 1 / 3 * 7 * 3
6.999999999999999Although int values are combined above, dividing one int by another yields a float value: a truncated finite approximation to the actual ratio of the two integers divided.
>>> type(1/3)
<class 'float'>
>>> 1/3
0.3333333333333333Problems with this approximation appear when we conduct equality tests.
>>> 1/3 == 0.333333333333333312345 # Beware of float approximation
TrueThese subtle differences between the int and float class have wide-ranging consequences for writing programs, and so they are details that must be memorized by programmers. Fortunately, there are only a handful of native data types, limiting the amount of memorization required to become proficient in a programming language. Moreover, these same details are consistent across many programming languages, enforced by community guidelines such as the IEEE 754 floating point standard.
Non-Numeric Types¶
Values can represent many other types of data, such as sounds, images, locations, web addresses, network connections, and more. Some are represented by native data types, such as the bool class for values True and False and the str class for text values such as 'Hello, World!'. The type for many other values must be defined by programmers using the means of combination and abstraction that we will develop in this chapter.
This chapter introduces many of Python’s native data types, as well as new ones we will create. For those interested in further details, a chapter on native data types in the online book Dive Into Python 3 gives a pragmatic overview of all Python’s native data types and how to manipulate them, including numerous usage examples and practical tips.
Working with Types¶
Programming languages vary in their treatment of data types. Python is organized around types, but does not require that those types be written down explicitly in the code. Values of similar types can in many cases be used interchangeably by the same functions (such as int and float values). However, there are cases when examining, annotating, or conditioning on the type of a value is useful.
Type Inspection¶
The built-in function isinstance takes a value and a class. It returns true if the value is an instance of the class. For example, show rounds float values before printing them, but prints all other values as they are.
>>> def show(n):
if isinstance(n, float):
print(round(n, 2))
else:
print(n)
>>> show(100 / 3)
33.33
>>> show(100 > 5)
TrueType Coercion¶
The process of converting a value of one type to an equivalent value of another
type is called type coercion. A value of one native type can in some
circumstances be converted to another. All values can be converted to a str. A
string containing only a number can be converted to an int (if there is no
decimal component) or float. Call a type on a value to perform the conversion.
>>> str(12)
'12'
>>> float('34.56')
34.56
>>> int(78.0)
78
>>> int('9')
9Type Hints¶
Most functions are designed to be called on values of a particular type. A function can be annotated with the intended types of its formal parameters (the names of its arguments), as well as its return value following a -> symbol. These annotations are called type hints in Python. In the example below, the formal parameter x and the return value are annotated as int values.
>>> def square(x: int) -> int:
return x * x
>>> square(5)
25Type hints are not restrictions on what the function can take and return, but instead documentation of the function’s intended use. This square function could take and return float values, despite the type hints, and it would return successfully.
>>> square(1.4142)
1.9999616399999998The square function could also be called on str arguments, such as square('three'), which would cause a TypeError, but not because of the type hints. Instead, the call to square would be allowed, then an error would occur when the Python interpreter attempted to evaluate x * x with x bound to a str value.
Union types. A function that can take either a str or a float can be annotated with the union type str | float. This double function doubles a number, converting a str argument to a float and back.
>>> def double(x: str | float) -> str | float:
if isinstance(x, str):
return str(2 * float(x))
else:
return 2 * x
>>> double('3')
'6.0'
>>> double(3.5)
7.0Type aliases. One way to give a type a name is to use a type statement. User-defined types are conventionally capitalized. Once a new type name is introduced, it can be used in type hints.
type Quantity = str | float
def double(x: Quantity) -> Quantity:
if isinstance(x, str):
return str(2 * float(x))
else:
return 2 * xType checkers. Type hints can be type checked, which is a process that inspects the code in a Python file without executing it to determine whether names annotated with a type will be bound to compatible values. Likewise, it checks whether all return values of a function will be compatible with the annotated return type of that function. The Visual Studio Code editor with the Microsoft Python extension installed can detect type incompatibilities as you edit a Python source file, but this type checking is turned off by default. To turn it on, select Settings from the Code menu (Mac) or Preferences from the File menu (Windows), then change the setting called Python › Analysis: Type Checking Mode from off to standard. Search for “type checking mode” to find the setting. Then, type incompatibilities will be underlined as errors. Type checking for Python is not limited to VS Code and can be configured for many editors.