Reviewing Python - Fundamentals
Tags: python, tutorial, beginners
Published: 2019-12-18 07:18:48


##Hi Guys Good Day!

It's been almost two months that I haven't posted anything because 
I was busy studying and playing games haha. Ok, mostly my articles are about __JavaScript__ or __TypeScript__ so I'm gonna go to a different area specifically __Python__. __Python__ is my favorite programming language not because it's the most popular programming language right now or it has a lot of uses in different areas in Software Development like in __Web Development__,  __Machine Learning__ or __Data Science__ but because it's the language that I used that help
me graduate college. I loved it since the first time using it. I love the syntax, I love how easily you can learn it's basics and fundamentals and how you can easily write lesser code with it compared to other languages. Honestly, It gives me hope when there were hard times in my last semester in college that I thought that I was not able to graduate because it was hard to manage the time between my thesis and other subjects but Thanks to __Python__ I graduated. It's been almost two years that I haven't programmed in __Python__ so I started learning it again so here's what I learned again.
So Let's get started.

###Variables
__Variables__ are _placeholders_ for values. Variables are use to keep track or remember data in our program.
Instead of remembering a specific value we remember the _variable_ that __holds__ that value. Making in variables in __Python__ is straightforward.

```py
my_secret_num = 1
myName = "Mark"
isDone = False
_ = None

# A variable name must not start with a number or a special character except underscore "_"

0_1 = 1 # raises a Syntax Error
dollar$= "$ss" # raises a Syntax Error
@_2 = 1 # raises a Syntax Error

```

###Indentation 
What makes __Python__ unique from other languages is that it has strict indentation rules, sometimes it's a pain in the ass doing it manually but if you're using an __IDE__ then I think that IDE maybe has some option to configure the number of spaces to use. The standard number of spaces to use in Python is __4__. Indentation makes your code cleaner and more readable. An indentation starts after a colon <code>:</code>.

```py
if True:
    print("Indentation Rocks") # This statement has 4 spaces before it.

i = 0
while True:
    if i == 2: # This statement has 4 spaces before it.
        break # This statement has 8 spaces before it.
    else:       # This statement has 4 spaces before it.
        i += 1 # This statement has 8 spaces before it.

def func():
    pass  # This statement has 4 spaces before it.

for num in [1,2,3]:
print(num) # This raises an Error SyntaxError: expected an indented block

```

###Data Types
Every value that we assign to a variable holds a value and that value has a specific _data type_. Every _data type_ can be used in any situation but in a certain situation or problem, there is always a more suited _data type_ to use. Python has a number of data types like __Boolean__, __Strings__, __Numbers__, , __Lists__, __Dictionaries__, __Tuples__, __Sets__, __Files__ and __None__.

```py
my_first_name = "Mark" # String
my_fav_num = 21 # Number - Integer
my_fav_float_num = 21.0 # Number - Float

is_current_year_odd = False # Boolean - True | False
wish_list = ["new video card","new monitor","new keyboard"]  # List
language_profiencies = { # Dictionary
     "Python" : 6.5,
     "JavaScript" : 8.5
     "TypeScript" : 7
}
cant_change_this = ("Global Warming","is","Real") # Tuple
nums = {1,2,3,4,5,1} # Set
sample_file_data = open("path/to/file.txt","a") # File
```

###Operations and Flow Control

There's a lot of operations that we can use in Python.


Most of this is used for updating a certain value by using current value and updating it. I mostly used the first half in this table because I haven't faced a problem that required using the second half.

__Augmented Assignment Operators__

| Shorthand                  |   Equivalent        | 
| --------------------- | :-------------: | 
| **a += b**        | __a = a + b__          |
| **a -= b**       | __a = a - b__           | 
| **a \*= b**  | __a = a * b__          | 
| **a /= b**        | __a = a / b__           |
| **a //= b**  | __a = a // b__           |
| **a %= b**     | __a = a % b__           | 
| **a \**= b**     | __a = a ** b__           | 
| **a &= b**     | __a = a & b__           | 
| **a \^= b**     | __a = a ^ b__           | 
| **a \>>= b**     | __a = a >> b__           | 
| **a <<= b**     | __a = a << b__           | 

__Math Operators__

Math Operators are mostly used in __Numbers__ in Python.

