Here we focus on the 'data type' of numbers, of which there are two main ones we are concerning ourselves with:
A data type is (binary) data that is structured and has rules of its access and use applied to it by the programming language you are using (Python). A bunch of binary data in memory might be interpreted by Python as a string for characters, or a decimal number, or an integer. It might also represent the data for a function definition! Only when you follow Python's rules for data types can you give this blob of binary bits structured existence and use.
For example, the following values are stored in the variables
b look the same to a human, but are fundamental different to Python.
arefers to a numerical literal 22. It is interperted as a valid number that can be used for arithmetic expressions.
b is a string of characters '2' and '2'. Mathematically they are meaningless.
In the cell below, I attempt to add
a + b and it fails. This is expected! The string characters is not a mathmetically capable data type!
a = 22
b = "22"
a + b
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-bd58363a63fc> in <module>() ----> 1 a + b TypeError: unsupported operand type(s) for +: 'int' and 'str'
int) and decimal (
float) numbers are compatible data types in Python. While they are not at all stored the same underneath, they are mathematically compatible. What happens if you perform math using an
int and a
float (mixed data types)? In this case, it usually defaults to the data type that gives the value the most information (which is a
float.) A fraction part of a number is more informative data than just a whole number.
In the example below, we add
a to the literal
0.6767. The resulting value from this addition is a new
float value of
22.6767. This result cannot be stored as an integer, obviously.
a + 0.6767
If you want to know exactly what data type a value is, we can use the
type() function. Give it anything (an identifier, a literal) and
type() explains to you what data type that value is.
Below are examples of the basic arithmetic operations. Note the data type returned from the evaluations.
val = 3.0 + 4.0 print(val)
3 + 4
3 * 4
The evaluation of
result, below, from the use of the "integer division" operator,
/, is a whole number:
3.0 despite being whole it's value represented as a
float data type instead of an
integer data type. Some functions, like
range() only operate on integers. What if I wanted to use
result in my call to
range()? Well, we can call the function
int(). It's a special function that is representative of the
int data type. This function makes new integers! We use it to our advantage here by making a new
int from the
float value of
3.0. In the end,
result now stores
result = 10.0//3.0
result = int(result)
for n in range(result): print(n)
0 1 2
You have a full breadth of numerical comparison operators (equality, inequality, greater than, less than, etc), logical operators (
not) that can be nicely intermixed with your regular arithmetic operators to make some complex expressions.
c = 75667
a > c
a == c
a != c
a >= c
c >= a
c <= (a ** 10)
(a != 6) and (c > 3)
(a != 6)
(c > 3)
(17 / 4) * 4 == 17
17.0 == 17
Data in other languages are 'dumb', 'passive'. They exist as just a bundle of bits 'typed' as the data they are supposed to be (
float, like in the C language); however any actions on the data are performed externally on that data and it might violate the rules of the data type (C can do crazy things).
In Python (and other languages), data types are extended in concept and are based on "objects" (hence, "object-oriented" languages). The data is now 'smarter', more active. Everything used in Python is an object of one type or another. Every literal you type is evaluted into its proper data type (
list, etc). Data is now not just the value(s) it represents but it also comes packaged with actions, the functions that act on that data.
In short, objects are data types that are packaged values and functions that operate on those values, in one nicely encapsulated entity. Every
int, for example, has the value and functions within that
int that operate on that one
int value. Every
int created has their own set of these functions meant for
int manipulation. The same goes for any data type. This enforces the correct actions that can be done the data.
For example, a lesser known but still valid operation to perform on an
int is to check its 'bit length', or the number of binary bits used to represent a base-10 integer.
a to the
int value of
3. This is base-10 that we all know and love. To ask that
int how many binary bits (one's and zero's) are used to represent this number, we call the
bit_length() function on the
int object. It is a valid action only for
ints! It returns
2, which means that the binary string ($11$, or $2^1 + 2^0$, represents base-10 integer
3). OK, that's so important as the fact we used a function bundled with what we thought was just plain old integer data!
a = 3 a.bit_length()
Even mundane operations like
a + 2 do not externally act on the
int represented by
a but actually calls a special function within the
int object called
__add__ (pronounced "dunder add"). See the two versions, below. No matter what, you are "asking" an object to perform work. If it doesn't support that action it will fail!
a + 2
How do objects store all these bits of information about itself? The
int object needs to store its value and the functions it supports that operate on that value. These attributes are stored in a namespace. Every object in Python has a namespace and it's a 'bucket' that holds all the identifier attributes stored within an object.
The following is the namespace of our
a. We use the function
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
All the "dunder names", or attributes with "
int would be near the end of that list.
Each data type has its own namespace with its own attributes valid of that data type
You use the
. (dot) operator to access function or value attributes inside an object's namespace.
Modules are an excellent example of objects. You can import another Python code file into yours to use the code within it for something useful. The
math module is one such module that comes included with Python upon installation. We
import math to "bring in" the math module for use.
math is created and is now a variable that refers to a module object. The namespace of this of this module shows you all the items it has made available for use. The
math module comes with many math operations (
log) and even some
float constants like (
As mentioned above, the
. (dot) operator is used to access these attributes of any object, the module object included.
<module 'math' from '/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload/math.cpython-36m-darwin.so'>
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
We use the dot operator to access
Opps, it's a function and I didn't properly run it! Instead, I asked Python to 'describe' that identifier and it explained to me that it's a function. OK, let's properly run it:
Let access the constant for $\pi$!
math.pi + 10
If you attempt to access an attribute that doesn't exist in an object's namespace, its as if you access a variable name that wasn't defined yet, but referred to as an
AttributeError in this context:
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-77-64d315a5c94c> in <module>() ----> 1 math.foobar AttributeError: module 'math' has no attribute 'foobar'
Namespaces should be kept clean. Import the module you need, wholesale or selectively import what you need into your "main" or top-level namespace. The following imports just
sqrt() into my main namespace. I don't need to access it using
from math import sqrt
What's my "main namespace" and what does it look like? Namespaces are hierarchial trees of objects. You start with a main namespace in which other objects with their own namespaces are stored. You can call
dir() without any parameters to see what this main namespace looks like:
['In', 'Out', '_', '_12', '_13', '_14', '_15', '_16', '_17', '_20', '_23', '_24', '_27', '_28', '_29', '_30', '_31', '_32', '_33', '_34', '_35', '_36', '_37', '_38', '_39', '_4', '_40', '_41', '_42', '_44', '_45', '_46', '_47', '_48', '_5', '_51', '_52', '_6', '_7', '_9', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_i10', '_i11', '_i12', '_i13', '_i14', '_i15', '_i16', '_i17', '_i18', '_i19', '_i2', '_i20', '_i21', '_i22', '_i23', '_i24', '_i25', '_i26', '_i27', '_i28', '_i29', '_i3', '_i30', '_i31', '_i32', '_i33', '_i34', '_i35', '_i36', '_i37', '_i38', '_i39', '_i4', '_i40', '_i41', '_i42', '_i43', '_i44', '_i45', '_i46', '_i47', '_i48', '_i49', '_i5', '_i50', '_i51', '_i52', '_i53', '_i6', '_i7', '_i8', '_i9', '_ih', '_ii', '_iii', '_oh', 'a', 'b', 'c', 'exit', 'get_ipython', 'math', 'quit', 'result', 'sqrt', 'val']