<jupyter_start><jupyter_text>Python Crash CourseThis notebook is modded from a base notebook taken from: https://anaconda.org/gwinnen/python-crash-course/notebookThis notebook will just go through the basic topics in the following order:* Data Types    * Numbers    * Strings    * Printing    * Lists* Data Structures    * Dictionaries    * Booleans    * Tuples     * Sets* Comparison Operators* if,elif, else Statements* for Loops* while Loops* range()* list comprehension* functions* lambda expressions* map and filter* methods* importing stufff* Making a simple toy application.If you would like to learn up more on Python, check out FreeCodeCamp's course here.  Data types Numbers<jupyter_code>1 + 1
1 * 3
1 / 2
2 ** 4 #exponent
4 % 2
365 % 13 #modulo
(2 + 3) * (5 + 5)<jupyter_output><empty_output><jupyter_text>Variable Assignment<jupyter_code>x = 2
y = 3
z = x + y
z<jupyter_output><empty_output><jupyter_text>Strings<jupyter_code>'single quotes'
"double quotes"
" wrap lot's of other quotes"<jupyter_output><empty_output><jupyter_text>Printing<jupyter_code>x = 'hello'
x
print(x)
num = 12
name = 'Sam'
#Inplace text replacement/assignment with .format()
print('My number is: {one}, and my name is: {two}'.format(one=num,two=name))<jupyter_output>My number is: 12, and my name is: Sam<jupyter_text>Data Structures Python has 4 types of data structures, each with their own quirks.* Lists    * Denoted by square brackets [].    * e.g. myList = [1,2,3]* Tuples    * Denoted by parentheses ().    * e.g. myTuple = (1,2,3)* Dictionaries (Hash Table)    * Denoted by curly brackets {} with key value pairs.    * e.g. myDict = {key1: value, key2:value}* Sets    * Seldom used. Denoted by curly brackers **without** keys.    * e.g. mySet = {1,2,3,4,5}  Lists<jupyter_code>[1,2,3]
['hi',1,[1,2]] #List of lists
my_list = ['a','b','c']
my_list.append('d') #Useful function
my_list<jupyter_output><empty_output><jupyter_text>Indexing ListsNote that python is 0-indexed; e.g. counting starts from 0.<jupyter_code>my_list = list(range(0,10))
my_list #Print by default in notebooks
my_list[0]
my_list[1]<jupyter_output><empty_output><jupyter_text>Indexing with slicing (more than 1 element). Note the slicing syntax is like myList[start:stop:interval].<jupyter_code>my_list[1:] #Take 1st element till the last
my_list[:2] #Take from start till the stop-1 element
my_list[:-2] #Reverse counting. Take from start and stop before the last 2 elements.
my_list[-3:] #Reverse counting. Take from last 3 elements till the end.
my_list[0:-1:2] #Skip indexing. Start from the first (0th) element and stop before the last element, skipping by 2.
my_list[0] = 'NEW' #Lists are mutable
my_list
nest = [1,2,3,[4,5,['target']]]
nest[3]
nest[3][2]
nest[3][2][0]<jupyter_output><empty_output><jupyter_text>List math operations<jupyter_code>my_list = ['a','b','c']
my_list * 2 #Extend list with itself by N factor
my_list + my_list #Same for addition
my_list * 0 #Funny thing<jupyter_output><empty_output><jupyter_text>Dictionaries<jupyter_code>d = {'key1':'item1','key2':'item2'}
d
d['key1']
d = {'key1':{'kkey1':3,'kkey2':4}, 'key2':[5,3]} #Frakenstein dict 
d['key1']
d['key1']['kkey2']
d['key2']<jupyter_output><empty_output><jupyter_text>Booleans<jupyter_code>True == 1
False == 0<jupyter_output><empty_output><jupyter_text>Quirks of objects<jupyter_code>if [5]:
    print('A non-empty list is True!')
if []:
    print('An emptyp list is False!')
if not []:
    print('An empty list is False!')
if 'hi':
    print('bye') #Same thing for strings
if 5:
    print(6) #Sasme thing for int. Basically objs.
if 0:
    print('Oh no.') #0 == False.
if '':
    print('Hello?') #Same with empty string.<jupyter_output><empty_output><jupyter_text>Tuples Tuples are immutable (can't be changed once instantiated).<jupyter_code>t = (1,2,3)
t[0] #can still be indexed
t[0] = 'NEW' #Can't change element<jupyter_output><empty_output><jupyter_text>SetsReturns an object sequence comprised of unique elements only; in ascending order.<jupyter_code>{1,2,3}
{1,2,3,1,2,1,2,3,3,3,3,2,2,2,1,1,2}<jupyter_output><empty_output><jupyter_text>Comparison Operators<jupyter_code>1 > 2
1 < 2
1 >= 1
1 <= 4
1 == 1
'hi' == 'bye'<jupyter_output><empty_output><jupyter_text>Logic Operators<jupyter_code>(1 > 2) and (2 < 3)
(1 > 2) or (2 < 3)
(1 == 2) or (2 == 3) or (4 == 4)<jupyter_output><empty_output><jupyter_text>if,elif, else Statements<jupyter_code>if 1 < 2:
    print('Yep!')