| Operation                  | Operator           | Example            | Result |
| --------------------- | :-------------: | :-----------------: | :-----------------: | :-----------------: | 
| **Exponent**        | __**__          | 2 ** 3             |            8      |
| **Remainder**       | __%__           | 5 % 3              |               2   |
| **Floor Division**  | __//__          | 4 // 3             |             1     |
| **Division**        | __/__           | 2 / 2              |                1  |
| **Multiplication**  | __*__           | 2 * 2              |             4     |
| **Subtraction**     | __-__           | 1 - 2              |           -1      |
| **Addition**        | __+__           | 1 + 2              |              3    |

All of these operations can only be used on a value with a type of __Number__. But some of these operations can be used in other types other than __Numbers__.

```py
print("Hello " + "World") # This prints Hello World
# string concatenation

print("Hello" * 3) # This prints HelloHelloHello
# string repetition

print([1,2,3] + [4,5]) # This prints [1,2,3,4,5] 
# + combines both list

print([1] * 3) # This prints [1,1,1]
# * list repetition.

```
__Boolean Operators__ 

Boolean Operators are mostly used in <code>if</code> conditions or Flow Control which we will talk about later.


| and operator                  | Result           | 
| --------------------- | :-------------: | 
| **False and False**        | __False__          |
| **False and True**       | __False__           | 
| **True and False**  | __False__          | 
| **True and True**        | __True__           | 


| or operator | Result           | 
| --------------------- | :-------------: | 
| **False or False**        | __False__          |
| **False or True**       | __True__           | 
| **True or False**  | __True__          | 
| **True or True**        | __True__           | 


| not operator | Result           | 
| --------------------- | :-------------: | 
| **not True**        | __False__          |
| **not False**       | __True__           | 

__Comparison Operators__

Comparison Operators are used to comparing two values which will evaluate into a single Boolean value.

| Operation                  | Operator           | Example            | Result |
| --------------------- | :-------------: | :-----------------: | :-----------------: | :-----------------: | 
| **Greater than**        | __>__          | 5 > 2            |            True      |
| **Less than**       | __<__           | 5 < 2            |               False   |
| **Greater than or equal to**  | __>=__          | 4 >= 3            |       True     |
| **Less than or equal to**        | __<=__           | 4 <= 3            |   False|
| **Not Equal**  | __!=__           | 2 != 2           |            False     |
| **Equal**     | __==__           | 2 == 2             |           True      |

__if, elif, else statements__.
Sometimes we want our code to run based on a certain condition or conditions. That's where __if, elif, else statements__ comes to play. The syntax for these three is shown below.

```py
is_done = True

if is_done:
   print("The job is done") # This will be printed because the variable "is_done" is True
else:
   print("The job is not done") # This will not print 

```

The __else statement__ will run will only run if the __if statement's__ condition is not <code>True</code>. If we want multiple conditions we can use the __elif statement__. The __elif statement__ will be checked if the previous __if statement__ or previous __elif statements__ evaluated to <code>False</code>.

```py
my_grade = "B"

if my_grade == "A":
    print("Amazing")
elif my_grade == "B":
    print("Very Good")  # This will be printed
elif my_grade == "C":
    print("Good")
elif my_grade == "D":
    print("Bad")
elif my_grade == "E":
    print("Very Bad")
else:
    print("Failed")
	
```

In the above example, it prints <>Very Good</code> because the variable <code>my_grade</code> has a value of <code>"B"</code> and it ignores the rest of the __elif statements__ and __else statement__.

__Loops__
Python has two main mechanisms for repeating a block of code over and over again or in a specific number of times.
__while__ and __for__ loops. 

__while__ loops are used for repeating a block of code _while_ a certain condition is __True__. The syntax for a 
__while loop__ is shown below.

```py

count = 0
while count < 5:
    print(count)
    count += 1

```
In this example, the loop will continue until the _count_ variable will have a value of __5__. Because our condition says __repeat the block of code while the count is less than 5 so in the block of code we add a value of 1 to count__.


__for__ loops, on the other hand, execute a block of code in a specific number of times. The syntax for a  __for loop__ is shown below.
```py

avengers = ["Iron Man", "Captain America","Thor", "Hulk", "Black Widow", "Hawkeye"]

for avenger in avengers:
    print(avenger) 

```
In this example, we assign the variable name _avenger_ for each of the six values on the _avengers_ List. Mostly it starts from the _0_ index up until to the last. In every iteration, the _avenger_ variable will hold a different item in the List.


