Overview of Types and Values

Python is dynamically typed - it makes use of Duck typing to determine the data type of a variable. It also has a function called type which can be sent to a variable to determine this data type as shown below.

	#!/usr/bin/env python3
	# Copyright 2009-2017 BHG http://bw.org/

	x = 7
	print('x is {}'.format(x))
	print(type(x))

In this example, the data type as we will see in the output is int. If we add a few lines as follows

	x = 7.0
	print('x is {}'.format(x))
	print(type(x))

and run it again, we get the following output.

	x is 7
	<class 'int'>
	x is 7.0
	<class 'float'>

We can see that when the program runs, x is set to a value of 7 with data type of int and then to 7.0 with a data type of float, so the data type is not fixed and can change as required.

Similarly, we could also use other values for x that would give us other data types, including the following.

	•		x = "Philip"		data type = str (string)
	•		x = True			data type = bool (Boolean)
	•		x = None			data type = NoneType

The String Type

Every data type in Python 3, including the data types are classes and we saw this with the string type earlier where we used format, one of its class methods.

Like most languages, strings can be wrapped in single or double quotes. In some languages, there can be some very subtle differences (for example, strings with double quotes in Perl are interpolated whereas strings in single quotes are not). In Python, there is no difference at all.

Python also allows you to start and end a string with three single quotes and this allows you to have a string that is spread over several lines like this.

	x = '''


	seven





	'''

If you print this string to a monitor, it will be output as shown, including the empty lines. It may not be immediately obvious why this is significant, but remember that in Python, the end of a statement is usually the line ending so if you used single or double quotes for this string

	x = "


	seven





	"

you will get an error.

	SyntaxError: EOL while scanning string literal

The problem here is that the string literal in the first line isn't terminated before the end of the line is encountered. Using three single quotes essentially causes the interpreter to ignore the line endings and treat everything between the quotes as a single string. You can also use three double quotes at the beginning and end of the string and that works in exactly the same way.

If we have a literal string such as

	"seven"

this is an object, just like any other Python string and this means, amongst other things, that we can use string methods on it. For example, we can convert the string with the capitalize method

x = "seven".capitalize

which will convert the first letter to upper case. Note that this only converts the first letter of the string. If you have a string that contains several sentences, all in lower case, it won't capitalize every sentence, just the string as a whole. Similarly, there are other methods we could use such as upper() or lower() which will convert the string to all upper case or all lower case respectively.

We also saw the format method earlier which is used to format a string and this is quite flexible. For example, we could use it to add a couple of numbers to the string like this.

	x = "seven {} {}.format(8, 9)

which will give us the output

	x is seven 8 9

It doesn't make a lot of sense to do that since we could have just added these numbers to the string. But we can also do the same thing with a couple of variables which would make more sense. The main point here is that these are positional arguments since they mark the positions in the string where the arguments passed to the format method will be placed. These are indexed from 0 and if we don't specify which is the first or second argument, they appear in the order in which the arguments are passed to the format method. We can specify these, if we want to change that order.

	x = "seven {1} {0}.format(8, 9)

which will switch them around to give the output

	x is seven 9 8

We can also add a modifier with a colon like this.

	x = "seven {1:<9} {0:>9}".format(8, 9)

This will add 9 spaces to the string being added, the < symbol adds trailing spaces and the > symbol adds leading spaces. This would probably be clearer if we put these two values in single quotes like this.

	x = "seven '{1:<9}' '{0:>9}'".format(8, 9)

which allows us to more clearly see these spaces in the output. These values are being either left adjusted (with the < symbol) or right adjusted (with the > symbol). Personally, I don't think that this is particularly intuitive, but it may help to think of the string being left adjusted or right adjusted with the symbol pointing in the appropriate direction.

	seven '9        ' '        8'

In this example, we used these modifiers with index values, but we can dispense with the index values if they are not needed so

	x = "seven '{:<9}' '{:>9}'".format(8, 9)

will do the same thing without switching the order.

If we want to add leading (or trailing) zeros instead of spaces, we can insert a zero like this.

	x = "seven '{:0<9}' '{:0>9}'".format(8, 9)

Actually, we can insert pretty much any character there and the string that we are inserting and that character will be used to pad the string. For instance

	x = "seven '{:8<9}' '{:8>9}'".format(8, 9)

will produce the output

	seven '888888888' '888888889'

