Introduction to Python Notes
Yay, let's learn how to talk to computers. It'll probably pay more than my english degree. XD
the pound sign indicates the beginning of a comment
Python uses int to represent positive and negative whole numbers (integers)
Strings (sequences of Unicod characters) are represented using the str type
As I understand, a "literal" is a functional example of these types.
Delimiting a string is done by adding quotes both '' and "" are allowable so long as the opening and closing match.
Python isn't limited to the use of ASCII characters, it uses unicode. You can also have empty strings ""
Brackets  are used in order to select an item from a sequence. (it starts from zero and counts up). You may also select from any data type using this method.
Strings and integers are "immutable" data types. You may not use brackets to set a character. In python a character is simply a string length of one.
In order to convert a datatype from one type to another we may use datatype(item)
ex: >>> int("45")
If a conversion fails an exception is raised.
Python's variables are called "object references"
In Python the = isn't exactly the same in other languages. = binds an object reference to an object in memory. If the object reference is already bound, it rebinds with the newest object value.
The object reference is the first variable in this statement and the object is the second.
x = 245
Object references are called identifiers. They must not interfere with Python keywords and must start with a letter or an underscore. They are case-sensitive.
'Dynamic typing' means that an object reference can be re-bound to refer to a different object at anytime. This is not the case as in C++ and Java which use strong typing in which an indentifier may not be bound to an object of a differing identity type.
The type() function returns the data type also known as the "class."
The print() functions may be foregon when using an interactive interpreter. In such a case, we may type >>> x, y, z in order to return their values. The print() function must be used for working code. A "tuple" is an ordered immutable sequence of objects.
Python allows for the collection of data types which may hold items such as associative arrays and sets. "Tuples" and "lists" can be used to hold any number of data items of any data type. Tuples are immutable (we may not change them), lists may be changed however. We can insert and remove items in lists at any time. We may create a tuple by entering a comma seperated sequence of items
ex >>> "Medford", "Portland", "Jacksonville"
In order to create a tuple (even one with only one item) we must use a comma after the first item. >>> 'one',
Empty tuples are created by using empty parenthesis >>> ()
We can creat a list by using square brackets 
ex >>> [1, 4, 9, 16, 25, 36, 49]
['alpha', 'bravo', 'charlie', 'delta', 'echo']
['zebra', 49, -879, 'aardvark', 200]
Lists and tuples actually store object references. When they are created they take copies of the object references they are given. When it comes to literals like integers and strings, an object of the appropriate data type is created in memory and initialized, and then an object reference referring to the object is created, and it is this object reference that is put in the list or tuple.
Collection data types are objects, therefore we may nest them to create lists of lists. Sometimes the fact that lists and tuples hold object references and not objects makes a difference.
In procedural programming we call functions and may pass in data items as arguments. >>> len() is a function which takes a single data item as its argument and gives the "length" of the item as an integer.
>>> len([3, 5, 1, 2, "pause", 5])
Tuples, lists, and strings are "sized", meaning they have a given length that is considerable. If an argument is passed to len() that doesnt have a notion of size then an exception is created.
Data items are objects and are also called instances of a particular data type, which is known as a class. Python's objects differ from data types of other languages in that objects can have methods. A 'method' is a function that is called for a particular object. For example the list type has an append() method.
Therefore we can append a list like so:
>>> x = ['zebra', 49, 'elbow', -9]
['zebra', 49, 'elbow', -9, 'more']
The 'x' object, aforementioned, has knowledge that it is a list in reality. Therefore we don't have to specify that directly when using the method. We can also procedurally do this operation by using >>> list.append(x, 'more') This would append 'more' to the list x because x is passed as an argument which is recieved as the list.
We can add strings and integers to lists by using the methods 'append', 'insert', and 'remove'. Append seems to just add the list item onto the end of the list. Using insert, we can specify a position of index to insert the new object followed by a comma and then the value one would like to insert. Remove seems to search for the value that you would like to delete from the list. I think it simply picks the first value that it finds to match your argument, removing only that one, and not removing any subsequent identical values. When using the insert method, it is not neccesary to add anything to query when specifying position, simply use a discrete number that is within the index range and begins with zero as the first position.
Here on page 20 a difference is made between object oriented and procedural programming. I'm not exactly sure what the differences are, however, I understand that in OOP, objects are directly edited, or something like that. Procedural programming seems to work in a more linear fashion, possibly.
An 'attribute' can be any kind of object, so far we've only seen method attributes. Attributes are objects which may have more attributes, therefore we can access them by way of using the "access attribute dot." This type of nesting can continue for a long time in order to get to the attribute you want.
The list type has several attributes. Of which some are
insert() -- insert and item at a given index position
remove() -- remove an item at a given index position
We can retrieve items from lists and tuples using the  that we spoke of before. simply call the object reference of the list and then append it with square brackets. Pass an integer as an argument in order to retrieve the sequenced items. Lists can be accessed in order to change them ex: >>> x = 'forty nine' would add the string 'forty nine to the index position of 1 in the list. It's important to know that when adding a value to a list in this manner you will certainly destroy whatever value occupied that position previously.
Python defines precisely what features a 'sequence' must support and also defines the features that a sized object must support.
These are fundamental to any computer language. In Python we are provided with four.
The Identity Operator
Because all variables in Python are object references, it makes sense to ask whether two or more object references are referring to the same object. The 'is' operator is a binary operator which returns 'True' if the left handed object reference is referring to the same object as the right handed object reference. The left hand identifier is a, whereas the right hand identifier is b.
>>> a is b
The strange thing with the is operator, is that we may have a string or integer that is identical to that of another, yet the identity operator will return false because the object references are not referring to the same instance, just identical yet seperate instances.
The most common use case for this operator is to compare a data item with the built in null object which is 'None'. It is used as a placemark for that which is unkown or non existent. 'None' is a python specific value, so using it otherwise is probably going to throw an error. Important: if a object reference has not yet been defined, you can't just go asking if it is None. It seems None is testing for an actually value of None, I can see this being useful if you would like to set a variable before executing other code. Instead of creating the variable and giving it a string or integer value, you can just give it a None value. This is called a 'NoneType' by the Python Shell.
We can invert this operator by using 'is not'. Basically this operator is used in order to see if two object references are referring to the same object, or to see if an object is None.
Remember, this is testing for identity, not value. It usually only makes sense to use this operator when you want to know if the exact same object is being specified by the different references. This seems to be necessary because of the dynamic typing that Python employs. Maybe also because of the object orientation paradigm that is being used. I suppose at the heart of python is the 'object', or 'that which may be referenced'. It seems that assigning a variable to the same integer will give you a True for identity. This is because Python is reusing the same object for efficiency's sake. They tell me this is not a problem, but I don't know if I agree, because who is to know when python will employ this. They tell me that this doesn't matter so much, and that one must simply remember to always use value operators if you are comparing values and use identity operators only when you are actually comparing against None or exact sameness of referenced objects.
Identity comparisons are quickly executed because the value of the object doesn't have to be evaluated. Only the 'identity' of the object. That is to say 'is this object the very same object?' It would seem to me that this would be a boolean test. Which would make sense if it is executed quickly.
We can test for identity using multiple operators seperated by commas. >>> a is not None, b is None This is a valid operation, and I think it would probably also work for other types of operations as well.
< less than
<= less than or equal to
== equal to
!= not equal to
>= greater than or equal to
> greater than
These operators compare object values in accordance to the object reference that is used to identify them.
Here are some example operations:
>>> a = 2
>>> b = 6
>>> a == b
>>> a < b
>>> a <= b, a != b, a >= b, a > b
(True, True, False, False)
It works with strings too:
>>> a = 'many paths'
>>> b = 'many paths'
>>> a is b
>>> a == b
This shows the difference between the identity operator is and the comparison ==. They may have identical values but they have different identities.
A cool thing: we can chain these comparisons together like so:
>>>a = 9
>>>0 <= a <= 10
Python allows for this "chaining" together of operations. Which is nice because it cuts down on typing. I don't know to what extent it will allow this kind of syntax. It apparently doesn't work if you are trying to assign values to variables. To do this otherwise you could use the logical 'and' operator.
"3" < 4 outputs an error because we don't just go and start converting strings to integers without asking first, duh.
Python allows for the creation of custom integer types that can interact differently with out types. (How to do that, or in what case it would be best is beyond my knowledge)
The Membership Operator
For data types that are sequences or collections such as strings, lists, and tuples we may compare the membership of one item against the other.
>>> p = (3, 5, 'fort')
>>> 5 in p
>>> 'forte' not in p
We can do this for strings as well:
>>> phrase = "All the red ants bite the green frog."
>>> 'A' in phrase
Conveniently we can also search for membership of substrings (parts of the string)
This can be a slow operation when dealing with very long lists or tuples, but it is supposed to be fast when searching a dictionary or set (these are covered later). Apparently, this operation searches linearly through the list.
Here they note something called a 'traceback', or a 'back trace'. This is used for error handling. I suppose each thing done in Python is a 'call' and the number of operations done is called a 'callstack'. This is used for dealing with errors, when an unhandled exception is found it traces all the calls from that point back to the very first call made by the script. (Or so I think)
Python provides three logical operators:
Both and as well as or are 'short circuit logic', they return the 'operand' that determined the result. They don't return the boolean True or False (unless they actually have Boolean operands)
>>> five = 5
>>> two = 2
>>> zero = 0
>>> five and two
>>> two and five
>>> five and zero
These must be pretty useful, but I'm not yet seeing the concrete performance capabilities of this abstraction. It seems to be just spitting out the operand that decided for the operation.
Control Flow Statements
I'm assuming these are statements that can control the flow of the program. Python begins at the top and works its way down the program. A control structure or a method or function can control what is executed in the program. I'm assuming that is what this chapter is about. Exceptions will also divert the flow of the program.
Here is discussed the 'if' statement found in python. Also discussed is the 'for' and 'while' loops. I suppose these are control structures.
A 'boolean' expression is anything that can be evaluated to produce a Boolean value (True and False).
These things are evaluated as "False" by Python.
-A predefined constant False
-The special object None, as mentioned before
-The integer value 0
-An empty collection or sequence (sequence here is not well defined but we will include lists, tuples, or empty strings)
Everything else is considered True. When using custom data types, one may choose exactly what solves as a False Boolean.
In Python, we call a block of code a 'suite.' Sometimes Python's syntax requires that a suite be present if it is to execute some amount of code. For this reason the Python specific keyword 'pass' may be used. Apparently, this keyword does nothing and it can be used where a suite would otherwise be required in order to execute the code. It can also be used to skip a piece of code that is included in order to show that a certain use case has been considered.
Generally, an if statement in Python will look something like this:
It is not neccesary to use elif: clauses when crafting if statements. The final else: statement isn't neccessary either. I suppose these are simply control structures that may be utilized in order to add complexity to the program where it is needed. Interestingly, we can use the 'pass' keyword in the suite of any elif or else statement in order to do nothing in the event that the expression finds a compatible value to operate on. Each of these elif and else statements are also known as 'branches.' Colons are a neccessary part of the Python syntax and must be utilized. Python doesn't use parentheses or brackets in its control structure syntax, which is nice.
Python uses indentation to signify its block structure. You'll get used to it they said. Trust me they said. Python style guide recommends exactly four spaces to be used in order to signify its block structure. Sublime text should be able to do this using the tab key. However, Python should work fine without having exactly four spaces. But it is always good to be consistent.
The while statement is used to execute a block of code zero or more times. The number of cycles that the code loops depends on the state of the while loops Boolean expression.
We may also employ the 'break', 'continue', and else clause. The break statement breaks out of the loop. The break statement switches control to the statement following the innermost loop in which the break statement appears. The continue statement switches control to the start of the loop. Both break and continue are normally used inside if statements in order to change the behavior of the loop.
item = get_next_item()
if not item:
This while loop will run only while items are available. The while statement has a suite which includes the if statement. The if statement's suite is composed of the break.
The For ... In Statement
The for loop in python uses the membership operator that we have spoken of elsewhere.
for variable in iterable:
The for loop also supports break and continue as well as the optional else clause. In this loop the variable is an object inside the collection 'iterable'. Strings may be iterated over (character to character), lists, tuples, and other collection data types. When setting the variable for the iterable sequence, you may choose any variable that will end up corresponding with the arguments that you use in the suite.
The advantage of using a for loop to print out a list or tuple is that it allows you to gain greater control over the formating of the data set.
in this example we can identify a few things:
for letter in 'ABCDEFGHIJKLMNOPQRSTUVWYXZ':
if letter in 'AEIOU':
print(letter, 'is a vowel')
print(letter, 'is a consonant')
Firstly, we may see that 'letter' is the variable first signified in the 'for' statement, and then it is used subsequently in the 'if' statement suite. The most important thing is that whatever this variable is, it should stay the same, or else you will throw and exception for an undefined variable.
Moving on to exception handling. Exceptions are objects in Python. When an exception is printed it is converted into a string. This produces a message text. Here is a syntactical form for exception handlers:
except exception1 as variable1:
except exceptionN as variableN:
I suppose you don't need to use the 'as variable' part, that is if you don't want to read its message text.
During the try block the code is executed. If there are no exceptions raised than you've got nice code. If an exception is raised it will go to the first matching exception that is found in the except statement. If an as variable statement is used, then that exception will be saved as an object with the object reference now being the variable that was stipulated. That is pretty cool, and very object oriented.
Here is an example of handling an exception when you want to only be able to have a discrete number input to a form:
s = input('enter an integer: ')
i = int(s)
print('Valid integer entered:', i)
except ValueError as err:
In this code we have a try block which opens up the testing of the input which is assigned the variable 's'. 'i' signifies the input of the user and is changing it with the int() function. This would usually convert an integer into a whole number, or a string as well. If an exception is raised in the form of a ValueError (which I'm assuming is a built in placeholder in Python) then it is passed of to the variable err, which is then printed in the following suite. Exception-handling is sometimes considered an advanced topic. I guess its really important to how Python works, so its important to learn it now.
Python contains a full set of arithmatic operators, which includes binary operators for the four following mathematical operations: Addition (+), Subtraction (-), Multiplication (*), and Division (/).
Additionally, Python data types can be manipulated with 'augmented assignment operators'. These include += and *=.
The +, -, and * all behave as one should expect when both of the operands (the values used as arguments to be operated upon) are integers.
The - operator can function as both a unary (negation) and a binary (subtraction) operator. This is similar to other languages. Python, however is different when it comes to the division operation. When the division operator is utilized in Python it converts the result into a floating point value (3.0) instead of an integer. Many languages would just truncate the floating point value and give you an integer. But Python will actually convert the value to floating point all the time. This can then be converted back to an integer by using the int() function. There is also a truncating division operator, but we'll save that for later.
Augmented assignments work differently in Python, I think mostly because of the dynamic typing object orientation of the working paradigm. For instance, when one has an augmented assignment operation such as >>>a += 8 we are not simply mutating the value of the object that a was before it was thrown this augmenting assignment. Let's say that >>>a = 4 If we are to do this >>>a = a + 8 we are not doing the same exact thing. In that example we are referencing the value of a twice. Whereas when we use the augmented assignment operator we are only calling a once. This is supposed to make a difference if you are manipulating a dataset that is prown to error. Essentially, because integer object are immutable, we aren't simple changing the value, but we are creating a new object in memory with the augmented value of the previous object and then we are rebinding the object reference 'a' to this new object. If the old value is nowhere referenced, such as in a previous b = a assigment then the old value is sent for garbage collection. This is an interesting aspect of the way that Python is handling objects.
Again we have an example that shows how the augmented assignment operators are working:
>>>name = 'John'
>>>name + 'Doe'
>>>name += ' Doe'
Here we can see that the augmented assignment operator is reassigning the value of name to John Doe, whereas before it was simply spitting out the result of adding a string onto the given object 'John'. The new value of 'John Doe' is actually being stored in a new object associated with the variable name. It's important to note again that strings are imutable objects, so Python is actually creating a new object witht the value 'John Doe.'
When can use an augmentor with lists. Because lists are mutable objects, we don't have to create a new one in memory in order to append one. Here is a good example of using the operator.
>>>seeds = ['sesame', 'sunflower']
>>>seeds += ['pumpkin']
['sesame', 'sunflower', 'pumpkin']
If I'm not mistaken the same thing could be achieved if we did this:
or possibly this:
We can add all sorts of things to lists. But we must make sure that what we are attempting to add is an iterable itself. This means that you can't attempt to add an integer to a list using the augmented assignment operator. We'll I guess you can try, but it won't work. In orde to do such a thing one would need to enclose the integer in brackets in order to format it correctly. Remember, you can add a sorts of nesting lists into lists, so one can add an existing list of items to a list. If you attempt to augment a list with a string that is not enclosed in brackets then you will end up adding a whole bunch of items to the list, each being one character long. If you want this kind of thing to happen, then good, but just make sure you know that it is going to happen. You don't have to worry about this kind of thing if you are using the append method.
The Input Function
Python uses the input() function, a built in function which accepts input from the user. The input function takes an optional string argument that is printed on the console. Then it waits for the user to input a response and press Enter. If the user simply pushes Enter and doesn't put in any text, then the function simply returns an empty string. If the user does enter text, then the function returns a string containing the contents of the input, without a line terminator.