If you want to get the _index_ rather than a value in the List. You can use the <code>range</code> and <code>len</code> functions. 

The <code>range</code> function has three parameters __range(start,stop,step)__. The __start__ has a default value of __0__ and step has a default value of __1__.  If the __start__ and __step__ are not provided then whatever value provided in __range__ function will be the __stop__ and should be an integer. 

```py
print(range(5)) # This prints range(0,5)
print(range(0,5,1)) # This is equivalent in the print statement above
print(range(0,5)) # This is equivalent in the print statement above

# We can convert the return value of the range function into a List. Using the list function

print(list(range(5))) # This prints [0,1,2,3,4] does not include the stop

```

The <code>len</code> function returns the number of items in the container. We can use it in __Lists__, __Strings__, __Tuples__, __Sets__, and __Dictionaries__.

```py
print(len("abc")) # This prints 3. The number of characters in the String
print(len([1,2,3,4,5])) # This prints 5. The number of values in the List
print(len((1,2,3,4))) # This prints 4. The number of values in the Tuple
print(len({1,2,3,3})) # This prints 3. The number of values in the Set
print(len({"a":"eyy","b":"bee","c":"si"})) # This prints 3. The number of keys in the Dictionary
```

So the <code>range</code> and <code>len</code> functions are a good combination for getting __indexes__ in __Lists__.
```py
for index in range(len(avengers)):
    print(f"{index} - {avengers[index]}") # This prints the index and the corresponding value at that index in each iteration
```

There are statements that are only valid inside loops. Those are __break__ and __continue__ statements.

The __break__ statement is used to stop or exit the Loop right away.

```py
i = 0
while True:
    count += 1
    if count == 3:  # When the value of count is 3. This loop will exit
        break
    else:
        print(count)

for avenger in avengers:
    if avenger == "Thor":  # When the value of avenger is Thor. This loop will exit
        break
    else:
        print(avenger)
```

The __continue__ statement will skip the current iteration and _continue_ to the next one. It will go back to the loop declaration before continuing to the block of code.

```py
i = 0
while True:
    count += 1
    if count == 3:  # Will not print 3
        continue
    else:
        print(count)

for avenger in avengers:
    if avenger == "Thor":  # Will not print Thor
        continue
    else:
        print(avenger)
```

###Boolean
__Boolean__ type has only two values <code>True</code> and <code>False</code>. When we use __comparison operators__ and __boolean operators__ between values they evaluate to a single boolean value. Boolean values are used for <code>if</code> conditions , <code>while</code> loops and 
etc.

```py
is_absent = False
my_fav_num = 5

print(is_absent) # This prints False
print(my_fav_num > 1) # This True because it compares if 5 is greater than 1 which evaluates to True
print(True and False) # This prints False
print(True or False) # This prints True
print(not False) # This prints True

if is_absent:
    print("Mark is present")  # This will not be printed because is_absent is False
else:
    print("Mark is absent")  # This will be printed

while True:  # This is an example of an infinite loop
    print("This will not stop")  # Press Ctrl+C to Stop

```

<code>True</code> and <code>False</code> are just <code>1</code> and <code>0</code> respectively. They don't have the same type but they have the same value but have different printing logic.

```py
print(True == 1) # This prints True
print(False == 0) # This prints True

```

### Strings
__Strings__ are obviously values for texts. Working with Strings in Python is relatively easy and fun. We can make a string with either double-quote <code>"</code> or a single-quote <code>'</code>. If a string starts with a single-quote
it also must end with a single-quote. If it starts with a double-quote and must end with a double-quote.

```py
my_name = "Mark"

my_favorite_language = 'Python'

my_favorite_book = 'The Sum of all Fears"
# This will raise a SyntaxError because it starts with a single-quote but ends with a double-quote
```

We can access a specific value in the __String__ using an _index_. The _index_  is the position of that character (a character is a single string like "a") in the String. An _index_ is an integer that starts with 0,1,2,3 until we end up to the last character in the String which has an index of the length of the String minus 1. We can also use negative numbers when indexing -1 is the last index in the String, -2 refers to the second to the last index and so on.