if 1 < 2:
    print('yep!')
if 1 < 2:
    print('first')
else:
    print('last')
if 1 > 2:
    print('first')
else:
    print('last')
if 1 == 2:
    print('first')
elif 3 == 3:
    print('middle')
else:
    print('Last')<jupyter_output>middle<jupyter_text>for Loops<jupyter_code>#For loops have the following syntax
for element_of_iterable_object in iterable_object:
    
    doSomething(element_of_iterable_object)
seq = [1,2,3,4,5]
for item in seq:
    print(item)
for item in seq:
    print('Yep')
for jelly in seq:
    print(jelly+jelly)
#Useful enumerate() function on iterables. It returns the element and its index together.
for indices,element in enumerate(seq):
    print("Element No. {} | Element value: {}".format(indices,element))<jupyter_output>Element No. 0 | Element value: 1
Element No. 1 | Element value: 2
Element No. 2 | Element value: 3
Element No. 3 | Element value: 4
Element No. 4 | Element value: 5<jupyter_text>while Loops<jupyter_code>i = 1
while i < 5:
    print('i is: {}'.format(i))
    i = i+1<jupyter_output>i is: 1
i is: 2
i is: 3
i is: 4<jupyter_text>range()<jupyter_code>range(5)
for i in range(5):
    print(i)
list(range(5))<jupyter_output><empty_output><jupyter_text>List ComprehensionFunny and ugly yet lovable thing of Python.<jupyter_code>x = [1,2,3,4]
out = []
for item in x:
    out.append(item**2)
print(out)
[item**2 for item in x]<jupyter_output><empty_output><jupyter_text>functions<jupyter_code>def my_func(param1='default'):
    """
    Docstring goes here.
    """
    print(param1)
my_func
my_func()
my_func('new param')
my_func(param1='new param')
def square(x):
    return x**2
out = square(2)
print(out)<jupyter_output>4<jupyter_text>Lambda Expressions<jupyter_code>def times2(var):
    return var*2
times2(2)
lambda var: var*2<jupyter_output><empty_output><jupyter_text>map and filter<jupyter_code>seq = [1,2,3,4,5]
map(times2,seq) #Returns a map object. Need to cast it to a list.
list(map(times2,seq))
list(map(lambda var: var*2,seq)) #Instantiate in place
filter(lambda item: item%2 == 0,seq) #Returns a filter object. Need to cast as list.
list(filter(lambda item: item%2 == 0,seq))<jupyter_output><empty_output><jupyter_text>Functions<jupyter_code>#Define and instantiate function
def mySum(var1,var2,do=True):
    
    if do:
        result = var1 + var2
        print(result)
        
    return result
result = mySum(5,2) #Notice that the default arguments are optional cause they are already defined.
mySum(5,2,False) #Can't return value because it's not instantiated in the function
#Define and instantiate function
def mySum(var1,var2,do=True):
    
    #Instantiate result to return if do == False
    result = None
    
    if do:
        result = var1 + var2
        print(result)
    else:
        print("Done nothing.")
        
    return result
mySum(5,2,False) #Can't return value because it's not instantiated in the function<jupyter_output>Done nothing.<jupyter_text>Classes<jupyter_code>class wrongCalculator():
    """
    Docstring if you like. This is a simple calculator.
    """
    
    def __init__(self,fudge1,fudge2): #Note the self value. Necessary for all function to refer to itself.
        """
        The __init__ is a sfunction that will be called when the class is first initiatied.
        Python will always look for this particular function during instantiation.
        Note that these are also called 'magic functions' or 'dunder functions' as denoted by the
        '__' prefix and suffix.
        """
        
        #Initialize class attributes from instantiation input
        self.f1 = fudge1
        self.f2 = fudge2
        self.check_me_later = "Still here."
        
        print("Initalized")
        
        
    def add(self,x1,x2): #Note the self value again.
        """
        Simple 2 value adder.
        """
        return x1 + x2 + self.f1 #Fudge the value
    
    
    def minus(self,x1,x2): #And again.
        """
        Simple 2 value subtractor.
        """
        return x1 - x2 - self.f2