In these examples, we put the 'fill' character before the </> symbol. If we are doing this to add leading zeros, we can also put it after so

	x = "seven '{:0<9}' '{:0>9}'".format(8, 9)

will add trailing zeros to the 8 and leading zeros to the 9 and

	x = "seven '{:<09}' '{:>09}'".format(8, 9)

will do exactly the same thing. It may not be immediately obvious, but the 9 dictates the minimum length of the string. For instance, in the above example, the strings being inserted are both one character in length and we have specified a minimum length of 9, so the number of leading or trailing zeros is 8 and this would be different for different string lengths. For example, let's say we change the strings like this.

	x = "seven '{:<09}' '{:>09}'".format(123456789, 9)

The output we get from this is

	seven '123456789' '000000009'

Notice that there are no trailing zeros in the first string because it is already 9 characters long. If we made it longer

	x = "seven '{:<09}' '{:>09}'".format(123456789123, 9)

the output would be

	seven '123456789123' '000000009'

Since the first string is longer than the minimum length, it is unchanged. For reference, there is more information on the format method on the W3 Schools web page.

Python 3.6 and later versions also allow you to use an f string which we saw earlier and this is really just a shorthand version of the format method. Rather than using positional operators, the variable is placed directly inside the string in curly braces. To give an example of this, the following creates a string and adds 8 and 9 in to replace the positional operators.

	x = "seven {} {}".format(8, 9)

If we rewrite this as an f string, as before, it only makes sense to do something like this when you have variables to insert in the string.

For an f string, as we saw earlier, we put an f before the string, we insert the curly braces inside the string where we want the data to appear. But this time, we put the data inside the curly braces rather than after the string. If we use the same example again, it might look something like this.

	x = f"seven {8} {9}"

Syntactically, this is perfectly correct but we are inserting literals into our string literal. What this means is that the curly braces are not actually doing anything. If we removed them, leaving the string otherwise as it is, the result would be the same. Let's assume that we have two variables, a and b with a having a value of 8 and b having a value of 9. We can add these to our f string like this.

This leads to the question, what can we put inside these curly braces in an f string and the answer appears to be any expression. In other words, anything that returns a value. Now, you may recall that earlier, we looked at what things could be considered an expression and that included literal values such as 7 or 8, variables such as a or b, functions or even assignments. Consider the following examples.

	

Expression Output

x = f"seven {a*a} {b*b}" seven 64 81 x = f"seven {a*a} {print(b*b)}" seven 64 None (recall that a function always returns a value, even if it is none) x = f"seven {a*a} {c==b*b}" seven 64 False

However, it doesn't seem to work with assignments so for

	x = f"seven {a*a} {c=b*b}"

the output is

	PS C:\Users\emuba\Documents\Exercise Files\Ex_Files_Python_EssT>  c:; cd 'c:\Users\emuba\Documents\Exercise Files\Ex_Files_Python_EssT'; & 'C:\Users\emuba\AppData\Local\Microsoft\WindowsApps\python3.9.exe' 'c:\Users\emuba\.vscode\extensions\ms-python.python-2021.11.1422169775\pythonFiles\lib\python\debugpy\launcher' '61734' '--' 'c:\Users\emuba\Documents\Exercise Files\Ex_Files_Python_EssT\Exercise Files\Chap03\types.py'
	File "c:\Users\emuba\Documents\Exercise Files\Ex_Files_Python_EssT\Exercise Files\Chap03\types.py", line 7
	x = f"seven {a*a} {c=b*b}"
							  ^
	SyntaxError: f-string: expecting '}'
	PS C:\Users\emuba\Documents\Exercise Files\Ex_Files_Python_EssT>

We can also use the same options for aligning the variables in an f string or adding leading or trailing zeros that we can use with the format method.

Numeric Types

The main numeric types are integer and floating point. Remember that everything in Python is an object, which means that there are classes for both integer and floating point numbers. One of the consequences of this is that you can create your own types based on these and Python does include some built-in types, including the complex type that do just that.

Bear in mind that Python uses Duck typing so if you do something like this.

	x = 7 * 3

	print(x)
	print(type(x))

you will see that x has type int. On the other hand, if we change this to

	x = 7 * 3.0

	print(x)
	print(type(x))

this gives the output as

	21.0
	<class 'float'>