| String - 'Mark'       |      M      | a            | r  | k |
| --------------------- | :-------------: | :-----------------: | :-----------------: | :-----------------: | :-----------------: | 
| **Positive Indexing**        | 0   |     1       |     2 | 3
| **Negative Indexing**       | -4    |  -3        |  -2   |  -1      

```py

first_letter = my_name[0] # returns the first character in my_name variable which is "M"

print(first_letter) # This will print "M"

first_letter = my_name[-4] # returns the first character in my_name variable which is also "M" using negative-indexing

print(first_letter) # This will print "M"
```

We can use a great functionality called _slicing_ in Strings in Python. _slicing_ is extracting a specific portion of data in a given data type. We can specify where we start extracting, where we want to end the extract or the number of steps we want to. It looks like this  <code>my_name[0:4:1]</code> in this example  my_name is a string variable that holds a value of "Mark", slicing starts by using square brackets, the __0__ is our starting point, the __4__ is the endpoint but not included, and the last one __1__ is our step, the __step__ we make from the __start__ to __end__.

```py
guess_my_value = my_name[0:4:1]
# It will extract the portion of the characters from index 0 to index 4 but not including, with a step of 1
# so it extract 'M', 'a', 'r', 'k' with an index of 0,1,2,3 respectively. The step has a default value of 1.

print(guess_my_value) # This will print "Mark"

print(my_name[0:4]) # This will still print "Mark"

print(my_name[:]) # This will still print "Mark"

print(my_name[0:]) # This will still print "Mark"

```

__Note:__ Strings are _immutable_ which means that we cannot or delete change a specific character in that String unless 
you assign that variable that holds the string with a new value.


```py

my_name[0] = "A" # This will raise a TypeError 'str' object does not support item assignment
my_name = "Polo" # This assigns a new value for my_name
print(my_name) # This will print "Polo"


```
__Strings__ has many, many methods that can help with our basic problems. Most of these methods returns a new string and does not mutate the original String.

```py
my_name = "Mark"

print(my_name.upper()) # This prints "MARK"
# The "upper" method returns a new string version as all in uppercase

print(my_name.lower()) # This prints "mark"
# The "lower" method returns a new string version as all in lowercase

print(my_name.islower()) # This prints False
# The "islower" method returns a Boolean indicating if the String contains all lowercase letters
# it returns True if all letters are lowercase else return False

print(my_name.isupper()) # This prints False
# The "isupper" method returns a Boolean indicating if the String contains all uppercase letters
# it returns True if all letters are lowercase else return False

print(my_name.isalpha()) # This prints True
# The "isalpha" method returns a Boolean indicating if all characters in the String 
#  are alphabetic  it returns True else it returns False

print(my_name.isdigit()) # This prints False
# The "isdigit" method returns a Boolean indicating if all characters in the String
# are numbers it returns True else it returns False

print(my_name.index("ark")) # This prints 1
# The "index" method searches a substring in String and returns the starting Index
# of the substring else this method raises a ValueError.

print("a,b,c,d".split(",")) # This prints ["a","b","c","d"]
# The "split" method obviously splits the String with parameter separator and returns a
# list of all strings. It also has a second parameter which is a number that indicates 
# the number of splits we want.

print(my_name.startswith("Ma")) # This prints True
# The "startswith" method returns a Boolean indicating if the String
# starts with a certain substring else it returns False 	

print(my_name.startswith("rc")) # This prints False
# The "endswith" method returns a Boolean indicating if the String
# ends with a ceratin substring else it returns False 	
```

There are still many methods that I haven't discuss in this example, you all can try it yourself.

###Numbers
The __Numbers__ data type has lots of types in Python. __Integers__, __Floating-point numbers__, __Octal__, __Hex__, __Binary__, __Complex Numbers__, __Sets (which will talk about later)__, __Boolean__, __Decimal__ and __Fraction__. __Numbers__ is not a single type like __Strings__ or __Lists__ but they are several types that are related to each other.

```py
import fractions
import decimal

my_num = 5 # Integer
pi = 3.14 # Floating-point number
ten = 0o012 # Octal
x = 0x123 # Hex
two = 0b010 # Binary
unique_nums = {1,2,3,2,3} # Sets
is_done = True # Boolean
one_and_a_half = fractions.Fraction(1/2)  # Fraction
one_over_three = decimal.Decimal(1/3) # Decimal
```
Ok, I'm not gonna talk a lot of all these types because we mostly use __Integers__ and __Floats__.