Kalkulator = wrongCalculator() #Opps, did not supply the required variable in __init__
Kalkulator = wrongCalculator(2,5) #Much better
Kalkulator.add(1,1) #Lols
Kalkulator.minus(2,2)<jupyter_output><empty_output><jupyter_text>Watch out, it's Inheritance! ![image.png](attachment:image.png) In its defense, at small scale and well managed larger scale projects (very well managed), inheritance can actually be very handy and time-space saving.<jupyter_code>class rightCalculator(wrongCalculator):
    
    def __init__(self):
        """
        Init func again.
        """
        #Super() will let this child function (rightCalculator) inherit all functions of its parent (wrongCalculator) 
        #We're initializing with fudges == 0 so the calculator will be correct.
        super().__init__(fudge1=0,fudge2=0)
        
    
    #Let's try to create a new function with rightCalculator.
    def multiply(self,x,y):
        """
        Simple multiplier.
        """
        return x * y
Kalkulator = rightCalculator()
Kalkulator.add(1,1)
Kalkulator.minus(2,1)
Kalkulator.multiply(2,3) #New function added on top of wrongCalculator
Kalkulator.check_me_later #Attributes are inherited too.<jupyter_output><empty_output><jupyter_text>MethodsA method in python is somewhat similar to a function, except it is associated with object/classes. Methods in python are very similar to functions except for two major differences.* The method is implicitly used for an object for which it is called.* The method is accessible to data that is contained within the class.Basically methods are functions defined in a Class.<jupyter_code>st = 'hello my name is Sam'
st.lower()
st.upper()
st.split()
tweet = 'Go Sports! #Sports'
tweet.split('#')
tweet.split('#')[1]
d
d.keys()
d.items()
lst = [1,2,3]
lst.pop()
lst
'x' in [1,2,3]
'x' in ['x','y','z']<jupyter_output><empty_output><jupyter_text>Importing Stuffs<jupyter_code>import os #Import 1 at a time
import csv,time #Import multiple at a time
import math as mt #Import with proxy name

from pathlib import Path #Import specific function from lib
from matplotlib.pyplot import plot as plt #Combine them all!<jupyter_output><empty_output><jupyter_text>--------------------------------  Creating a Simple Toy ApplicationThis is a simple application that basically opens an image from a given image path, and return as resized version of it for plotting. However, before being resized, it'll check for whether we should resize it and if we do, what size we should resize it to. *For this, we'll be using 5 main packages / libs.** os - to navigate the OS* pathlib - to deal with paths * PIL - to read an image given an image path and to resize* matplotlib - to plot and visualize image* numpy - to change image from a PIL object to an array/tensor of floats/ints. To be used for more manipulation.<jupyter_code>import os,PIL
import numpy as np
import matplotlib.pyplot as plt

from pathlib import Path
#See the files in our current work directory
os.listdir()
#Define image folder pth
img_folder = Path('images') #No need for absolute path, you can also use relative path like this

#See what's inside
imgs = os.listdir(img_folder)
imgs
#Can also use abs path
abs_path = os.path.abspath(img_folder)
img_folder_abs = Path(abs_path)

#See what's inside
os.listdir(img_folder_abs)
#Note that Path() objects can be concatenated further with '/' with a string
img_folder
img_folder/'my_next_dir'
#Open pic to see
img = PIL.Image.open(img_folder/imgs[1]) #Let's take a look at open_me.jpg
img
class imgViewer():
    """
    Simple image viewer.
    """
    
    def __init__(self,path,resize=True,rsz_dim=(512,512)):
        """
        Initialization.
        """
        self.path = path
        self.image = PIL.Image.open(path)
        
        #Check for rsz
        if resize:
            self.ori = self.image.copy()
            self.image = self.image.resize(rsz_dim)
        
        
    def size(self):
        """
        Simple function that returns dim of image
        """
        return self.image.size
    
    
    def to_np(self):
        """
        Convert image to np.array for manipulation.
        """
        return np.array(self.image)
            
    
    def display(self,ori=False):
        """
        Simple func to plot.
        """
        
        #Plot original or resized
        if ori:
            plt.imshow(self.ori)
            
        else:
            plt.imshow(self.image)
#Try
viewer = imgViewer(img_folder/imgs[1])
viewer.size()
#Check how it looks
viewer.display()
#Look at original non-resized
viewer.display(ori=True)
#Conv to numpy array
np_img = viewer.to_np()
np_img.shape
#Take a look
plt.imshow(np_img)
#Crop directly by indexing/slicing array without needing to use .resize()
plt.imshow(np_img[122:,122:,:])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11800587393629039, 'fraction_numerical': 0.021914300775660818, 'mean_word_length': 3.89134438305709, 'pattern_counts': {'":': 0, '<': 119, '<?xml version=': 0, '>': 116, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '29', 'dir': 'jupyter-structured-clean-dedup', 'id': '92208', 'license': 'MIT', 'n_tokens_mistral': 4694, 'n_tokens_neox': 4479, 'n_words': 1388, 'path': 'part1_python/part1_python.ipynb', 'repo_name': 'huaiyukhaw/lessons', 'size': '13279'}