Lesson 03 / 18
Basic Data Types
The concept of type, integers, real numbers, booleans, strings, and null values; static and dynamic typing.
Contents
The previous lesson bound a name to a number, then to a list, and the two behaved
differently. The list had an append operation; the number did not. The name for
this difference is type.
This lesson defines the concept of type and introduces the basic types that have a counterpart in every programming language.
What Is a Type
A type specifies two things together:
- Which values are valid — the set of values.
- Which operations are defined on those values.
The integer type’s set of values is the integers within a certain range; its operations are things like addition, subtraction, and comparison. The string type’s values are sequences of characters; concatenation and search are defined, subtraction is not.
The previous course’s conclusion applies directly here: memory contains only bit
patterns, and meaning comes from the interpretation rule applied to a pattern.
A type is exactly that interpretation rule. The programming-language answer to
whether the pattern 0x41424344 is an integer, a real number, or text is that
value’s type.
Integers
The integer type carries numbers with no fractional part. Two facts established in the previous course apply here: fixed-width integers have a range, and overflow occurs once that range is exceeded.
Languages diverge on this point. Some use fixed width and handle overflow either by wrapping or by raising an error. Others — Python is one of them — grow integers as large as needed; the only limit is memory.
Two division operations must be distinguished:
print(7 / 2) # 3.5 — real division print(7 // 2) # 3 — integer division (rounds down) print(7 % 2) # 1 — remainder print(-7 // 2) # -4 — rounding down also applies on the negative side
The rounding direction of integer division differs between languages: some round toward zero, some round down. In code that works with negative numbers, this detail changes the result.
Real Numbers
The real number type carries fractional values in floating-point representation. The previous course’s warning applies here as well: values are approximate and are not compared with equality.
print(0.1 + 0.2 == 0.3) # False print(abs((0.1 + 0.2) - 0.3) < 1e-9) # True — comparison with tolerance
Practical rule: quantities that require precision, such as money, are not held in floating-point types; decimal-based types, or integers carrying the smallest unit, are used instead.
Booleans
The boolean type has only two values: true and false. These values are usually not written directly; they are produced by comparisons.
measurement = 12 print(measurement > 10) # True print(measurement == 10) # False
Boolean values are the input to the conditional branching in the next topic. A condition “being true” means it produces a value of this type.
Many languages also allow non-boolean values to be used in a condition: zero, an empty string, and an empty collection count as false; everything else counts as true. This convenience can reduce readability; code is less prone to misunderstanding when what a condition tests is written explicitly.
Strings
The string type carries sequences of characters. The previous course’s lesson on character encodings gave the detail underlying this type: a string is a sequence of code points, and it is turned into bytes in memory according to an encoding.
name = "value" print(len(name)) # 5 — code point count print(name[0]) # v — indexing starts at zero print(name.upper()) # VALUE print("value" + " " + "1") # value 1
Indexing starting from zero is a convention common to most languages, and it comes from address arithmetic: an index states how many elements have been advanced past the start.
In many languages strings are immutable: a string’s content cannot be changed, only a new string can be produced. This is why string concatenation is expensive inside a loop; every concatenation creates a new object.
Collections
Types that hold a sequence of values instead of a single value form the input to the course’s shared problem:
measurements = [12, 18, 7, 25, 14] # list: ordered, mutable print(len(measurements)) # 5 print(measurements[0], measurements[-1]) # 12 14 print(sum(measurements) / len(measurements)) # 15.2 print(max(measurements)) # 25
This lesson introduces collections only enough to use them; their internal structure, costs, and selection criteria are the subject of the Data Structures course.
Immutable and Mutable Types
Types are also distinguished by whether their values can be changed after creation.
An immutable value’s content cannot be changed after it is created. Numbers, and strings in most languages, belong to this class. Every operation that looks like a change in fact produces a new value.
A mutable value’s content can be updated in place. Lists belong to this class.
text = "value" new = text.upper() print(text, new) # value VALUE — the original string did not change measurements = [12, 18] measurements.append(7) print(measurements) # [12, 18, 7] — the list changed in place
The distinction gains weight when combined with the sharing rule from the previous lesson. If two names are bound to the same object and the object is of a mutable type, a change made through one is also visible through the other. If the object is immutable, no such surprise occurs: since a change produces a new object, the other name stays bound to the old value.
For this reason, shared data requires care when working with mutable types; immutable types, in contrast, are safe to share. The same observation will be repeated in the lesson on passing arguments to functions.
Null Value
Most languages have a special value that denotes the state of “no value.” This value expresses a result that has not yet been computed or could not be found.
The cost of this convenience is that this value can turn up in unexpected places: code that tries to operate on a null value raises a runtime error. Some languages address this risk at the type level and mark values that may be null with a separate type, so that whether the check was performed can be tested at compile time.
When Type Is Checked
Languages test type compatibility at two different times.
Static typing: Types are known and checked during compilation. An incompatible operation is caught before the program ever runs. In exchange, types must be declared or inferred.
Dynamic typing: Type information is carried on values, and checking happens at run time. The syntax is shorter; in exchange, the same error stays invisible until the relevant line executes.
number = 5 text = "5" print(number + number) # 10 print(text + text) # 55 — string concatenation # print(number + text) # runtime error: incompatible types
The error in the third line surfaces under dynamic typing only once that line runs. Under static typing, the same expression would have been rejected before running. The principle established in the previous course’s lesson on compilation stages applies here as well: the question is when the error is seen.
Summary
- A type specifies the set of valid values together with the operations defined on them; it is the programming-language name for the interpretation rule applied to a bit pattern.
- Integer division comes in two forms, and the rounding direction varies between languages.
- Real numbers are approximate; they are compared with tolerance rather than equality.
- Boolean values are produced by comparisons; a null value denotes absence and is a source of runtime errors when left unchecked.
- Strings are sequences of code points; in immutable types every change produces a new value, in mutable types the content is updated in place.
- Static typing catches errors before running, dynamic typing catches them while running.
Next Step
Types have been defined; it is now time to build expressions from these values. The next lesson takes up arithmetic, comparison, and logical operators, along with the operator precedence rules that — in an earlier lesson — were the source of a logic error.
To keep your progress and take notes, Log in
My notes
Log in to take notes.