If you want to use __Strings__ with __Integers__ or __Floats__. You have to convert first that number using the <code>str</code> function before using it.

```py
print(str(2) + "is two")
print(str(3.0) + "is three")
print(4.1+ "is four point one") This raises a TypeError: unsupported operand type(s) for +: 'float' and 'str'
```

You're wondering why the __Boolean__ type is related in the __Numbers__ Type. Just as I said earlier 
the Boolean values <code>True</code> and <code>False</code> are just __1__ and __0__ respectively they just different printing logic.

```py
print(True == 1) # This prints True
print(False == 0) # This prints True
print(isinstance(True,int)) # This prints True
```

While __Sets__ has some mathematical operations using certain operators and methods. We will talk about __Sets__ later.

###Lists
__Lists__ are values that acts as a _container_ for other values. It can hold multiple values of the same type or multiple values of any type. It's called __Arrays__ in other programming languages. We can declare a __List__ using the square brackets <code>[]</code>. We can put values in the __List__ and separate them using comma __,__ <code>[1,2,3,4,5]</code>.

```py
numbers = [1,2,3,4,5]
```
We can access a specific value in the list using an _index_. The _index_  is the position of that value in the List. An _index_ is an integer that starts with 0,1,2,3 until we end up to the last item of the List which has an index of the length of the List minus 1. We can also use negative numbers when indexing -1 is the last index in the list, -2 refers to the second to the last index and so on. Same as __Strings__, __Lists__ has _positive_ and _negative_ indexing.

```py
one = numbers[0] # numbers[0] holds the value of 1in the List
two = numbers[1] # numbers[1] holds the value of 2 in the List
five = numbers[-1] # numbers[-1] holds the value of 5 in the List

# We can get the length of the array using the "len" method. We only pass the name of the variable that holds the List.

len_of_numbers = len(numbers) # len(numbers) returns 5

# If we access an index that is more than the number of values in the List
# you will get an IndexError

guess_this_number = numbers[1000] # This will raise the IndexError

```
If you want to check every item in a __List__. You can use __for Loops__.

```py
avengers = ["Iron Man","Captain America","Thor","Hulk","Black Widow","Hawkeye"]

for avenger in avengers: 
      print(avenger) # prints every avenger each in a new line

# If you want to use the index instead of the value
# we can also use that using for loops with the help of "range" and "len" functions

for index in range(len(avengers)):
      print(index) # prints the index each in a new line

# Tip: Instead of checking the length of a List in a condition
# just use the list instead

data = ["one"] 

if len(data):
    print("This will be printed.")

# Use this instead

if data:
    print("This will be printed.")

```
Just like __Strings__ we can use _slicing_ in __Lists__.
```py
l = [1,2,3,4,5]

# list[start:stop:step] 

len_l = len(l) # has a value of 5
half = len_l//2

first_half = l[0:half] # l[0:half] will evaluate to l[0:2] is also equivalent to l[:2]

print(first_half) # This will print [1,2]

second_half = l[half:len_l] # l[half:l] will evaluate to l[2:5] is also equivalent l[2:] 

print(second_half) # This will print [3,4,5]

odd_nums = l[0:len_l:2] 
#  l[0:len_l:2] will evalute to l[0:5:2] the 0 our start, the 5 is our stop and the step is 2 
#  l[0:5:2] is also equivalent to l[0::2] or l[::2]

print(odd_nums) # This will print [1,3,5]

reverse_l = l[::-1] 
#  l[::-1] This reverse the start and stop meaning we will start at the last item and stop 
# with the first item. [::-1] is also equivalent to l[len(l)::-1]

print(reverse_l) # This will print [5,4,3,2,1]

```
__Lists__ has many methods that make our life easier.
```py

avengers.append("Ant-Man") 
# The "append" method add a new item at
#  the end of List

avengers.count("Iron Man") # returns 1
# The "count" method counts how many times
#  the value passed appears

avengers.extend(["Doctor Strange","Spiderman","Black Panther"])
# The "extend" method adds all items 
# passed at the end of the List. The parameter passed must be an Iterable object.

avengers.index("Captain America") # returns 1
# The "index" method returns the first index of the value passed.
# If the value does not exist this method raises a ValueError.

avengers.insert(0,"Captain Marvel")
# The "insert" method obviously inserts a value 
# at a specified index but does not replace the
#  current value at that position.

avengers.append("Thanos")
avengers.remove("Thanos") # returns 1
# The "remove" method obviously removes the first occurence of the value passed.
# If the value does not exist this method raises a ValueError.

avengers.reverse()
# The "reverse" method reverses the position of each item in the List.
# If the List looks like this my_list = [1,2,3]
# then we use l.reverse()
# then my_list would look like this [3,2,1]

avengers.pop()
# The "pop" method removes the last item in the List and returns it.
# We can specify the index to remove in pop method as a parameter
# If the index is not valid or if the List is empty this method raises an 
# IndexError.

```
###Tuples
__Tuples__ is like a __List__ but the difference between them is that __Lists__ are _mutable_ which means
we can change or delete the values inside a __List__ but with __Tuples__ are _immutable_ which means
we __cannot__ change or delete any values on a __Tuple__. We can make a __Tuple__ using parentheses.

