A Space Adventure Introducing Python

Written by jmuzsik | Published 2017/12/13
Tech Story Tags: programming | python | javascript | coding | software-development

TLDRvia the TL;DR App

This article is not targeting the absolute beginner. It is specifically for JS Developers or the like looking for a fun introduction to the incredibly popular programming language Python. Many concepts explained: multiple inheritance, list comprehensions, the basics, and peculiarities of the language. All code can instantly be copied/pasted into a repl, and there is a repl near the end of the article to look directly at.

In a universe of 90328443435329598 alien beings. Two new beings came from the void…

Python Alien

Hello human. Your name is Marcel or so I hear. And you are one of 7530305890 human beings.

I am a python and my name is Guido, one of 3 python beings.

Now, there are: 90328443435329600 alien beings.

Now, some code, that compiles to exactly what was written above:

total_aliens = 90328443435329598 . That is to say that Python is dynamically typed.

class Alien(object): as well as class Human(Alien): expresses that the Alien class inherits from class object while Human inherits from class Alien.

def is simply how a function is created.

__init__ is what is automatically run when a new instance of a class is created, it is the initialiser.

self : Every method in a class necessitates this first argument. It does not have to be self but this is the convention. This is what the purpose of self is, taken from a concise description on Stack Overflow:

  • instance_object.parent_class_method(arg) internally converts to: parent_class.parent_class_method(instance_object, arg) . So self binds the instance object to the method invocation, similar to this in JS, with some subtleties to be aware of.

Lastly, global : writing it as above references the global variable so it can be altered within the functions scope.

super interacts with the direct parent class that Human inherits from, Alien. The argument sets self.being of the new instance to be equal to human.

pass is necessary in Python when you write an empty function, an empty if/else or anything of the like. If left without pass an error occurs.

A new instance is created as shown. print is how one logs out values.

Grabbing values associated with the instance of the class is done with dot notation.

Ok, onto functional programming and a continuation of the story.

Human, I have six tasks you must accomplish to leave this planet:

To leave this planet, there is a generator function that runs each instance in which you overcome a problem. This builds up fuel for your spaceship home. This is the function:

First, I give you a list( _['str_1', 'str_2', …]_), six strings will be inside it. You must check what the first letter of each string is numerically (a is 0, b is 1, etc.), then check if this index exists in the list, and finally add that string to a new list. Return the new list.

This returns ['damnation', 'fuggedaboutit', 'zoological', 'buttercup'] .

[] : in this case it is a list, not an array. They are slightly different.

  • The major difference: array([3, 6, 9, 12])/3.0 (array syntax) returns array([1, 2, 3, 4]) while [3, 6, 9, 12]/3.0 (list syntax) returns an error.

for str in strs: is the predominant type of loop one sees in Python. It iterates through each index’d value starting at the zeroth index.

some_list.append(some_value) pushes the specified value to the end of the list.

some_list.index(some_value) looks for some_value inside the list and returns the first index where the value exists. If it is not found, ValueError exception occurs.

