Coding Practice - Python (Codeacademy)

As part of my quest for employment, I am brushing up on some popular programming languages. In the last couple of days I've been working through the Codeacademy Python course.

Codeacademy uses a very cool in-browser coding environment with step-by-step exercises to teach the basics of a particular language. The material occasionally felt too slow or hand-holdey, but then I probably have more programming experience than Codeacademy's target audience. My only real complaint is that some of their Python syntax is out-of-date.

Here are the notes I wrote while working through the course. They're rough and I'm mostly posting them for posterity, but they do explain a few points that the Courseacademy material did not cover properly.



PYTHON

Behaves like your typical sequentially-executing language, fairly close to Java with very little syntax

Variables are implicitly typed. Specifically, they are Dynamically typed: you CANNOT specify type

No semicolons at the end of lines
Whitespace, ie tabs, is used instead of brackets for things like if-statements

function calls still use () to hold their arguments


The above is sufficient for importing from Python's standard modules. To import from custom modules somewhere in your file system, do the following:


    Make an empty file called __init__.py in the same directory as the files. That will signify to Python that it's "ok to import from this directory".

    Then just do...

    from user import User
    from dir import Dir

    The same holds true if the files are in a subdirectory - put an __init__.py in the subdirectory as well, and then use regular import statements. For each level of directory, you need to add to the import path. So if the directory was named "classes", then you'd do this:

    from classes.user import User
    from classes.dir import Dir


Classes have their usual object-oriented internal functions.
ex. datetime.now()

you can print to console with a print command, which can be formatted with placeholders. For example (print'onetwothree%s' %'four') would print onetwothreefour

----

CONTROL FLOW

control flowis done using the typical if/else. "else if" is written as elif in python.

The exact syntax is ...

if __________ :
    ________
elif _________ :
    ________
else:
    _________


    Apparently python does not support switch/case statements becuase you can just write a big if, elif, elif.... to get the same effect. Which is stupid, because then you have to repeat the variable name and comparison operators over and over.

----

INPUT

Python can take input from command-line using variable = raw_input(string) where string will be displayed followed prompt for the user to enter their response and finish with the return key, and the response will be stored in variable.

String length can be checked with len(string)
String s can be checked for alphanumericy with s.isalpha()


----

FUNCTIONS

Python functions are defined in the following format

def functionName(parameter1, parameter2):
    code

Since we don't define a return-type for functions, we can implicitly return anything, or not return at all.

----

IMPORTING

Importing is done in a couple of ways:

"import module" will allow you to access any function in "module" by typing module.functionname

"from module import thing" lets you directly access thing, whether it is a function or variable

"from module import *" works like import module, except you'll be able to directly caccess everything in module without typing module._____

------

BUILT-IN FUNCTIONS

Python comes with a built-in function dir(module) which will list all the names in the namespace of module. If dir() is called with no argument, it returns all names in the current namespace. This is useful for keeping your namespace in check when importing modules.

max() takes any number of objects and returns the largest one. Because python is implicitly typed, this can get messy if is given arguments other than integers or floats.

min() is like max(), but returning the smallest instead

abs() returns the absolut value of a number.

type() returns the datatype of whatever argument you provide. So it tells you if something is an int, float, str, etc. Note that it returns a keyword rather than a string. Correct: blah == int,  Incorrect: blah == "int"

range() takes one or two integers and will return a sequential list of integers. If one integer is passed in, the list counts from 0 to argument-1. If two are passed in, the list counts from the first argument to the second-1. Not sure how this handles negative integers, if it does at all.

random.randint() takes two intgers and returns a random integer between the two arguments (inclusive)

-----

LISTS

Python lists can be created with the form list_name = [item_1, item_2, ....], including the empty list []. Items in lists are accessed by index, eg. list_name[0]

Along with python's automatic memory management comes dynamic-length lists. Items can be appended using listName.append('thing'). Items can be removed by index (list.pop(index) or by the item's value (list.remove(value)) or using the del(list[index]) command. Lots of options.

As with len(string), len(list) will return the number of items in a list as an integer

Lists can be "sliced" with the format slice = oldList[startIndex:endIndex:stride]. The result will be a list containing the elements of oldList from index startIndex to (endIndex - 1), counting stride between each returned item. Note that endIndex can exceed the actual end of the list by 1 in order to accessthe last element of the list. If no index is for start or end, slice will run to the respective end of the list. Stride defaults to 1.

Lists can be traversed backwards using a negative stride.

Note that strings can be sliced in exactly the same way, and I'd bet that under the hood strings are just lists of characters.