So, the expression on the first line is multiplying an integer by a float. Technically speaking, this is impossible because the multiplication method used will be either integer multiplication or float multiplication. Since we have used two different types, Python automatically converts the int to a float so we are doing float multiplication and the returned value is therefore a float. We can see that from running the type method on it, but we can also infer this from the fact that the output value has a decimal point (it is displayed as 21.0 rather than 21 which would be the output from an integer multiplication).

We will also see something similar if we perform an arithmetic operation with two integer operands that produce a float result.

	x = 7 / 3

In Python, this will give a result of 3.5. Some languages use the / operator for integer division (including Python 2 and earlier Python versions) but in Python 3, this is a float division so it will always give a float result. Consider the following example.

	#!/usr/bin/env python3
	# Copyright 2009-2017 BHG http://bw.org/

	x = 6 / 3

	print(x)
	print(type(x))

The output here is

	2.0
	<class 'float'>

We can infer from this that Python is using the float division method in all cases, when when both the operands and the result could be represented as integers. We've seen some implicit casting of types here, but Python also all for explicit casting.

	x = int(6 / 3)

	print(x)
	print(type(x))

The output of this is

	2
	<class 'int'>

In this example, we are converting the result of dividing 6 by 3 to an integer, so we have used the float division operator and returned a float value, but the cast converts the result to an integer.

If we did the type casting before the division like this

x = (6 / int(3))

This not have any effect. The reason for this is that 3, in this case, is one of the inputs which are actually integers anyway. The output, or the result of performing the division operation, is the float value and if we want it to return an integer, that is the value we need to cast to an integer.

Note, that in this case, the value can easily be converted between a float and integer without any loss. What I mean by that is if you convert the integer, 2 to a float, you get 2.0 and if you convert 2.0 to an integer, you get 2.0. It is important to remember that if you convert an integer to a float and then back to an integer, you will end up with the original integer. Now, let's look at another example where we cast a float to an integer.

	x = int (7 / 3)

	print(x)
	print(type(x))

This will give us the same output. We are performing a float division of 7 / 3 which returns a value of about 2.3333 which is then cast to type int and that takes the integer part and removes the decimal part. It doesn't round up or down although it has the effect of rounding down. If we then convert the integer back to a float, this will give us 2.0. If we convert from a float to an integer and then back to a float, there is no guarantee that we will restore the original float value. So

	x = float (int (7 / 3))

will return a value of 2.0. In this case, we have obtained a float value from the division of 2.3333, converted it to an int with a value of 2 and then back to a float with a value of 2.0.

If we use the integer division operator, the result would essentially be the same as using the float operator and converting the result to an integer. Now consider this example.

	x = 8 // 3
	print(x)
	print(type(x))

The output from this is

	2
	<class 'int'>

This seems to be the same as using float division and converting the output to an integer and it certainly gives the same output, but there is one crucial difference. Whereas the float division returns 2.3333, the integer division does actually return 2 since we can only since there are only 2 whole 3s in 7 with one left over. The float division doesn't have any remainder but because the integer division does, we can get that remainder with the modulo operator which in Python is %. So

	x = 7 % 3
	print(x)
	print(type(x))

will give the output

	1
	<class 'int'>

which is our remainder.

I want to look at another example using floats. Consider the following example.

	x = .1 + .1 + .1 - .3
	print('x is {}'.format(x))
	print(type(x))
	

This seems like a very straightforward example where we are summing three values, all of which are 0.1 and will therefore give us a value of 0.3 and the then subtracting 0.3. Common sense will tell you that this will return a value of 0 (or 0.0). In fact, the output from this is

	x is 5.551115123125783e-17
	<class 'float'>