You are at: 17% fuel. Now, a different task. Another function. First argument: a single number. Second argument: a dictionary (_{‘some_key': 'some_value', …}_) containing a key called _a_list_ with a value that is a list full of strings (_'a_list':[...]_) and a separator ( _'separator': ‘something'_). As so: _{‘a_list': […], ‘separator': ‘something'}_). The first argument specifies how many strings to use from the list. Store each string concatenated with the separator into a single variable or memory location and return that.

This returns: ‘Trump is terrible! Genghis Khan is terrible! That noisy person is terrible! ’

range(num) is an iterator of the number specified. If only one argument it iterates from zero to the number minus one. Range can take three arguments, it is similar to a condensed for loop: range(-10, -100, -30) => -10 -40 -70 with whatever for this_thing_is in range(...) being the => values.

{...} : dictionaries are unordered key/value pairs or associative arrays. The keys must be strings and to access the values through bracket notation one must also insert a string value, or a variable name.

Your now at: 34%. Next, I give a more confusing challenge. For one, you do not know how many arguments are given! The first set of arguments contain either a number, the value None, or a string. The second set contains key/value pairs associated in this way: _some_keyword=some_value_. _some_keyword_ will be a string. _some_value_ will be a number. Return a dictionary. One key/value. The key must be only the strings I first gave you (the arguments) in reverse order. The value must be a concatenated string, only use the string in the keywords from the zero index up to the number specified as the value, all into one single string without spaces.

This logs out: {‘big amazing animal’: ‘elephant’} .

If you print out args prior to reversed(args) you see that args is: (‘animal’, ’amazing', None, 6, ‘big') . This is a tuple. A sequence of immutable objects. Tuples are written with parentheses and they cannot be altered the same way as a list or array, as in, altering at specific indexes.

enumerate allows one to have an automatic counter and access the values simultaneously.

if/elif/else is how to write if/else statements.

is None is how one returns a true or false value for checking the None value. It is similar to null but more so undefined in JS as many instances of code can return this value such as when one alters a list through a higher order function that one would not expect to return something or when a function does not return anything, it returns None.

isinstance(arg, int) is used to check if the arg is an instance of a specified class. int in this case but something like this can also be expressed:

  • isinstance(marcel, Alien) will return true. Classes, strings, ints, and all else you expect can be checked in this way.

Ok, good. You are at 51%. Now, an interesting one. You are given a matrix. Three lists in a list, each list has four strings inside. First, make a new list associating each index of each list as so: (_[[[0][0], [1][0], [2][0]…],[[0][1], [1][1]…], ...]_) then, flatten each list to a single string, and finally join that list into one single string. You can fit all of this logic into one statement… a list comprehension.

This returns: You must make it look this way. Very extremely, magically, important thing to do!

First: [[row[i] for row in matrix] for i in range(4)] . The same thing:

One must think from the most outward brackets/logic in.

So: [all_the_logic] is to say: create a new list and whatever is done inside here will determine what the list looks like in the end.

[[some_inner_logic] for i in range(4)]: This will specify there to be 4 rows and whatever[row[i] for row in matrix] results in at each instance is what each row will be.

[row[i] for row in matrix] means to use the index, i (0, 1, 2, 3) from range as be aware it is constant while the first, second, third, and fourth row is being created. During each row creation this occurs:

  • Loop through each row in matrix =>push value at row[i] into this newly created list => append this list to the initially created list.

At this point this is what is created: [['You’, ‘must', ‘make'], ['it', ‘look', ‘this'], ['way.', ‘Very', ‘extremely,'], ['magically', ‘important', ‘to do!']] . A transposed matrix.

Onto: [str for sublist in [transposed_matrix] for str in sublist]

Again, outward brackets to innermost.

Create new list: [result_of_logic]

Read from the furthest-to-left for loop: sublist in [transposed_matrix] is to say: loop through each list within the matrix, starting at the first.

Then read onto the next for loop: for str in sublist . This equates to saying to loop through each str within this sublist. That is the value now available to append to the list.

Lastly, look at the beginning of the list comprehension: str . That is what is appended to the list: ['current_str', 'second_str', ...] . So the left-most value is what is, in a way, pushed into the newly created list until the loop is finished. As if append automatically occurs.

As for: ''.join(final_list) , '' is the string separator.

Notice how the left-most for loop is what first occurs and the further to the right one goes is the nesting.

Ok, now you are at 68%, only two left to go! This one I need you to create a factory function. There will be five functions to implement. _append_ (push to end of list), _extend_ (concat one list to another list), _insert_ (insert item into specific index), _remove_ (removes each instance of item specified), and _pop_ (pop off final item in list). You cannot use the higher order function equivalents and each function that you create cannot mutate the original list. You are given three or four arguments.

  1. a list
  2. the name of the function as a string
  3. the one argument the function needs, index if it needs four (for insert)
  4. an item (only needed for insert)

Ok, good luck!

Know that each of these functions are not the optimal way to do this. Using the associated higher order function is much quicker. Also, know that the higher order functions all return None except pop which returns what was popped.

append and extend: This is simply a subtlety of Python. Adding a list to another list automatically concats the two lists. li.append(item) and li.extend(other_li) is the proper syntax but these two options mutate the original list while the ones written above do not. other_li in extend is not mutated though.

insert and pop both use slices in this case. I’ll focus on insert . Notice: the_list[:idx] : this means to slice the list up to the index but not including it. the_list[idx:] : is to say to slice from and including the index up to the last index of the list.

remove : a simple list comprehension (they are extremely fast operations btw). [x for x in the_list if x != item] : new_list = []=> for x in the_list: => if x != item: => new_list.append(x) or, but not technically new_list += [x] . Though, be aware that the real li.remove(item) solely removes the first occurrence of the item. This remove removes all occurrences.

Very well done, onto the next one. Your at 85% fuel, this is the last one! Next one is a bit of an oddity as it is two functions in one. I am to give you a wide assortment of keywords and arguments. Your function will be called multiple times with a random amount of arguments or no arguments. And/or a multiple assortment of keywords. I need you to store all the argument values that I give you in a non-global list, and all the keyword values in a non-global dictionary. Return a dictionary as so: _{'the_list': [...], 'the_dictionary': {...}}_ . Each time the function is called the list is altered or the dictionary depending upon the input and all values are appended to the previous calls values.

In the end, the final calls list is unpacked as the first argument and the dictionary is unpacked as the second argument into another function.

(*args, li=[], dic={}, **keywords) : Notice that the two arguments that are not keywords or arguments are in between these two calls, this is required.

But the really odd part, why is li and dic continually mutated during each function call? Python does not create a copy of each argument for each function call but holds a reference to the original default argument, and if one is not careful they can easily fall into the trap of creating a function that is not pure.

As for this: back_to_earth(*dictionary_and_list()['a_list], : this is how one can unpack a list into a function so each item in the list becomes an individual argument. Values are grabbed with *args. As for **dictionary_and_list()['a_dictionary]) : this unpacks the dictionaries values into the functions arguments. Each keyword in the function that has an associated key in the dictionary will be given that value.

And what the code outputs:

Ok, your there: 100%! Ok, you are done, it is time to leave this Python world. Congratulations on all your accomplishments! Goodbye!Heading3back2to1earth!0

Last but not least, after each function was finished I ran liftoff.__next__() . As you can see in the code below. One thing to know. If it is run one more time an error occurs. Why? Well, generator functions can only be read once and never again. So when the iteration is finished, there is no longer a .__next__() and all previous values have been garbage collected.

All the code in one place:

And the repl:

repl.it - pythonIntroductionArticle by @jerrymuzsikAll the code that was written in the article in one place. repl.it

This is Guido, Python alien

A few other things to be aware of:

  1. Filenames, variable names, and function names are named with underscores.
  2. Indentation: 4 spaces is the convention.
  3. CamelCase is not used much, only in classes.
  4. If the variable is globalthenitisalllowercase (if a variable is global then it is all lowercase).

Thank’s for reading. Any recommendations, ideas, comments, thoughts, claps, or whatever else you feel impelled to do in relation to this article would be greatly appreciated!


Published by HackerNoon on 2017/12/13