Lists can also be searched by contents to return index. Listname.index("item") will return the index of item.

Items can be inserted to a specific position in a list with listname.insert(index, item)

Items can be removed by value usng listname.remove("item")

"For" loops have a special interaction with lists. By using "for variable in list_name" you will do something to each item in list_name.

Lists can be sorted numerically or alphabetically using listname.sort(). This will directly modify the list rather than return a new copy. Not sure how this would behave on lists on non-numerical, non-alphabetic or mixed-type lists.

Lists of numbers can be summed with the sum(list) function.

Lists can be concatenated with "+"

Lists can be formatted for printing with any sepaarator (instead of ", ") using "separator".join(list). This will return a string of the list elements separated by separator.

"List Comprehension" allows Python to build lists while filtering them according to some condition. eg even_numbers_to_50 = [i for i in range(51) if i % 2 == 0]

------

DICTIONARIES

Dictionaries are like lists, but each element is manually assigned a string or number key rather than an automatic index (ie key-value pair)

An empty dictionary can be created with empty curly-braces {}, whereas lists use []

New elements can be added to a dictionary like so: dict_name[new_key] = new_value

 Elements can be deleted using the del command, like "del dict_name[key_name]". I wonder if it also supports the .remove(value) option like lists do? Definitely wouldn't have .pop since dictionaries have no defined order.

A list of all key-value pairs in a dictionary can be returned using myDictionary.items(). The items won't be returned in any particular order.

All keys or all values can be returned with .keys() and .values() respectively



--------

LOOPS

you can escape a loop with the "break" command

"For" loops iterate through lists and perform an action for each item. ie "for myItem in myList:"

"For" loops can keep an index of each recursion using enumerate(). eg. for index, item in enumerate(list):

"For" can iterate over multiple lists simultaneously using zip. eg. for a, b in zip(list_a, list_b). Pairs will be created in index-order, and will stop at the end of the shorter list.

"For" loops can have an else:, like while loops. This else will be executed when the loop exits normally (ie, finishes the list) but not when the loop is broken. As with while, the else clause wil be executed even if the loop itself never actually executes (ie, if you pass in an empty list)

"While" loops perform an action so long as a condition is true. ie "while someCondtion:"

While loops can have an "else:" condition which will execute when the while condition evaluates to false. So the else will execute when the loop exits normally or even if the loop is never executed at all. However, breaking out of a while loop will skip the else.


-------

Functional Programming / Lambda

Python allows functions to be passed in, like any other value, using lambdas. For example, a lambda function wth one parameter would be  (lambda x: x % 3 == 0)

A lambda function can be useful as an argument to the built-in function filter(). For example, Filter(lambda x: x % 3 == 0, my_list) would return the elements of my_list which cause the lambda function to return True. I could imagine this being useful for any sort of customizable search-engine, where there can be a single search function and the user's chosen filter settings are passed in as a unique lambda function.

---------

Bitwise Operators

Binary numbers can be written in python by using the prefix "0b"  For example, 0b10 represents binary 2. Note that when you try to print a binary number, the decimal representation will be printed. To get the binary representation, you must use bin().

bin() takes a number as an argument and returns its binary representation. oct() and hex() do the same or octal and hexidecimal respectively.

When using the int() function to convert a strubg, you can actually provide two arguments: a string, and what base the string is in. So if you have int("110", 2), the result will be 6 instead of 110. However if you are calling int() on a number (ie 0b00, or 1.5), then you do not need to provide a base and python will get mad if you do.


& is bitwise AND... ie (1 AND 0 == 0), (1 AND 1 == 1). Bits are compared from right to left, and ends when either number runs out of bits. if x == 101 and y = 0110, then x & y will return 100

| is bitwise OR. Works like AND, except (1 OR 0 == 1)

^ is bitwise XOR, works like OR exceot (1 XOR 1 == 0)

~ is bitwise NOT, it takes a single binary number and flips all of its bits. This includes the sign bit, which makes things really damn confusing. ~0b100 == -0b101, where I would've expected it to be ~0b011. This would probably make sense if I knew how Python actually stores numbers in memory.

A "bit mask" is a variable holding a binary number, which we can use to compare with other binary numbers to quickly scan or control specific bits in those other numbers.

We can use a bit-mask with bitwise AND to determine if a given bit in a given binary number is 1. For example, to check the third bit we creating a bit mask 0b0100 and using bitwise AND on the given number with the mask. If the result is 0, then the third bit in the given number must've been 0. If it's 1, then the third bit must've been 1.