```py
my_favorite_numbers = (1,21,19)

# If you only assign one value in the Tuple without using a comma 
# Python will treat that as an expression and in turn that variable
# will not be a tuple instead will the first item in the parentheses

my_secret_nums = (666) 

print(my_secret_nums) # will print 666 instead of (666)

my_new_secret_nums = (666,)

print(my_new_secret_nums) # prints (666,)
```

__Tuples__ is almost like a __List__ we can use _slicing_, _concatenation_, we can access a value using an _index_, we can tuple repetition using the _*_ operator. But we __cannot__ add or change any value in a Tuple. A Tuple has only two methods __index__, and __count__.

```py
secret_hero_real_names = ("Bruce Wayne","Steve Rogers","Clark Kent","Peter Parker")

secret_hero_real_names.count("Bruuce Wayne") #returns 0
secret_hero_real_names.count("Peter Parker") #returns 1 
secret_hero_real_names.index("Steve Rogers") # returns 1
```

###Dictionaries
__Dictionaries__ are also a _container_ for data like __Lists__ but unlike __Lists__ which we access a specific value in __List__ with an _index_ in __Dictionaries__ we access a value using a _key_ or _property_ which in turn holds a value and items in __Dictionaries__ are _unordered_ while in __Lists__ are __ordered_. We can make a __Dictionary__ using curly braces <code>{}</code>.

```py

books_currently_reading_bookmark = {
   "Angel & Demons" :  484,
   "Da Vinci Code": 100
}

# We can use a key with a type of "string","integer" or "tuples" in Dictionaries

d = { 1 : "one" }
print(d[1]) # prints "one"

d1 = { (1,2,3,4,5,6,7,8,9,10): "one-ten"}

print(d[(1,2,3,4,5,6,7,8,9,10)]) # prints "one-ten"

#We cannot use a type of "dictionary" or "list" as a key in Dictionaries

d2 = { [1,2,3] : "one-three" } # This will raise a TypeError : unhashable type: 'list'
```

We can add a new key-value pair anytime in a dictionary.

```py
letters_sounds = {}

letters_sounds["a"] = "eyyy"
letters_sounds["b"] = "bee"

print(letters_sound) # prints {'a': 'eyyy', 'b': 'bee'}
```

If we check a key that does not exist in a dictionary. It will an error.
```py
print(letters_sounds["c"]) # This will raise a KeyError which means that key "c" does not exist in the letters_sounds Dictionary.
```

We can use the <code>in</code> operator or <code>get</code> method in __Dictionaries__
to check if a key exists in a Dictionary.

```py
if "c" in letters_sounds: # Check if "c" exists
    print(letters_sounds["c"])
else:
   print("letters_sounds does not have a 'c' key")


print(letters_sounds.get("c") == None) # prints True
# The get method has a second parameter which is the default value 
# that will be used if the key does not exist in the Dictionary

print(letters_sounds.get("c",0)) # prints 0
```
__Dictionaries__ has many useful methods that can help some problems when dealing with __Dictionaries__.