This happens because float values have a specific degree of precision (in Python, floats are precise to 17 decimal places. This means that they are not completely accurate. We might describe this by saying float values are approximations, so our total isn't 0.3, it is approximately 0.3 and the result we get from the subtraction is almost zero but not quite and that is because of the inaccuracies. Let's assume that we are working with monetary amounts, the implication of this is that 10p + 10p + 10p is about 30p, but not exactly. For this reason, using floats to calculate monetary amounts is probably not a good idea. It is better to store these values only as integers so, for example, £9.99 would be stored as 999 (that is, 999 pence). If you need to display a monetary amount, you can always divide it by 100 using integer division (giving you the number of pounds), get the remainder using the modulo operator (giving you the pence part, and then use format to display these as a single monetary amount, like this.

	x = 9.99

	x = int (x * 100)   # convert the value in pounds and pence to just pence
	l = x // 100        # get the value in pounds
	p = x % 100         # get the value in pence minus the pounds

	print('The price is £{}.{}.'.format(l, p))
	print(type(x))

The output from this is

	The price is £9.99.
	<class 'int'>

That is one simple solution, but Python 3 also has a type called Decimal that can help here and we could use it like this.

	from decimal import *

	a = Decimal('.10')
	b = Decimal('.30')

	x = a + a + a - b

	print('x is {}'.format(x))
	print(type(x))

Notice that we are importing everything from decimal and this gives us access to the method Decimal. This converts our value to a decimal number. The argument passed to Decimal is a string because if we passed it a numeric value, we would still have the same inaccuracy we saw before so by passing it as a string, we are getting exactly the value shown.

Running this gives us the output

	x is 0.00
	<class 'decimal.Decimal'>

The result is what we expected and we can use this technique when working with floating point numbers when we don't really care about being very precise (for example, monetary amounts only need to be precise to two decimal places), but we do need accuracy (we don't want a monetary amount such as 0.30 to be expressed as about 0.30, it has to be exactly 0.30.

However, we should also bear in mind that creating decimal values like this does not guarantee that a result will always be displayed to two decimal places. For example, consider the following example.

	from decimal import *

	a = Decimal('.10')

	x = a * 10 / 3

	print('x is {}'.format(x))
	print(type(x))

Multiplying 0.10 by 10 will give us exactly 1.00, but when we divide this by 3, there is no exact number that represents the correct answer accurately and so we get the output

	x is 0.3333333333333333333333333333
	<class 'decimal.Decimal'>
	

In most cases, working with monetary amounts is going to work perfectly well, for example, if you are adding up a number of monetary amounts where you can guarantee that you won't have any value or any result that cannot be expressed exactly as a numerical value to two decimal places. But you should be at least aware of the fact that some of these values can produce results that can't be expressed exactly with the same level of precision.

That being said, it is also important to remember that this type of situation arises because the operation is not suitable for the values you are working with. As an example, you might have some code that calculates the price of an item after applying a discount. Imagine you have an item that costs £10.00 and the discount is 1/3 off, there is no exact price that matches this criterion so you would have to know how such cases are to be handled (always round up, always round down or round to the nearest value that can be expressed as a monetary amount.

Another option is to create your own module for dealing with decimal numbers which might build on the decimal module or be written from scratch.

In many cases, the basic number types, integer and float, will be sufficient for working with numeric data, but in some cases you may want to use an additional module such as decimal or create your own.

The bool Type

As with most languages, the bool type is used in evaluating conditions, making comparisons and so on. It has two possible values, True and False (note the capitalisation) and the type is bool. Let's look at an example of that.

	x = True

	print('x is {}'.format(x))
	print(type(x))

The output from this is

	x is True
	<class 'bool'>

We will look at conditionals later, but it is important to note that while many comparison operators will yield a boolean result of either True or False, there are also other data types that can evaluate to false. For example, the data type NoneType which can only take the value, None, is the value a variable has if no value has been assigned to it. So the code

	x = None

	print('x is {}'.format(x))
	print(type(x))

will give the output

	x is None
	<class 'NoneType'>

The following example demonstrates the use of a variable of type NoneType in a conditional statement.

	x = None

	if x:
	print("True")
	else:
	print("False")

Running this will give the output

	False

We could have given x the value False and the result would be the same or we could have given it the value True and we would get the output, True or we could have inserted a condition which will evaluate to either True or False. However, there are also some values that evaluate to False and None is one of these. Similarly, 0 or an empty string will evaluate to False a non-zero numeric value or a non-empty string will evaluate to True so we don't necessarily need a condition to return a value of type bool in a conditional statement.

The following snippet of code demonstrates this. It tests a given number to determine whether that number is a power of two. This works on the principle that in binary, regardless of the number of bits in the number, if it is a power of two, one of the bits will be set to 1, the others will all be set to zero (because the individual value of each bit is a power of two. If you subtract 1 from a number that is a power of two, the bit that is set to 1 in the original number will be set to 0 in the new number and all following bits will be set to 1. Any bits before the 1 will be zero if it is a power of two in both numbers. To illustrate this, let's assume the number, x, is an 8 bit number with a decimal value of 16. In binary, the number is

	0001 0000

Subtracting 1 from 16 gives us a number we will call check which has a value of 15 and in binary, that is

	0000 1111

If we ignore the first three zeroes, the first number is the opposite of the second number so each corresponding bit will be different, either 1 in the x and 0 in check or 0 in x and 1 in check. The first three binary digits will be 0 in both but the main point is that there is no bit that is set to 1 in both x and check, so if we perform a bitwise and operation (in Python, the bitwise and operator is &), the result will always be zero if x is a power of two. As it turns out, if x is not a power of two, there will always be at least 1 bit that is set to 1 in both x and y, so the bitwise and will always return a non-zero value. For example, if x is 15, the binary value, again, is

	0000 1111

In this case, check will have a value of 14 which in binary is

	0000 1110

The bitwise and operation will return a binary result of

	0000 1110

We can put this altogether into a practical example to take a number and check whether a given number is a power of two.

	x = 16
	check = x -1

	if (x & check):
	print("The number {} is not a power of 2".format(x))
	else:
	print("The number {} is a power of 2".format(x))

So our bitwise operation will return a 0 if x is a power of two and a non-zero number if it is not so it is this zero or non-zero value that is used in the conditional statement to determine whether we print the first or the second statement.

Sequence Types

Sequence types in Python include list, tuple and dictionary, but there are others that can be imported. We will look at a list first, and anyone who has worked with arrays in any other programming language will be familiar with the square bracket syntax for both creating a list and accessing a list element.

	x = [ 1, 2, 3, 4, 5 ]
	for i in x:
	print('i is {}'.format(i))

A list in Python is mutable so we can change any element with a statement such as

	x[1] = 42

or

	a = x[1]

All Python sequence types are indexed from zero and a list can contain elements of any data type, including other lists. You can also add or remove items from a list and this is not usually the case with an array.

Although it is probably not unreasonable to think of a list as being an array, in Python, an array is one of the sequence types that you can import, in this case it comes with numpy or can be imported in the form of the array module. In Python, arrays have to be declared which you don't have to do with lists and, as far as I'm aware, you can't add items to or remove items from an array after it has been created (this is typically true of arrays in general and the fact that an array has to be declared suggests that this is the case, but we will investigate this to find out if our theory is correct!

The following demonstrates the use of an array in Python, in this case imported as the array module.

	from array import *

	array_1 = array.array("i", [3, 6, 9, 12])

	print(array_1)
	print(type(array_1))

If we run this and look at the output we will see the array itself and the data type which is array.array. The syntax here does look a little strange, we are using the arrays constructor method to create our array and the constructor takes two arguments, a string and an array. The string indicates the data type, in this case "i" for integer. This is known as a type code and you can find a list of these along with more information on the array module on the Data Flair website in an article entitled Python Array Module - How to Create and Import Array in Python. There is also an interesting article detailing the difference between lists and arrays in Python on the LearnPython.com website. The author is Kateryna Koidan.

The article referenced above also gives a list of array methods including append and remove, so it seems that it is possible to add or remove elements although I wasn't able to get this to work in VS Code. When talking about array methods and capabilities, it is important to remember that we are talking about arrays imported with the array module and the fact that we need to provide a type code is indicative of the fact that all of the array elements must be of the same type. However, if you import numpy, you can access arrays that can include different types of element, so if you are looking for online documentation on arrays, you need to ensure you are looking at the documentation for the module you are importing.

Tuples in Python are similar to lists, but they use parentheses rather than square brackets and they are immutable. According to W3Schools.com, this means that you can't change elements in a tuple or add/remove elements. The following code snippet demonstrates the use of a tuple

	tuple_1 = (1, 2, 3, 4, 5)

	tuple_1 = tuple_1 + (6, 7, 8, 9, 10)
	x=tuple_1[0]

	print(tuple_1)
	print(type(tuple_1))

	print(x)

Note that we have created the tuple in the first line of code and it contains 5 elements and we have added a second tuple, also with 5 elements and the resulting tuple contains all 10 elements, so clearly it is possible to add elements to a tuple. Also, note that we use square brackets rather than parentheses to access an individual tuple element which is potentially confusing.

If we add this line of code to the above

	tuple[0]=0

and try running it, an error now appears in the output.

	TypeError: 'type' object does not support item assignment

We are seeing this error because of the fact that we are trying to assign a value to an element of the tuple that already has a value and an assignment of this kind is not allowed.

For the sequences we have created so far, each element has been explicitly specified, but we can also create a sequence using the range function. The range function takes between 1 and 3 arguments. In the following example

	x = range(5)

	for i in x:
	print('i is {}'.format(i))

we have created a range of 5 numbers from 0 to 4. When only one argument is passed to range, it is interpreted as the upper boundary of the range. If we provide two arguments, these are interpreted as the lower and upper boundaries. As an example, let's say that we want to create a second range of numbers from 5 to 9. We can do that with

	x = range(5, 10)

This might be a little confusing, if we only use one argument as we did in the first example, it seems clear enough that the range will have 5 elements and since it starts indexing from 0 (as all Python sequences do), the range is 0 to 4. Another way of looking at this is that the range goes up to but does not include the upper boundary.

This is a little less clear when we have two arguments, since the lower boundary is included so the range goes from the lower boundary to the upper boundary and includes the lower boundary but not the upper boundary.

Now, let's imagine we want to count 10 down to 1, you might think that it is possible to do that by specifying 10 as the first argument and 0 as the second argument (effectively switching the upper and lower boundaries around). Although this seems logical, it won't work because by default, we count up in increments of 1 from the first argument until we reach the second argument so if the first argument is higher, we have already exceeded the second argument and the result will be an empty range. However, the third argument I mentioned earlier is the step so we can get our range to count backwards by specifying a step of -1 so the range would be expressed as

	x = range(10, 0, -1)

Note that when we switch these around, we are now counting from the upper boundary down to the lower boundary, and it is the lower boundary that is now not included. The step value can be any integer value, positive or negative so you could specify a range like this.

	x = range(0, 100, 5)

	for i in x:
	print('i is {}'.format(i))

In this example, we are counting from 0 to 100 (remember that 100 is not included in the range) in increments of 5 and outputting each number. This has the effect of outputting any number less than 100 that is also a multiple of 5.

One feature of a range is that it is immutable, which makes sense because it is intended to produce a range of numbers so you probably wouldn't need to make any changes to the elements of a range. On the other hand, you may want to have a mutable list that just happens to include a range of numbers, let's say from 1 to 10. Firstly, we would create the range like this.

x = range(1, 11)

A range is immutable, but a list isn't so we need to convert the range to a list with a cast, so our statement becomes

	x = list(range(1, 11))

If we run this, the output is the same as it was before we used the list cast, but now we can change individual elements of the sequence with statements like this.

x[0]="one"

Without the case, we would get the error

	TypeError: 'range' object does not support item assignment

The last Python sequence we want to look at here is a dictionary. This isn't a very common data type in that you won't, for instance, see a data type called dictionary in C or Java, but in generic terms, a dictionary is an Associative Array and most languages provide support for some type of associative array so this is a map in C++, Java or Go. The basic principle is that in the sequences we have seen so far such as list, each element is indexed according to its position in that sequence so when we code something like this

	x[0] = "one"

We are changing the value of the first element, whatever that currently is. The problem with this is that there is no direct relationship between the index and the value. That is, the index tells you where the value is in your sequence but doesn't tell you anything about it. Think of it this way, let's say that our sequence is a list of telephone numbers in a contacts app. In order to know whose phone number is first in the list, we would need some more data such as a second list with contact names so if the first phone number belongs to Geddy Lee, we will put that name first on the list.

A dictionary is designed to make this a little bit easier by providing a key for each value in the sequence. In this example, the first element in our list of phone numbers would have a phone number in the value and the key would be "Geddy Lee". This means that if we want to look up a phone number for Geddy Lee, we don't need to know where it is in the sequence, we just need to know the name stored in the key and we can use that to access the stored telephone number.

So, a dictionary is made up of key value pairs and when we are creating the dictionary, we use a colon to separate the key from the value, commas to separate the pairs and everything is enclosed in curly braces, like this.

	x = { 'Geddy Lee':2112, 'Geoff Hurst':1966, 'Ferenc Puskas':1954, 'Steve Gerrard':2005, 'Mo Salah':2020, 'Virgil Van Dijk':2019, 'John Lennon': 1962}

	for i in x:
	print('i is {}'.format(i))

Interestingly, I expected that the values would be output, but this actually outputs the keys so our output is.

	i is Geddy Lee
	i is Geoff Hurst
	i is Ferenc Puskas
	i is Steve Gerrard
	i is Mo Salah
	i is Virgil Van Dijk
	i is John Lennon

So in this for loop, we are iterating through the keys and I guess that we could use the key to access the value if we wanted to print that instead.

	for i in x:
	v=x[i]
	print('v is {}'.format(v))

Since i represents the key in each iteration through the loop, we can use that to access the corresponding value

	v=x[i]

So this is taking that key and setting the value of v to the value that has that key in x. We can use this method to access any value from the dictionary via the key so we might, for example, add a line such as

	print(x['Geddy Lee'])

This is perhaps a more practical way of accessing the data if you want to make use of an individual item rather than perform some operation on all of the elements. Of course, in a real-world application, it is unlikely that the value would be hard-coded like this, but would probably be represented by some sort of variable which is set to one of the keys in the dictionary based on some user response or input, but the principle is the same. For example, if we have a variable called k which is set to a key selected by the user (for example, Ferenc Puskas), the print statement might look something like this.

	print('Contact: {} 			Number: {}').format(k, x[k])

This will give us the output

	Contact: Ferenc Puskas                  Number: 1954

If you do need to iterate through all of the keys and values in a dictionary, we have already seen how we can iterate through the keys and use each in turn to access the corresponding value. But Python offers a shorthand for doing that.

	for k, v in x.items():
	print('Contact: {} 	Number: {}'.format(k, v))

In the earlier example of a for loop we saw, we iterated over the dictionary using one variable, i, which represented the key in each iteration. This time, we have two variables, k and v, and we can use these to access the key AND the value in each iteration and we use both of these in the print statement.

We've seen a number of different sequence types in Python and they all have some similarities and difference. One thing that they all have in common is that they are all ordered. A list and a tuple is ordered in the sense that each element occupies a specific position in the sequence so, for example, you can retrieve the first element and as long as you don't change that element (in a list, you can't change any element of a tuple in any case), you will always see the same element being returned. The order of the elements is therefore governed by the index value. In the case of a dictionary, each value is accessed using the key so the order isn't as obvious but we can access each of these in a predictable way and if we use a for loop to iterate through the dictionary and let's say we print out the key and the value in each key value pair.

You may have noticed that the order in which these are displayed is the same as the order in which they appear in the dictionary declaration so we can see that a dictionary does have an order. There are collections in Python, for example sets, that are unordered but we will look at them later.

type() and int()

Remember that everything in Python is an object. We have seen several examples using the type method to check the type of a data item so an integer have the data type int, floating point numbers have the data type float and so on. We could equally say that an integer is an object that is an instance of the class int or that floating point numbers are instances of the class float. The following is an example of this.

	x = (1, 2, 3, 4, 5)
	print('x is {}'.format(x))
	print(type(x))

This gives the output

	x = (1, 'two', 3.0, [4, 'four'], 5)
	<class 'tuple'>

On the first line, we are creating a tuple and outputting it on the second line. On the last line, we are using the type method to output the type, tuple, and note that this is shown as the class.

A tuple is a sequence so the class, tuple, refers to that sequence, but the individual elements can be of any type. For example, let's say that we want to check the type of the second element in the tuple. We can do that with.

	print(type(x[1]))

If we run this, we will see that 'two' is an instance of class str (string).

Every object is an instance of some class and it is normal to have multiple objects with the same class. For example, let's say we add a new line to our code to create a second tuple like this. We will also add a line to output the tuple and its type and we will see that the same tuple is output twice and the class is tuple in both cases, so these two tuples are pretty much identical, they each have the same set of elements.

Now, instead of type, let's see what happens when we use the id function. The full code example is now

	x = (1, 'two', 3.0, [4, 'four'], 5)
	y = (1, 'two', 3.0, [4, 'four'], 5)

	print('x is {}'.format(x))
	print('y is {}'.format(y))
	print(id(x))
	print(id(y))

The output we get from running this is

	x is (1, 'two', 3.0, [4, 'four'], 5)
	y is (1, 'two', 3.0, [4, 'four'], 5)
	2361731312912
	2361732383264

What is happening here is that not only does an object have a type identifying the class of which it is an instance, it also has an id which identifies each object uniquely. In this case, we have two objects that are both identical tuples, but they are still separate objects, they are both stored separately in memory and they both have their own id. If we replace the line creating the second tuple, y, with

	y =x

and run the code again, we get this as the output.

	x is (1, 'two', 3.0, [4, 'four'], 5)
	y is (1, 'two', 3.0, [4, 'four'], 5)
	1891582536976
	1891582536976

This time, y isn't just identical to x, it is referencing the same tuple and this is reflected in the fact that both x and y now have the same id. You might expect that if you made any changes to x or y, this would be reflected in the other but this doesn't seem to be the case. For example, if we add these two lines of code after the line setting y to the value of x

	x = x + ('x', 'hello')
	y = y + ('y', 'world')

and run the code again, the output is now

	x is (1, 'two', 3.0, [4, 'four'], 5, 'x', 'hello')
	y is (1, 'two', 3.0, [4, 'four'], 5, 'y', 'world')
	2049058419904
	2049058420864

The tuples are no longer identical so when we concatenated the x tuple with another tuple (which seems to be the only change you can make to a tuple), we have a new tuple. Note that the id is not the same as x and y had previously been. It may be worth just noting that you will get a different id every time you run the code because the tuples are being created and assigned ids every time the appropriate statements are being executed.

If we go back to the original version of the code that created a second tuple and instead of looking at the ids of the tuples, we will iterate through the tuples and print the id of each individual element. The code is

	x = (1, 'two', 3.0, [4, 'four'], 5)
	y = (1, 'two', 3.0, [4, 'four'], 5)

	print('x is {}'.format(x))
	print('y is {}'.format(y))

	print('Id for tuple x is {}'.format(id(x)))
	print('Id for tuple y is {}'.format(id(y)))

	for i in range(0, len(x)):
	print(id(x[i]))
	print(id(y[i]))

Note that I used a range to cover each element in the tuples and then used the range value to access the element and print its id. The output that we get from this is

	x is (1, 'two', 3.0, [4, 'four'], 5)
	y is (1, 'two', 3.0, [4, 'four'], 5)
	Id for tuple x is 2346649275824
	Id for tuple y is 2346651610384
	2346648758576
	2346648758576
	2346651399984
	2346651399984
	2346653488400
	2346653488400
	2346653478528
	2346651400640
	2346648758704
	2346648758704

We can see that x and y have different ids so they are identical tuples but they are not the same tuple. However, if we look at the pairs of ids, we can see that these are mostly identical, the exception being the fourth element which is a list. The other elements are all literals of some kind, including integers, a float and a string. When the program runs, Python creates these lists and stores them in memory somewhere and just as the tuples are identical but not the same, these lists are identical but stored separately in memory and so they have separate ids. Literals are only created once so the id for the number 5 or the string 'two' will always be the same in any program (although that doesn't mean it will be the same every time you run the program.

You may want to use an objects type in a conditional statement. For example, let's say that we want to execute a statement that prints something like 'x is a tuple' if it has the type tuple. You might expect it to work like this.

	x = (1, 'two', 3.0, [4, 'four'], 5)

	if (type(x)=='tuple'):
	print('x is a tuple')
	else:
	print('x is not a tuple')

We know that x is a tuple, but when we run the code, we will see the message that x is not a tuple and really this is because we are testing whether x is a string with the value 'tuple'. There are two possible fixes here. The first is to remove the quotes from tuple, so

	if (type(x)==tuple):

Alternatively, we can use the isinstance method which takes a variable and a data type as arguments and returns True if that variable is of the type provided. For example, to test whether x is a tuple, we might use

	if isinstance(x, tuple):

So if we run this code

	x = (1, 'two', 3.0, [4, 'four'], 5)

	if isinstance(x, tuple):
	print('x is a tuple')
	elif isinstance(x, list):
	print('x is a list')
	else:
	print('x is not a tuple or a list')

we will get the output

	x = (1, 'two', 3.0, [4, 'four'], 5)

We can change x to reference a list and this will give us output confirming it is a list or to any other date type (let's say we have x = 5) and this will generate outputting stating that x is not a tuple or a list.

To summarise here, if you need to test for a particular data type, you can use isinstance but if you just want to know what the date type is you can use the type function. You can also get the id of any object and you can use the id to determine whether two objects are identical or if they are in fact the same object. There is another function you can use to do that and that is the is function, actually, it would probably be more accurate to call it a comparison operator.

Let's see an example of that.

	x = 7
	y = 5

	if x is y:
	print('x and y  are the same object')
	elif x is not y:
	print('x and y are different objects')

We are using is to test whether they are the same object and since this is a binary operation (they either are the same or they are not) we could just use an else clause to output a message stating that they are not the same. However, I have used an elif clause instead simply to demonstrate the fact that we can negate is using the note operator and in this case, the output tells us that x and y are not the same object. If we amend this so that x and y are both equal to 7, when we run the code we will see that x and y are the same object. So, is gives us a handy operator we can use to determine whether two variables are referencing the same object.