We can use a bitmask with bitwise OR to change a given bit in a given number. If we want to turn the third bit on, do newNumber = 0b0100 | oldNumber. To turn a bit off we would use bitwise AND, and a mask of all 1's except for the bit we want off. This unfortunately requires knowing how many bits the old number contains.

We can use a bitmask with bitwise XOR to flip the bits of a number without all that sign-nastiness of NOT. Just use a bitmask full of 1's. ie, to flip the rightmost four bits of oldNumber, do newNumber = oldNumber ^ 0b1111

>> and << shift the bits of a binary number right and left respectively. For example, 0b001 << 1 == 0b01. These mare mathematically equivalent to divding and multiplying the numbers by 2 respectively. Note that this will only work properly on integers, not on floats or strings.

>> and << are useful for shifting masks to hit whichever bit we want them to. For example if we could start with a mask that flips the first bit, 0b1, determine at runtime that we want to flip the Xth bit, and then shift the mask (X-1) bits to the left before applying the ~XOR operator. This would flip the Xth bit.

----------

CLASSES

Python is an object-oriented language. We create objects, which have attributes (like variables) and methods (like functions).

We specify a class as follows: "class Name(parent):" We can use "object" as a generic parent, and thus I suppose object ends up being the top-level parent for EVERY class.

Every class must have an __init__() function defined, which takes at least one argument (by convention, we call this argument "self"). This first argument is how Python will refer to the class we are creating.

Inside of __init__() we can create and initialize attributes for the object, using "self.attrubuteName = value". We might add more parameters to __init__() so that a coder can pass in values when creating an instance of the class, and use those values to initialize the class's attributes.

Global variables are accessible everywhere in a program, member variables (aka class variables) are accessible only to members of a certain class (but still have a separate value per instance), instance variables are accessible only within the instance of the class where they are created. Class variables can be modified for ALL instances simulatenously by addressing your change to the classname

    e.g if you define a class myClass and isntances One and Two, then you can change both one.myVariable and two.myVariable by using myClass.myVariable = whatever.

    However, if you address your class-variable change to a specific instance, ie one.myVariable = whatever, then rules around class variables change depending whether the variable is mutable (lists and dictionaries ARE shared) or immutable (numbers, strings and tuples are NOT shared). In general, DON'T DO THIS. If you want to set a variable for a specific instance, use an instance variable so that you know it'll work. This little issue gives some insight into Python's C heritage (maybe lists and dictionaries are really just pointers, whereas the immutable things are actually stored by value?)

    You cannot set an instance variable for all instances at once with myClass.myInstanceVariable = whatever. This code will simply have no effect.

    However, you CAN access and set an instance variable for a particular instance, the same way you could set an immutable class variable for that instance.

Whenever ANY class method is called, not just init, the first argument will refer to the class. We need to account for this argument, so every method we write should have "self" as its first argument.

Classes can Inherit methods [including __init__()] and attributes from parents, by replacing "object" in the class definition. ie "class DerivedClass(BaseClass):" These inherited methods can be overridden by simply defining a new method with the same name.

You can use the "super()" function to call the function which was defined in a parent, in case you have overridden that function in your current class and need to use both versions. But apparently Python supports multiple inheritence and the documentation is half-wrong and codeacademy is out of date, so I have no idea what to believe. I'll have to figure this out in a live environment, and probably avoid it as much as possible.

There are other functions (inherited from object, probably) which can be overridden for useful effects.

Overriding __repr__() allows you to set how your object should be represented on command line, for example if you call print myClass

---------

FILE I/O

Python lets you open a file in a mode (ie, "write") do things with the file, and close it.

Open() takes two arguments, the path to a file and a mode, and returns a representation of the file. eg. f = open("output.txt", "w")

"r+" lets you read and write

fileVariable.Write() takes a string argument which will be written to the file stored in fileVariable. Newlines "\n" have to be added manually.

fileVariable.Close() closes the file, which causes python to write the changes in its buffer to the file. If you "write" but don't flush the buffer, you won't make any changes.

fileVariable.read() returns a string of the entire contents of the file (including newlines)

fileVariable.readline() returns a string containing one line of a file, including the newline char. I guess it would be presumptuous for readline() to cut it, and silly to put it anywhere else.

With / As are keywords you can use to automatically close a file when you're done. I suspect these keywords can be used a lot more broadly. The following example will open a file, write to it, and close it.

    with open("file", "mode") as textfile:
        textfile.write("Success!")

fileVariable.closed returns a boolean showing whether or not the file is closed.