```py
new_letters_sounds = {
      "a": "ayy",
      "c" : "si"
}

letters_sounds.update(new_letters_sounds) 
# The "update" method adds or updates new keys in the dictionary it accepts 
# an Iterable type as a parameter or the source

letters_sounds.setdefault("c","sii") 
# The "setdefault" method add a new key and a default value in the dictionary
# if the key doesn't exist in the dictionary, if it exists it returns the value of that key

for key in letters_sounds.keys():
    print(key) # This prints "a", "b" and "c" respectively each in a new line
# The "keys" method returns all keys in the dictionary. This does not return a list.
# Instead it returns a set-like object.

for val in letters_sounds.values():	
    print(val) # This prints "ayy", "bee" and "si" each in a newline
# The "keys" method returns all keys in the dictionary. This does not return a list.
# Instead it returns a set like object.

# If you want to access the key and the value in a loop.
# You can use the "items" method.
for key, value in tuple(letters_sounds.items()):
    print(f"{key}:{value}") # This prints "a:ayy", "b:bee" and "c:si" respectively each in a newline
# The "items" method returns the key and value in a set like object.

letters_sounds.pop("c")
# The "pop" method removes a key and its value in the Dictionary.
# If the key does not exist this raises a KeyError.

letters_sounds.clear()
# The "clear" method removes all the keys and values in the Dictionary.
```

###Sets
__Sets__ are also containers just like __Lists__, __Dictionaries__ and __Tuples__. The difference is that __Sets__ are onordered collection, are __immutable__ (the __Set__ itself is mutable but the values must be immutable) like __Tuples__ and holds only unique values. Sets are declared using __curly brackets__ <code>{}</code>  or the <code>set</code> function and also __Sets__ look like __Dictionaries__ the difference again is that __Sets__ don't have __keys__.

```py
nums = {1,2,3,4,3,4}
nums_2 = set([1,2,3,4,4,5,5]) 
print(nums)  # This prints {1,2,3,4} because Sets only holds unique values.

print(nums_2) # This prints {1,2,3,4,5}
```
Because __Sets__ are onordered we can't access a specific value. It has no indexing functionality like __Lists__ and __Tuples__.

```py

print(nums[0])
# This raises a TypeError: 'set' object does not support indexing.
```

Even though __Sets__ are called _immutable_ it has methods that can change 
or remove a specific value from the Set.

```py
nums.add(4) 
# The "add" method adds a new value in Set if it does not 
# already exist. If already exists this method does nothing.

nums.remove(1)
# The "remove" method removes a specific value from the Set.
# If the value does not exist it raises a KeyError.

nums.clear()
# The "clear" method obviously removes all the values present
# in the Set.
```
We can use certain operations in __Sets__ that are not available in other types.
```py
s1 = {1,2,3}
s2 = {3,4,5,6}

print(s1 | s2)  # This prints {1,2,3,4,5,6} 
# This operation is called "union" it combines both sets and returns a new set.
# It also have a equivalent method on sets called "union" suprise, suprise.
print(s1.union(s2))  # Also prints {1,2,3,4,5,6} 

print(s1 & s2) # This prints {3}
# This operation is called "intersection" it finds the common elements in both sets and returns it in a set.
# It also have a equivalent method on sets called "intersection" suprise, suprise.
print(s1.intersection(s2))  # Also prints {3}

print(s1 - s2)  # This prints {1, 2}
# This operation is called "difference" it finds the difference between the first Set and the second Set and returns a new Set.
# So in this example it calculates what elements "s1" has that "s2" does not have.
# It also have a equivalent method on sets called "difference" suprise, suprise.
print(s1.difference(s2)) # Also prints {1,2}

print(s1 ^ s2) # This prints {1,2,4,5,6}
# This operation is called "symmetric_difference" it finds the elements that are not common between the two sets and returns it as a Set.
# So in this example it calculates what unique elements between "s1" and "s2" has and returns it.
# It also have a equivalent method on sets called "symmetric_difference" suprise, suprise.
print(s1.symmetric_difference(s2)) # Also prints {1,2,4,5,6}
```

Watch out for my next part of this post about __Reviewing Python__ it will involve __Handling Exceptions__, __Functions__, __List__, __Tuples__,__Dictionary__ and __Set Comprehensions__ , __Classes__ and __Modules__.


##Thanks guys for reading this post.
# Have a Nice Day :smiley:!.