Hey guys, welcome to this advanced Python
course. My name is Patrick and I create free tutorials about Python and machine learning.
In this course, I teach you all the advanced topics that bring your Python skills to the
next level. So who is this course for this course is aimed at an intermediate skill level,
you should already have some basic Python knowledge. For example, if you just completed
a beginner course and are looking for the next step, then this tutorial is perfect for
you. And even if you are already in the intermediate level, you can benefit from this because I
share some helpful tips along the way. And we really go into detail of all the different
topics. So here's an overview of what we will cover today, the course is splitted into 21
sections. And in my opinion, every experienced Python programmer should know about these
topics. Alright, let's start. list is a collection data type that is ordered mutable, and that
allows duplicate elements. So let's have a closer look at lists and what you can do with
them. First of all, a list is created with square brackets. And within these brackets,
you put each element that you want separated by a comma. For example, let's put some strings
in here, banana, cherry, and an apple. And if we print this, then we see that all elements
are printed, we can also create a new empty list with the list function. So my list two
equals list. And if we print this, then we see that this creates an empty list. And later
on, you can append items. List allows for different data types. So for example, we can
say that our list can contain an integer, a Boolean, and a string. That's all possible.
And the list allows duplicate elements. So if we put in another apple here, that we then
we see that we have two apples now inside our list. Now if you want to access an element,
you do that by referring to the index. So let's say item equals my lists, and then inside
brackets, you specify the index, and note that the indices start at zero. So index zero
is the first the very first item in this case the banana. And if we print the item, then
we see this is the banana. And index number one is the cherry. index number two is the
apple. And now if we put in an index that is too large, what will happen, then we get
an index error list index out of range. So be very careful with that. Now you can also
specify a negative index, so minus one, this refers to the last item, in this case, the
apple minus two is the second last item, and so on. Now if you want to iterate over your
list, you can do that simply with a for loop for i in my list colon and then do something
this crazy, just want to print it. So then we see that for each element inside our list,
we print it and note that you don't have to call this I you can call this also x or whatever
you want. Now if you want to check if an item is inside our your list, you can do it with
an IF and then your item that you want to check, say banana in my lists, colon and say
let's print. Yes. Else. Print. No. Now if you run this then we see that the banana is
inside. All this let's check if the lemon is no the
apple yes So that's a very simple syntax to check if your index is inside your list. Now
let's talk about some other useful methods that you can do with the list. First of all,
if you want to check how many elements Do you have inside your list, you can do that
with the Lang method. And now if we print this, and we see that we have three elements
inside our list. Now if we want to append items, we can do that by my list dot append.
And now let's append a lemon. Now, print that, we see that a new item the lemon, got inserted
at the very last at the end of the list. Now if we want to insert an item at a specific
position, we can do that with that insert methods. And now First, we have to specify
the index let's say at index number one, and then the item, say a blueberry, and then print
it. And we see that at index number one, now we have the blueberry. If you want to remove
items, we can do that with the pop method. And this returns the last item and also removes
it. So if we assign this to a variable, and print it. And we see that now we got our apple
back. And if we print our list, then we see that the app is no longer in our list. Now
we can also remove a specific element with the dot remove method. For example, let's
remove that. Sherry then we see that the cherry got removed. Now what happens if we specify
an item that is not inside our lists, for example, if we have a typo here, then we get
a value error. It's not in list. So be careful here. We can also remove all elements with
the clear method. So now we have an empty list. Some more useful things that you can
do is for example, you can reverse the list with the reverse method. So now the list is
in reversed order. And you can also sort your list with the sort method. Maybe this, for
example gets clearer for us numbers here also, let's say 4231 minus one, minus 510. And if
we start this, then we see that it's now in ascending order. So note that this sort methods,
sorts your list in place. So this changes your original list. And if you don't want
to have this change, but rather create a new list, then you can do this with the built
in sorted method, new list equals sartet, and then your original list and note that
if you print your original list again, then you see this is still the same. And if you
print a new list, then you see that this is now the new sorted list. Now, some useful
trick if you want to create a new list with the same elements multiple times. So for example,
I want a new list with five zeros in it, then you can do it like this. So let's say it's
put in a zero, and then times five. So if we print this, then we see that we have a new list with five
zeros. And we can easily conquer two lists with the plus operator. So let's say I have
Another list equals to 12345. And then we want a new list. So a new list equals my list
plus my list too. And if we print this, then you your fun new lifts with both elements
with both lists inside it. So, yeah, let's talk about slicing, slicing is a very nice
way to access sub parts of your list with the colon. So for example, let's create a
new list with some numbers 12345689. And let's create a new list and simply call it a and
then inside brackets, the A equals and then my list. And inside the brackets, you specify
this start index and the stop index. So let's say for example, start index one and stop
index five. And if we print this, and we see we have a new list that goes from index one,
to index five, and the last item, the last index is excluded. So it said index 123, and
index four. So our list has all the numbers from two to five. Now, if we don't specify
a start index, then it starts all the way from the beginning. And if we don't specify
a stop index, then it goes all the way to the end. And you also have an optional step
index. So and then I put in another colon, and then the step index, and by default, it's
one. So let's say, this goes all the way from the beginning to the end with a step one.
And if I put in a step two, then it takes every second item. And I can also specify
a negative index. So this is a SIM, a nice trick to reverse your list. Now let's talk
about copying a list. So let's say let's call this list original, and put in some fruits
in here, banana, Sherry, and an apple. Now, if I want to create a copy, and I simply do
it by assigning it to the original one, then you have to be very careful. So if I print
the copy, the nice see that it, it's the same as our original list. But now if I if I modify
the copy, what will happen is that it will also modify the original list. So for example,
if I append a lemon, and if I print the copy, and I also print the original, then we see
that the original list now also has a lemon in it. And this is because with this assignment,
both lists refer to the same list in inside the memory. So yeah, be very careful here.
And if you want to make an actual copy of your list, you can do it with the dot copy
method. So now if we print them, we see that the original method, the original list is
still the same. We can also do it with the list function, and as argument we use the
original list. So this also makes an actual copy. And as third option we can use slicing,
if I just use a colon here, so this means slicing all the way from beginning to the
end. And this also makes an actual copy. Now, as a last nice trick, I want to show
you an advanced technique that is called list comprehension. So that's an elegant and fast
way to create a new list from an existing list with one line. For example, if we have
a list with numbers 123456, and we want to create a list with squared numbers, and we
can do it like this, inside brackets, we say, i times i, for i in a, or maybe let's call
this my list. Now if we print my list and print the second list, then we see that a
new list got created where each element is squared. And the syntax is you have your expression,
and then a four in loop over your list. So note like the same with iterating, you don't
have to call this AI, you can also call this x. And then also use the x here. So that's
a very simple and elegant way to create a new list with another with some expression
in one line. tuple is a collection data type that is ordered and immutable. It is similar
to a list with a main difference to the tuple cannot be changed after its creation. a tuple
is often used for objects that belong together. And let's have a closer look at tuples. And
what you can do with them. First of all, a tuple is created with parentheses. And within
these parentheses, you put each element that you want separated by a comma. So for example,
let's put in Max, 2008, and Boston. And if we print this, we see each all the elements
inside our tuple. Now the parentheses are optional. So we can leave them away. And it's
still a tuple. One special thing is if you just want to have one element inside your
tuple. And even if you put it in parentheses, and you write it like this, then this is not
recognized as a tuple. So if we have a look at the type of this, then this is recognized
as a string. So what you have to do, then you have to put a comma at the end, even if
it may look strange. That's the right syntax. So now it's recognized as a tuple. You can
also use the built in tuple function to create a tuple from an iterable. For example from
a list, so say Max 28. Awesome. If you print this, then we also have our tuple created.
Now if we want to access elements, we just do that by referring to the index. So if we
say item equals my tuple, and then inside brackets, we specify the index that we want,
and the indices start at zero. So index zero gives us the very first item, we print this,
then we see we have Mac's index one, we get 28 index two, we get Boston, and if you use
an index that is too large, we get an index eiroa index out of range. So be careful here.
We can also specify a negative index minus one refers to the very last item. So that's
Boston, in this case, minus two is the second last item, and so on. Now, what happens if we want to change the
elements inside our tuple like with lists week, if we write my tuple and then get the
first index and assign it to a new value, like Tim, and if we run this, then we get
a type error object does not support item assignment. So this is not possible because
a tuple is immutable. Now we can easily iterate Over a tuple with the for in loop. So for
i in my tuple colon and then do something in this case, I just want to print the element.
So then for each element, we print that, and we don't have to call this I can also call
it for example x or whatever we want. We can also easily check if an element is inside
our tuple with an if in statement, so if max in my tuple, and then we say just print, yes.
And otherwise, we print No. And if we run this, then we get a yes, so Max is in our
tuple. If we check for Boston, it's also inside our tuple if you check for Tim, and we get
a no. So very easy syntax to check if something is inside our tuple. So let's talk about some
other useful methods that you can do with a tuple. For example, create a tuple with
some letters in it. And first of all, if we want to get the number of elements inside our tuple, we can
just use the Lang method, Lang, of my tuple. And this returns five, so we have five elements.
If we want to count some elements inside our tuples, so we can use my tuple dot count,
and then we count the letter P. So then we see we have two letter piece inside
our tuple. If we check for the L, we get a one, if we check for all, which is not inside
our tuple and we get zero. And we can also find the first index of some specific elements.
So for example, my tuple dot index of P. and if we run this, then it returns the first
occurrence of this element. So this is an index P. For example, if we say a, and we
get index zero, we get if we want to get the index of L, then we get index three. And if
we check for an element that is not inside our tuple, then we again get a value error.
So be careful here. Um, we can also easily convert a tuple to a list and vice versa with
the list and the tuple function. So if I say my list equals and then I use the list function,
and put the tuple here, then I get a list out of it. And I can convert it back, I say
my tuple to equals, and then the tuple function, my list if I print this, so then I have a
tuple again. Now let's talk about slicing with tuples. So slicing is a very nice way
to access sub parts of your tuple with the use of the colon. So for example, let's create
a tuple with some numbers in it. And let's create a tuple. And then the syntax is we
use the tuple, the original tuple. And then inside brackets we specify a start and a stop
index. So for example 225. And if we print this, then we have number 345. So this goes
from index number two to index number five, and the other last index is not included.
So it only has index two, three, and four in it. So if we don't specify a start index,
then it starts all the way from the beginning. And if we
If we don't specify a stop index, then it goes all the way to the end. Now, we can also
use an optional step argument. So by default, this is one. So in this case, it goes all
the way from beginning to the end with a step of one. And if we put in a two here, for example,
then it takes every second element. And we can also use a negative step, this is a nice
little trick to reverse your tuple. Now we can, let's talk about unpacking. So if we
create a new tuple, like at the beginning, let's put, let's use Max, 28, and Boston.
And we can unpack it, if we write to the, at the left side, we write our variables,
so name, H, and city, and then just say equals to my tuple, then we get each separate element
and the city. But the number of elements that you put in here must match the elements inside
our tuple. So if we just use two elements here, then we get a value error, to many,
many values to unpack. But what we can do is we can unpack multiple elements with a
star. So for example, if we use some numbers, so
1001234. And if we want to unpack this, so let's say I won, and then a star, and say,
I, two, and I three equals to my tuple. And then if we print I one, this is the very first
item, if you print by three, this is the very last item. And if we print by two, then these
are all the elements in between, and now converted to a list. Yeah, so one more thing that I
wanted to show you is to compare a tuple and a list. And because a tuple is immutable,
Python can make some internal optimizations. And thus, working with a tuple can be more
efficient sometimes, especially when working with large data. So let me copy this in here.
In this example, we create a list and a tuple with the same elements. And then we use the
SIS dot get size of method to return the number of bytes. And both of them. And if we compare
them, then we see that a list is larger, even though it has the same elements as the tuple.
And also can be more efficient to iterate over a tuple. And also to create a tuple.
So if we compare if we use the time it method, so there's a very nice method in the time,
module time at that time it and then you can use a statement and repeat this specific number
of times. So in this case, it's 1 million times 1 million times we want to create a
a list and 1 million million times we want to create a tuple and then measure the time.
If we run this, then we see that it took much longer to create the list than to create the
tuple. So yeah, keep that in mind that working with tuples can be more efficient than working
with lists. dictionary is a collection data type that is unordered and mutable. It consists
of a collection of key value pairs. So each key value pair maps the key to its associated
value. And let's have a closer look at dictionaries and what we can do with them. First of all,
a dictionary is created with braces. And inside these braces, you put each key value pair
separated by a colon. So let's say key name, and then colon, and then the value, Max. And
then you separate each item with a comma. So comma, and then let's put in another key
value, pair, age, colon 28, comma, and another one, city, Cole on New York. And if we print
this, and we see all the key value pairs here, then we can also use the dict function to
create a dictionary. And there we put all our keys as arguments. So name equals Mary,
comma, age equals 27, comma city equals Boston. And if you print this, we see that we have
a second dictionary here. And note that with this function, you don't have to use quotes
for your keys. Now, when you want to access the values, you do that by saying my dict
and then inside brackets, you give it the key. So you print the associated value for the name, then we
get max. And if we want to look up the age, we get 28. And what will happen if we use
a key that is not inside our dictionary, for example, check the last name, then this will
raise an exception, a key error. So be careful here. A dictionary is mutable, so you can
add or change items after its creation. So when we want to add an key value pair, we
simply do it like sell, we say my dict and then inside brackets give it the new key.
Let's give it an email. And then the associated value max at x y z.com. And if we print it,
then we see that our dictionary now has the email key value pair here. Now if we do the
same thing again, and the key already exists, then it got gets overwritten. So let's say
emails, coolmax, then we see that it still has the key email. And now with our new value.
If we want to delete items, we have several options, we can say, we can use the Dell statement,
so say Dell, my dict and then off the key name, and then we print it then we see that
the name key value pair is no longer inside our dictionary. Or we can use the pop method.
So we can say my dick dot pop, and then give it the key, let's pop the age. So now we see
that the age is no longer inside our dictionary. Or we can use the pop item method. So prior
to Python 3.7. This removes an arbitrary pair and since Python 3.7 This removes the last
inserted item. So in this case, it's Python 3.7 and then it removes the city. So we see
the city is no longer inside our dictionary. When you want to check if a key is inside
our dictionary then there are two common ways to do that. The first one is to use an if
in statements so we say if name in my dict and then we can use this key we can say print
my dict name. So then max gets printed. If we say last name and want to print it then
the if statement is wrong. So this doesn't get executed, so nothing is printed here.
Or you can use a try accepts accept statements. So try and then access a key. So let's say
my texts, name. And except print, let's just print error. So if we run this, then this
statement is successful, so it can print the name. And if we want to access the last name,
then this statement will raise an exception, a key error. So an exception is thrown, which
is caught here. So this statement then will be executed. So if we run this, then we see
that we have the error here. When you want to loop through a dictionary, you have several
different ways within for in loops. So you can say for key in my dict, and then print
the key, then you can see that this loops through the dictionary, and loop through all
the keys. You can also say dot keys. So this will do the same thing. The keys method returns
a list with all the keys inside it. You can also loop over the values. So you
can say for value in my dict dot values and then print the value. And then it prints the
values. Or if you want to have both in one loop, you can say for key comma value in my
dict dot items, and then you can print the key and the value. Now when you want to copy
a dictionary, you have to be careful. So the most common way to do it is like so let's
say my dict copy equals to my dict and just assign it to the original mighty dictionary.
And now if you print this, then we see that is the same as the original one. But now if
we modified the copy, this will also modify the original one, let's say my direct copy
and at the email max@xyz.com. Now if we print the copy and the original one, then we see
that both dictionaries now have the new key value pair. This is because with this simple
assignment statement, both dictionaries now point to the same dictionary inside our memory.
So be very careful if use this expression. If you want to make an actual copy, you can
use the built in copy function. So if we use this one and print it and we see that the
original one didn't change, or you can use the dict function. And as an argument,
you pass the dictionary that you want to copy. So if you use this, then we see that the original
dictionary also didn't get affected. Now there's a useful method to merge two dictionaries,
that is called the update method. Let's create two dictionaries. And the first
dictionary has a name, an age and an email. And the second dictionary also has a name
and an age but no email, but then it has a city. And if we want to merge these, we can
do it like this. They might take a dot update and then with the second dictionary, and now
if we print this then what happened. All the existing keys or key value pairs got overwritten. So the
name is now Mary, the age is now 27. The email didn't change and the non existing keys the
city got edit So, yeah, that's a nice way to update to dictionaries. Let's talk about
possible key types. So in all the examples before I used a string as a key, but you can
use any immutable type, for example, you can also use numbers as a key, or even a tuple.
If it only contains immutable elements. For example, we can say, my dict, and then key
three, and so value, give it the squared value, then a key six and 36, and the key nine, and
81. Now let's print this. So this is also possible, but then you have to be very careful,
because when you want to access a value, like so, and you want to do it like with lists,
and you refer to the index number, for example, say index zero, then this will raise an exception
because you have the key error. Zero is not in our list, what do you rather want to do
is you want to use the actual key, so the key three is not nine, and then if you print
the value, then we see we get the nine. So yeah, we also can use a tuple as a key. So
let's say my tuple equals eight, seven, and then create a dictionary and S key, we use
the tuple and s value, we use the sum. Now if we print this, then we see we have our
new tuple here, and are our new dictionary here. And yeah, so tuples are also possible.
But what is not possible, for example is a list. If we use a list here and run this,
then this will throw an exception type error on hashable type. That is because a list is
mutable and can be changed after its creation. And therefore it's also not hashable and cannot
be used as a key. So be careful here. Set aside collection data type that is unordered
and mutable. But unlike lists, or tuples, it does not allow duplicate elements. A set
is created with braces, just like a dictionary, but we don't put key value pairs in it. But
instead just single elements separated by a comma. For example, let's put some numbers
in here and print this. Then we will see our set here. And if we put for example, another
one and another two here, and print this again, then we see that only one of each element
is kept. Because a set does not allow duplicates, we can also use the set function and use an
iterable. Here, for example, let's use a list here. This will also create a set, or we can
use a string here are example Hello. And if we print this, first of all, we see that the
order is arbitrary because a set is unordered. And the order is not important. And we also
see that there's only one l in our set. So this is a nice little trick to find out how
many different characters are in your word. Now, if you want to create an empty set, and
you do it like this just with the braces, then you have to be careful because now if
you have a look at the type of this, then we see that this is recognized as a dictionary.
So if you want to have an empty set, you have to do it with the set method. Set is mutable
so you can change it later on. So now we can add elements and we do this with the dot add
method. So let's put in some numbers here and print this. And we can also remove elements
again with the Remove method. So let's remove the three and if we want to remove an element
that does not inside Our set, then this will raise a key error. So be careful here. So
there's another method, let's call that this cart method that does the same thing. So it
also removes the element. And if it does not find the element, then nothing will happen.
So no error here. We can also use the clear method, of course, this will empty our set,
or we can use the pop method. So this will return an arbitrary value of our set and also
removes it. So we print this. Then we see in this case, it returned the one and also
remove the one from our set. Now, we can iterate over our set very easily with an for in loops.
So for i in my set, and then do something, in this case, just print this. So this will
iterate over each element and print it. And we don't have to call this I we can also call
this for example x or whatever we want. Now we want to check if an element is inside our
set, we can do this with an if statement. So if one in my set, and then we print, yes.
So the one is in our set, the two is in our our set. And if we check for example, for
the four, then nothing gets printed. Now, let's talk about union and intersection. And
for this case, first of all, let's create three different sets one with odd numbers,
one with even numbers, and one with prime numbers. And now we can calculate the union.
So the union combines elements from both from two sets without duplication. So let's calculate
the union of odds. And we do this with dots union, and then as an argument, the second
set so events and print this. Then we see that now we have all the numbers from zero
to nine. So the union will combine elements from both from two sets without duplication.
We can also calculate the intersection of two sets. So the intersection will only take
elements that are found in both sets. So if we say the intersection equals arts dot intersection
events, and if we print this, then we will get an empty set, because arts and events
don't have the same elements. Now if we calculate calculate the intersection of arts and primes,
we will get all the prime numbers that are also odd. So 357 if you calculate the intersection
of events and primes, then we will get back only the even prime numbers. So in this case,
only the two now we can also calculate the difference of two sets. So let's create two
different sets again, set a with numbers from one to nine and set B with one with 123 10
1112. Now the difference will return a set with all elements from the first set that
are not in the second set. So let's call that call a diff equals set a dot difference set
B and print this. Then we will see that we will get back the
numbers from four to nine because it takes the elements from our first set, but not the
ones that are also in the second set so only from four to nine. So if we do it the other
way around set be the difference set a then it will take 10 1112 but not these three numbers
because they are also here. So then there's a second different method. second difference
method, that's called the symmetric difference method. So, the symmetric difference method
will return a set with all the elements from set A and set B, but not the elements that
are in both sets. So again, so it takes 456789 from set A, and 10 1112 from set B, but not
one, two, and three, because they are in both sets. So if I use set a symmetric difference
set B, then this is the same thing. Now, union and intersection and the difference method
that I just showed you, they will not modify the original sets, they always will return
a, a new set, but we can also modify our sets in place. So for example, we can say set a
dot update, set B. And now print our set A, then we will see that this updates the set
by adding the elements that are found in another set. So without duplication again, so it does
not add one, two and three again, but it adds 1011 and 12. There's also a intersection up
update method. So set a dot intersection update, set B. And what this does, it updates the
set by keeping only the elements from found in both sets. So only one two and three are
found in both sets. So only these numbers remain in our set. Then there's also the difference
update methods. So set a difference update set B. And if we print this, we will see the
numbers from four to nine because difference update, it updates the set by removing elements
found in another set. So it also it found it finds one, two and three in the set B so
it removes these numbers from our set A. And then there's the symmetric difference update.
So this updates the set by only keeping the elements found in set A and in set B, but
not the elements that are found in both. So one, two, and three are found in both sets.
So these are not taken, but then it takes all the remaining elements from both sets.
Yeah, you'd have probably have to play around with them yourself a little bit to make it
clearer. And yeah, let's also talk about soap sets superset and disjoint methods. So for
example, let's make them a little bit smaller. You can calculate the if set A is a subset
of set B. And we print this then this will return false because subset means that all
the elements of our first set are also in our second set. So if we use it the other
way around set B is a subset of set A, then this will return true because one two and
three are also in the second set. And the opposite is called the super set method. So
is super set. And in this case, it returns false because
a super set returns true if the first set contains all the numbers or all the elements
from the second set. So, set B does not contain 546. So it's not a super set. But set a is
a superset of set B, because it contains one, two and three. And we can calculate if two
sets are disjoint. So this join returns through if both sets have a null intersection, so
no same elements. So set a is this joint set B. And if we print this, this will return
false because they have same elements. And if we create, for example, a set C and put
seven, eight in here, and check set a is this joint with set B, then this will give us true.
Now let's briefly talk about copying two sets. If you have watched the previous episodes
about lists, for example, then you already know this, you have to be careful, and you
want to copied two sets and only do this with a simple assignment. So let's say let set
B equals to set one, set a. First of all, if we print this, then we see that we have
a copy. But now if we modify the copy, let's say set B at set seven. And if we print the
copy and also print the original one, then we see that also the original one changed,
because with this simple assignment both points to the same set. So be careful here, you only
copy the reference. Now if you want to make an actual copy, you have to use the dot copy
method. So if you run this again, and we see that the original set didn't change, or you
can also use the set method and use the first set as a argument. This will also make an
actual copy. Now as the last thing, I want to show you the frozen set, the frozen set
is also a collection data type. And this is just an immutable version of a normal set.
So you create this with the frozen set method. And there's an argument you can also put an
iterable here, for example, a list and let's print this, then we see our frozen set here.
So with a frozen set, you cannot change it after its creation. So if I try to do a.at,
two to this will give us an error. Or I can also say try a remove one. This will also
give us an error or any of the updates, update methods I showed you, they also don't work.
But for example, Union intersection and difference method, they will work a string as an ordered
and immutable collection data type that is used for text representation. And it is one
of the most use data types in Python. So I hope that at the end of the session, you'll
feel comfortable working with them. So let's start. First of all, a string is created with
either single or double quotes. So you can use double quotes and then put your letters
in here. So let's say hello world, and now we can print this and then we see our string
here or you can use single quotes This is probably more common. The only thing you have
to be careful is if you have another single quote inside this. So if you have for example,
I am a programmer. Now if you try to run this,
then this will get a syntax error. So what you can do is you can either use an escaping
backslash here, so this is valid. Or you can put your single quote in Side double quotes.
So this is again, a valid string. You may also sometimes see triple quotes. So this
is typically used for multi line strings. So now I can go in another line. And this
is also used for documentation inside your code. So now if we run this, we see that our
string goes over multiple lines. Now, you may also sometimes see an escaping backslash,
like so. And this just says that the string should continue in another line. But it should
not create a new line here. So now if we run this, then we see we have our one line hello
world string. Now, if you want to access characters, or sub strings, it's the same like with lists,
you access it with inside brackets, so let's say char equal, or let's create a string first.
Alright, my string equals hello world. And then you can say char equals and then my string.
And then in brackets, you put the index you want. So if you want the very first character,
you have to use index zero. So we can print this. So this is the H and F, we use index
one, we get the E, and so on, we can also use a negative index, so minus one is the
very last character, minus to the second last, and so on. But what we cannot do, for example
is we cannot access a character and change it. So if I want to change the first character
to a lower age, if I tried to run this, now, this will get a type error, a string object
does not support item assignment. And this is because strings are immutable, so they
cannot be changed. So be aware of that, we can also access a whole substring with slicing.
So then I will say my string and in brackets, I put this start index, so let's say one,
and then a colon and then a stop index. And then if I print this, then I will see I get
the string, e Ll O. So what this does, it starts at index one, and goes until index
five, but index five is excluded. So be careful here. So it has 123, and four, so our string
is E Ll O. Now if I don't use a start index, then it starts all the way from the beginning.
And if I don't use a stop index, then it goes all the way to the end. So this goes all the
way from beginning to end. And then there's another optional step index. So if I put another
colon here, and by default, this is one. So this takes every character. And now if I put
a two here, it takes every second character. And I can also put a minus one here, and then
what it does, it will reverse our string. So that's a nice little trick to reverse the
string with this slicing operator. Now, we can concatenate two or more strings simply
with a plus. So if I create another string, Tom and I will just call this hello and say
this is now a greeting. And then I can say my sentence, I will create a new string that
is greeting plus, and then I want a space between them. And then plus again, plus the
name. And now let's print this and then we see we have our concatenated string.
So very easy with this plus here. Now we can iterate over our string with a four in loop
so for i in greeting, and then do something so just print this print every letter So,
this goes over our whole string and prints each character. And we don't have to call
this I, we can also call this for example x or whatever we want. Now, if you want to
check if a character or substring is inside our string, we can do this with an if in statement.
So I say if and then I want to check for the letter E. So if e in greeting, and then I
will print Yes. And otherwise else I will print. No. So he is inside my word. So it
prints Yes. So if I check, for example, for p, then it will print No. And I can also check
for substring. So I can check for E Ll, this will also print Yes. Now let's talk about
some more useful methods that you can do with strings. So let's say we have a string with
some whitespace here, and then we have our hello world, and then some more whitespace
at the end. So if we print this, we will see that our printed string also has the wide
string. Now if I want to get rid of this, I can do my string equals my string dot strip.
So this method removes our whitespace. So now if I print it, we see that the whitespace
is gone. And be aware that we this method does not change our string in place, because
as I said, a string is immutable. So if I just write it like this, then this will not
change my original string. So if I run it, our original string still has the whitespace.
So what we have to do is, we have to assign it again to our original one, and then we
have the new string with without whitespace. Now, what we can do also, with strings is
we can say, we can convert every character to an upper case, so let's say my string dot
upper. And then we have all an upper cases, we can also say my string dot lower, then
we have all in lower cases, we can check if my string starts with specific character or
substring. So if we can say starts with and then we can say h. so this will give us true
or we can also check for Hello, also true and check with world. And we will get a false.
But we can also check if it ends with something. So if it ends with world, so now we have true.
And yeah, if it ends with Hello, then we get a false. Now we can find the index have a
character or a substring. So let's say my string dot find and then we want to find O.
So this will return the first index that it finds with an O. So index 01234. So it returns
a four, we can also check for substrings. So this is the at index three, our l o substring
starts and if it does not find a string, then it will return a minus one. We can also count
the number of characters or substring it finds. So let's check for how many O's we have in
hello world. So this will return to and how many peas do we have? We have zero. We can also replace characters or substrings
inside our string. So we can say my string dot replace. And then we want to replace world
with a new word. So we want to replace it with the universe. And now, if we print this,
then we see our string is now Hello universe. And also be aware here that this will return
a new string and does not change this one. So if it does not find this strings, let's
say for example, we have a typo here, then it does nothing. So it will still print the
original string hello world. Now, let's talk about lists and strings. So let's say you
have a string with some words. So let's say we have here, how are you doing. And you want
to convert this to a list and put at each word of my string as an element in my list.
Now, what you can do then is you can say, my list equals my string, dot split. And if
we print our list, then we see that we have each word now as an element in our list. And
by default, the delimiter it is looking for is a space. So here, the default argument
is a space. So it looks for each space, and then splits our string here. Now, for example,
if you have commerce, here, and then cannot find a space, so it, we only have one element
here. So now what you would then have to use, you would have to use as a delimiter, a comma.
And then again, we have four elements. Now if you have the list, and you want to convert
it back into a string, what you can do is you can say, let's say new string equals and
then we say, my whip, you know, we say and empty string, and then dot join, and then
the list as an argument. And then we print the new string. And then we will see that
this will concatenate all of our words, all of our elements in our list. So this will
put all of our elements together as a string. And between each element, it will put this
string that we put here. So now if you put a space here, then it will put a space between
each element. And now we have our original string again. So the dot join method method
is a very useful method to quickly join the elements of a list back into a string. And
I would highly recommend to remember this one because this is very useful. And let's
In fact, let's talk about this a little bit more. So let's say we have a list with some
elements. So let's say for example, only A's and then times six. So maybe you know this
syntax. So this will create a list with six elements. And now if you have the task to
join this into a string, a lot of times what you will see is that you will create an empty
string and then you will use a for in loop. So for i in my list, and then you will say
my string plus equals i. So let's check this. So it worked. We have our string here, but
this is bad Python code. Because what happens here since a string is immutable. This will
create a new string here and then assign it back to our original string. So this operation
is very expensive. What you should better use it The dot chain method. So as I just
showed you, we can say, my string equals, and then we will say an empty string dot,
join my list. And then we will print this. So this will also give us the same thing,
but it's much cleaner, and also much faster. So let's look at the time of both of these
ways. So let's say we have saved from the time at module, we import the default timer
as timer. And then we will say here, start equals timer. And at the end, we can say,
Stop equals timer. And then we will print, stop minus start. So this will give us the
time it takes from here to here. And we will do the same thing here. So if we run this, we will, let's remove this,
we see that both both was very, very fast. But now let's say we have, for example, a
very large list with let's say, 1 million elements. And now I don't want to print my
list. So if we run this now, what we will see that this The second way with a dot join
method is much faster. So the first way took more than half a second and the second only
point 01 seconds. So forget this way of doing it. And remember that duck shine method. Now
as the last thing, I want to talk about formatting strings. So there are two ways to format a
string, the old styles are with a percent operator, or with a thought format method.
And since Python 3.6, there's also the new f strings. And let's talk about all of these
methods. So let's say we have a variable and call it it's, let's say variable equals a
string, and we have the name Tom. And then we will create a string and say it's the variable
is and then we use the first method, so we use a percent s. And then after our string,
again, we use the percent and then the variable. So this tells the interpreter that we have
a placeholder with a string here. And then afterwards, we fill this placeholder with
our variable. So now if you print our variable, then we will see that our string is the variable
is Tom. Now, if we have a number here, we shouldn't use percent s here, we should use
percent D. So this stands for integer decimal value. So now we have the variable is three.
And let's say we have a floating point. So like this, and if we run this, then we see
that we still have three here because we told the Python that we have a decimal value here.
So now what we want now is we want a floating point, so we say percent F. And then we have
our floating point value here. And by default, it has six digits after the decimal points.
So if we want to specify how many digits we want to have, we can say, dot percent dot
and then how many digits and then let's say two digits and then.to F. So this will give
us two digits after the decimal point. So this is the very old formatting style. The
new formatting style is with the dot format methods. So now what He wants to do is as
a placeholder, we use braces. And then after our string, we call the dot format method.
And then here we put all our elements as arguments. So now if we print this, then we see that we have the placeholder got
replaced with our variable. And we can also specify how many digits so we can say, colon,
dot two, F. So then we have two digits after the decimal point. And for example, if we
have more variables, we simply would place another placeholder here, and then another
argument here. So let's say we have var two equals six, and then we would put var two
here. And then we will see that we have all our variables inside our string now. So these
are the old formatting styles. And the newest way to do it is with the F strings. So this
is, since Python 3.6, or newer, you can use the F strings. And with an F string, you would
simply put a f between the string and then the string. And then you will also use braces.
And inside the braces, you can use your variables directly. So you can use var here, and var
two here, and then you don't need this anymore. So if we run this, then we see it worked.
And yeah, I think this is much more readable, it's more concise. And it's even faster, especially
if you have a lot of variables here. So I would highly recommend using this f string
f strings now, since python 3.6. And yeah, what this does is it evaluates the this at
run time. So we can also put some operation here. So let's say it's a mathematical operation,
like var times two. And then this will, will be evaluated at runtime. So now, we see we
have our two times our variable here. And, yeah, so that's it about f strings. And that's
all I wanted to show you about the strings. The collections module implements special
container data types, and provides alternatives with some additional functionality compared
to the general Bert and containers, like dictionaries, lists, or tuples. So we will be talking about
five different types from the collections module, the counter the named tuple, the artists
dict, the default dict, and the deck. So let's start with the counter. And first of all,
we have to import it from collections import, counter. And the counter is a container that
stores the elements as dictionary keys and their counts as dictionary values. So let's
say we have a string called a with some different characters, a BBB, CCC. And then we can create
our counter, we say my counter equals counter, and then we give it our string. And if we
print it, then we see we have a dictionary with all the different characters as keys
and their count as values. So we have five times a four times B, and three times C. And,
like with a normal dictionary, we can have a look at only the items. So this will give
us all the key value pairs, we can have a look at the keys. So this will give us an
iterable over the keys. And we can also only have a look at the values. So this will give
us all the different values. And what's also very helpful is to have a look at the most
common element in our counter dictionaries. So we say if we first print our counter again,
and then we can see Today we want to print my counter dot most common. And then here
how many different items, so I want to see only the very first so the most common elements. So if I print this, then I will
get the A with the count five is the most common element. So if I say two here, that
will give me the two most common types, so it will also put the B in here. And this will
return a list with tuples in it. So, for example, if I want to have a look at only the, I want
to see what is the most common element, then I will x have to access the index zero, so,
this will give us the tuple at index zero. And then if I only want to see the element,
then I will again have to access the first element of this tuple. So against zero, and
then I will get the A is the element that is most common in our string. So, we can also
use a list here or any other iterable. Yeah, we can also have a list with all all the different
elements. So, if we say, print my counter dot elements, and this will give us an iterable
over elements repeating each as many times as it counts. So, I have to convert this to
a list in order to print it nicely. So now if I print it, and I will see, I will get
all the different elements here as a list. And I can, for example, iterate over this.
So that's the counter next talk about the named tuple. And of course, first of all,
we have to import it. So we say from collections import named tuple. And the named tuples is
an easy to create and lightweight object type, similar to a struct. So what I can do is I
can define my named tuple. I say for example, let's create a 2d point and call it point
equals and then I will say named tuple. And then as first argument, I give it the class
name. So typically, this is the same name that I use here. And then as a second argument,
I use another string and here I use all the different fields I want separated by either
a comma or a space. So I can say x comma y. So this will create a class called point with
the fields x and y. So now I can create this point. So I can say p t equals point and then
I will give it values for x and y. So for example, I will give it one and minus four.
And now if I print my point, then I will see I have a point with x equals one and y equals
minus four. And I can also access the fields. So I can say p t dot x and p t dot y. So then
this will print the values for x and y. Next is the ordered dictionaries. So from collections
import, ordered dict. And the ordered dict is just like a regular dictionary, but they
remember the order that the items were inserted. So they have become less important now since
the built in dictionary class has also the ability to remember the order since python
3.7. This is guaranteed. But for example, if you use an older Python version, this may
be a way to use a dictionary that remembers the order. So for example, let's create a
dictionary like so. And then we can append key value pairs like with a normal dictionary.
So we say here and brackets give it a key a and a value, one. And let's do this with
some more key values. So let's say we have B, C, and D, and 234. And now if we print
this, then we see it's the same order as we inserted
it. So for example, if we inserted the A, at the very end, then it will also get printed
at the end of our audit dictionary. Yeah, since here, I'm using three python 3.7. So
in this, I can also just simply use a normal dictionary now and it still remembers the
order. Next, we have a look at the default dict. So from collections import default dict.
And the default dict is also similar to the usual dictionary container, with the only
difference that it will have a default value if the key has not been set yet. So what we
will do, we have to create a default dict. And as an argument, we will give it an a default
type. So let's say we want to have an int, an integer here as default type. And then
we can fill our dictionary, again, let's say D, with the key a is one and D with the key
b equals two, and let's print our dictionary. So we will see it here and then we can access
the keys. So for example, let's access the key a, and then it will give one and the key
p will return to and now if I put in a key that does not exist, so for example C, then
what will happen, it will return the default value of an integer. And this is by default
a zero. So I can also for example, say I want a float default value. So then this will return
0.0 if it does not exist, or for example, I will have an empty list if it does not exist.
So yeah, with a normal dictionary, this would raise a key error. So now this would raise
a key error, but with a default dict it would return the default value of the type that
we specify. So, as a last collections type, we will talk about the deck. So the deck is
a double ended queue. And it can be used to add or remove elements from both ends. And
both are implemented in a way that this will be very efficiently. And yeah, let's create
a deck so let's say d equals deck and then we can append items like with a list, let's
say the append one and the append two and then print it. Now, now, we see our deck here
and also we can say we can say d dot panned left. So this will add elements at the left
side. So now we can see our three got added here. And we can also again remove elements
from both sides. So we can say d dot pop. And now if we print our deck then we will
see that the with pop, this will return and remove the last element. So now the two got
removed or we can say d dot pop left so this will return and remove the other From the
left side, so now, the three got removed. can also of course, say d dot clear. So this
will remove all elements, we can extend our deck with multiple elements
at a time. So we can see d dot extend, and then give it a list, let's say 456. So this
will add all the elements at the right side, or we can say d dot extend left, this will
extend all the elements at the left side. And note that now, it will add First, the
four from the left side and the five, and then the six. So now six is the most left
elements in our deck. We can also rotate our deck so we can say d dot rotate one. And now
if we print it, we will see that this will rotate all elements one place to the right,
I can also say for example, do not rotate to and then this will rotate all elements
to places to the right. Or if I want to rotate to the left side, and I will give a negative
number here. So if I say d dot rotate minus one, then all our elements will rotate one
place to the left. The inner tools module is a collection of
tools for handling iterators. Simply put iterators are data types that can be used in a for loop.
So for example, the most common iterator is the list. And the error tools offer some advanced
tools. And we will be talking about the product, the permutations combinations, the accumulate
function, the group by function, and some infinite iterators. So let's start with the
product. So first of all, we have to import it. So we say from it or tools, import product.
And let's say we have two lists a equals one, and two, and B equals a list with three and
four. And then we say we have a product of a and b, and the product will compute the
Cartesian product of the input iterables. So let's print this. So print the product.
And then we will see that we have a editor tools object. So this is an iterator. And
to see the elements we can come convert it to a list, and then we will see the product.
So the product will combine one and three, and one and four, and then two, and three,
and two and four. So this is the product, we can also define a number of repetitions.
So if we say repeat equals two, then it can repeat. And let's run this and then we see
that this is a very large list. So let's make our second iterables smaller, and print this.
And then maybe the repetition gets clearer. So we have one and three. And since we can
repeat again we do one and three, and we have one and three and two and three, two, and
three and one and three, and again with repetition, two and three, and two, and three. So that's
the product. Then we also have something called permutations. So permutations will return
all possible orderings of an input. So let's say we have one, two and three as a input,
and then we calculate the permutations of a and print this again, as list and then we
see all the different orderings So we have 123132213231312, and 321. So that's permutations.
And we can also specify the length of the permutations as a second argument. So if we
want to have shorter permutate, permutations with only length two, we skip the argument
two. And then we see different orderings with the length of 22121321, and so on. That's
permutations, then we have combinations. So from either tools, import combinations, and
the combinations function will make all possible combinations with a specified length. So let's
also make an example here. Let's make a list 123, and four, and then say comm equals combinations
of a. And the second argument with the length here is mandatory. So in this example, I only
want the length two, and then print this again as a list. And then we will see all possible combinations
with length two, so 12131423, to four, and three, four. And, and note that we don't have
combinations of the same arguments or no repetitions here. And if we want that, we can also use
the combinations with replacement function. So then we import it, so import combinations
with replacement. And then let's make another combination iterable and say combinations
with replacement of a and also of length two, and Prentiss comm with replacements, and then
we see that it will make combination of one and itself. So one and one, one and two, one
and three, and so on. So this is combinations and combinations with replay replacement.
Now, when we have the accumulate function, so the accumulate function makes an iterator
that returns accumulated sums, or any other binary function that I will give as input.
So let's make an example. First of all, import the accumulate function. And then we can say,
we leave the list a equals 123, and four, and then we say, accumulate equals accumulate
of a, and print this. First of all, let's print our list and then print the accumulated
list. So we see that our list is one, two, and three, and they accumulated sums is 136,
and 10. So the first elements stays the same. And then we have one plus two is three, three
plus three is six, and six plus four is 10. So that's the accumulate function. And by
default, it will compute the sums. But we can also for example, multiply the elements
so let's import operator. And then we can give as a second argument, we can say func
equals operator.ml. So this will multiply each element so one stays the same. One times
two is two, two times three is six, and six times four is 24. And as a third example,
let's just use the max. So this will return the max for each comparison. So for example,
if we have a five here in between, and have a look at our list than one is the same two
now two is the max then compared with five and five is Till the max compared with three
and five, still the maximum and compared with four, five is still the maximum. So that's
accumulate. Now let's talk about the group by function. The group by function makes an
iterator that returns keys and groups from an inner rebel. So let's make an example.
To make this clearer, let's say we have our list a equals 123, and four. And then we say
we make a group object, and that is group by, and then we want to group A, and we have
to give it a key, which map has to be applied. So as key, we can define a function, so let's
say smaller than three, and give it an input, and then return x smaller than three. So this
will return true or false. And as a key, we will give it this function. And then let's print this. So we will see
that this is a group by object and we can iterate over this. So we can say, for key
and value in our group object, and then we want to print the key and the value. And then
we will see it prints the key and an inner tools, object, group or object. So we can
convert this to a list to see the values. And then it gets clearer. So we have our input
array. And we group this into other lists. With the comparison if it's smaller than three,
so for one and two are grouped together, because they are smaller than three, and the key is
true, and three and four are grouped together, and the key is false. Now, we can also use
a lambda function here, so I will talk about this in the next video. But as a very short
explanation, lambdas are small one line function that can have an input and will do some expression
and then will return an output. So I can write this same function in one line with a lambda
expression. So I can write lambda, x and then colon, and then simply x smaller three. So
this will do the same thing. If I run this, then it will print the same thing. Now let's
make an another second example. For this, maybe this is not clear at the first side.
So let's define a object persons. And this is a list. And inside this list, we have different
dictionaries. And the dictionaries contains a name and an age. And let's say we want to
group our persons by the same age, so let and then we say lambda x and simply x and
s key the age. And then if we run this and print this, then we will see as keys, we have
the different values for age. So we have 2527 and 28. And then we also see that it grouped
Tim and then together because they both are 25 years old. And then we have Lisa and Claire.
So that's the group by function. Then we also have some infinite iterators. There's the
count function, then this cycle function and the repeat function. And the count function
is very simple. So if we just say for i in count and then Give it a start values. So
let's start at 10, and then print this. So this will make an infinite loop that starts
at 10. And then adds one for every repetition. So one, so 10 1112, and so on. And this is
still going now. So then for example, if I say, if I is 15, then we will we break, so
then it will stop at 15. That's the count function, then there's the cycle function.
So this will cycle infinitely through an iterable. So let's say we have a list that has one,
two, and three. And we want to cycle through a and print this. So this will print one,
two, and three, and then cycle again, one, two, and three, and again, infinitely, until
I make some stock condition. So that's the cycle method. And now as a last thing, the
repeat methods, so repeat, for i in repeat, and then I want to repeat,
for example, just the one, then this will simply make an infinite loop. And we'll print
one. And I can also as a second argument, give it the stop repetitions. So how many
times do I want to repeat for example, if I say four, here, then it will repeat the
one four times. Lambda function is a small one line anonymous
function that is defined without a name, and it looks like this. First it has the lambda
keyword, then it can take some arguments, then a colon, and then an expression. And
what this will do, this will create a function with some arguments, and it evaluates the
expression and returns the result. So let's look at an example. To make this clearer.
Let's call a function and we call this add 10. And this is equal a lambda with an input.
And let's call our input x. And then it should evaluate x plus 10. So this will create a
function with one argument, and it adds 10 to the argument and returns the result. And
we assign this function to our var variable at 10. So now this is a function that we can
call with an argument. So let's call it with with five. And now if we print this, then
it will print 15. So this is practically the same as a normal function like this, let's
call this at 10. func, and this will take an argument x and return x plus 10. So these
two things do the same thing. But the lambda function is much shorter and only in one line.
So lambda functions can also have multiple arguments. So let's say let's create a another
lambda function and call this mouth and and this is equal lambda. And now we give it x
and y. And it should evaluate x times y. So this will create a function with two arguments,
and it will multiply these two arguments and returns the result. So now if we print for
example, mod two, and seven, then it will print 14. So that's the lambda syntax. Lambda
functions are typically used when you need a simple function that is used only once in
your code. Or it is used as an argument to higher order functions, meaning functions
that take in other functions as arguments. For example, they are used along with the
built in functions, sorted map, filter, and reduce, and we will have a look at all of
them to make the usage of landac euro. So let's start with the sergeant methods. So
you probably already know this, and I also showed this in my video about lists. So let's
say we have a list. And we call our list points to the, and the list has tuples with two elements
in it. So you can think of this as the x and the y well use of our points. And now if we
want to start this, so let's create a points to the sorted list. And then we can call sorted,
this is built in, so we don't need to import anything. And now we can start our list. So
we want to sort points 2d, and now print, first, print our points and then print our
points to the sorted. So by default, this will start our, our list by the first argument,
so by the x argument, so one, 510 and 15. But we can also give it a specific rule how
to sort it. So we can say we can give it a key argument and the key equals, and this
should be a function. And as we now know, we can write a function with a lambda in one
line. So we can say, lambda with an argument x. And now let's say we want to sort it by
the Y Well, you so by the second index, so then we say, x of the index one. So now if
we run this, then we will see that our list got sorted according to the Y index. So what
this does is, you can also, for example, give it a or define a function, and let's say sort
by y and then give it a index. And in this, give it an argument, and in this case, two
argument is a tuple. And then it returns the first index. So now we can also use this function
here, so sort by y, and if we run this, then this will return the same result. But now
we see with a lambda, we don't need this. And then we can simply use our lambda here,
so we can use our lambda here, and get rid of this function. And yeah, that's one use
case of a lambda. For example, let's make another example of sorting. Let's sort this
according to the sum of each. So therefore, we would say lambda x and then evaluate x
of index zero plus x have index one. Now if we run this, then we see that it got sorted
according to the sums of each tuple. So that's the sergeant's method with a lambda is key
argument. Now let's talk about the map function. So the map function transforms each element
with a function. So it looks like this. It has a func, a function as an argument, and
then a sequence. So this is for example, a list. So let's create a list with some numbers
in it. So 123, and four, and five. And now let's create a another list and call this
B equals and now we will want to multiply each element by two. So let's say map. And
then as a function, we define a lambda with an argument and evaluate x times to and then
as a second argument, we use our list. And then we print this and if we want to print
this, then if we simply print it like this, then it will print a map object. So we have
to convert it to a list first. And then we can see that each element got
multiplied by two. So that's the map function. However, you can achieve the same thing with
list comprehension. So you probably already know the list comprehension syntax. It's a
little bit easier. So you can write it like this. C equals And then let's say x times
two, for x in a. Now, if you print this, then this will do the same thing. So, personally,
I would prefer this syntax, it's a little bit easier. But you should have heard about
the map function. Now the second function is the filter function. So the filter function
also gets a function and a sequence. And it will, this function must return true or false.
And the filter function will return all elements for which the function evaluates to true.
So let's say let's also give it a six. And let's say we want to filter this. And we say,
we equals filter. And let's say in this example, we only want to have the even numbers. So
then we create a lambda with x, and we evaluate x modulo two equals equals zero. And then
if we run this, we should get only the even numbers. So again, here, we can achieve the
same thing with list comprehensions. So we can also write C equals a list and then inside
our list, we write x for x in a, and then we can give it a condition, we can say, if
x modulo two equals equals zero. So we print C, then we see that this will do the same
thing. And as a last function, I want to show you the reduce function. So the reduce function
also takes a function and a sequence. And it repeatedly applies the function to the
elements and returns a single value. So let's say I have a list here. And I want to compute
the product of all the elements. So let's call this product, product A equals and then
I can say, read us. And in Python three, I have to import this now. So I have to save
from func tools, import reduce. And then I can come call the reduce function. And as
a first argument, I give it a function. So I define the function here, again, in one
line with a lambda i say, lambda, x, and now it has two arguments here. So function, the
function for the reduced function always has two functions has two arguments. So let's
say x and y. And then it should evaluate x times y. And as a sequence, I gave it a, so
let's print the product. So this will print. Let's make this example smaller, then we can
see it has one times two equals to two times three equals six, and six times four equals
24. So yeah, that's the reduce function. And that's all I wanted to show you about lambdas. A Python program terminates as soon as it
encounters an error, and an error can be either a syntax error or an exception. So in this
tutorial, we will have a look at what's the difference between a syntax error and an exception?
What are the most common built in exceptions? How can we raise and handle exceptions? And
how can we define our own exceptions. So let's start with a syntax error. a syntax error
occurs when the parser detects a syntactically incorrect statement. So for example, if I
write a equals five, and then in the same line, I want to print this, this will raise
a syntax error because I have no I have to use a new line here. So this will be fine.
Or a syntax error can be for example, missing or too many parentheses. So if I try to run
this now, this will also raise a syntax error. And now exceptions. So even if a statement
is syntactically correct, it may cause an error when it is executed. And this is called
an exception error. There are several different error classes, for example, trying to add
a number and a string will raise a type error. So, if I say A equals five plus, and then
as a string, I write the 10. And now, if I run this, then this will raise a type error,
unsupported operand types for plus int and string. So, this is a type error. And now
let's talk about some more common built in exceptions. So, of course, there is the import
error. So if I say import, and then some module that does not exist, then this will raise
a module not found error, which is a subclass from the import error. This is a common exception,
then there's the name arrows. So let's say if I have a variable A equals five, and another
one, b equals C, and C is not defined yet. So, if I run this, then it will raise a name
error name C is not defined, then there's the file not found error. So, let's say I
want to open a file f equals open and then the file is called some file dot txt. So if
I try to run this, then I will get a file not found error, no such such file or directory.
Then there's the value error. Which happens if the function or operation receives an argument
that has the right type, but an inappropriate value. For example, let's say I have a list
with some numbers here 123. And now I can remove elements from a list with the dot remove
method. So I say a dot remove one, so this works fine. So now, I print A, and the one
got removed. And if I try to add the two, remove the four, which is not in the list,
and this will raise a value error, so list, remove x x not enlist, then there's the index
error. So if I want to access an index of a sequence, or of this list, that is not that
is too large. So for example, if I try to access the index for, then this will raise
an index error list index out of range. And if I have a dictionary, so let's say I have
a dictionary with and name, and the name is Max, and it has only the key value pair have
the name, and I want to access for example, I want to access the age then this will raise
a key error because the H key is not inside my dictionary. Now let's talk about raising an exception.
So if you want to force an exception to occur when a certain condition is met, then you
can do this with the race keyword. So let's say we have a variable x equals and minus
five. And then we say if x smaller than zero, then we want to raise an exception and then
we say, race and then we raise simply the base exception, and as message we give it
x should be positive. So now if we run this, then this will raise this exception x should
be positive. And now if we get given a value of larger than zero, then no exception will
be raised. As a second way you can use the assert statements so you can say. You don't
use an if statement. So you use an assert statement. So you say assert, and then a condition
and the search statements will will throw an assertion error if your assertion is true.
Not true. So if you write here, you make an assertion that x should be larger or equal
to zero. And now if we run this, then this will raise an assertion error, we can also
give it a message here. So x is not positive. And now this will print the message here.
And if our statement is correct, so x is positive, then your code will be just fine. So if I
ran this, then no assertion is here. Now if you want to handle exceptions, so you can
catch exceptions with a try except block. So you write for example, you write try and
then call on and then you can do some operations. So let's say I want to try a equals five divided
by zero, and this will raise an error. So let's simply run this and show you what happens.
So this will raise a cirro Division error, because division by zero is not allowed. So
what I can do, then I can make a try except blocks. So I will try this statement. And
then I can write except, so if an exception is raised, then the code will continue here.
And then I can simply print and our are happened. So if I run this, then your program doesn't
stop here, it will continue and it will continue in this line. And you can also catch the type
of exceptions so you can ride except exception as E. And then you can print your exceptions.
So you can if I run this, then it prints the division by serial message from the zero division
error class. Now, it's good practice to specify the type of exception you want to catch. And
therefore you have to know the possible errors. So for example, if you know that this is a
zero division error, you can simply write or you should write, except zero division
error. And then you can do something here. You can also for example, use multiple statements
here. So you can try multiple operations. So let's say we want to try five divided by
one. So this is this is fine. And then we say B equals a plus and then a string. So
we we've already seen this, so this will race. So let's this will raise a type error. So
let's print here. So let's catch this acceptive zero division error as E and print E. And
now we also want to catch a type error. So then we write type error as E, and then we
can also print this. So now if we run this, this will catch the Type error and prints
this message, unsupported operand types for float and string. And now if this fails, then
this will be catched here, and then this match message gets printed. So now we have division
by zero. That's how you can handle exceptions. Now you can also with a try except block you
can also have a else clause. So an else clause is run if no exception occurred. So here I
print. Everything is fine. And now, if I, for example, make divide by one that's fine,
and I want to say a plus four, that is fine. And then the code continues in the else clause.
And I also can have a finally clause. So the finally clause runs always No matter if there
was an exception or not. And this is, for example, use to make some cleanup operations.
So here we print Li cleaning up. So now if you run this, then the else clause runs, and
the finally clause runs. And if there is an exception, for example, this, then this line
is running. And again, the Finally, clause also is running. So yeah, that's how you can
handle exceptions. Now, as the last thing, let's talk about how we can define our own
exception. so we can simply define own error classes by sub classing from the base exception
class. So we can say for example, class value, too high error, and typically, you want to
give your class a name with an error at the end. So the class value to high error. And
then as a base class, we use the exception class, and then we can simply say pass. So
this is already a valid, defined exception error. So now, we can say, let's write a small
function, test value with an input. And now we say if x is larger than 100, then we can
raise this value to high error. And by default, it can also have an error message. So we say
value is too high. And now if we run our method with an argument of, let's say, 200, then
we will see that this will raise the value to high error, so value is too high. And now,
for example, we can use a try and accept lock. So let's say try test value, and then accept
and catch the value to high error. And then we print or let's print the era as E. And then print, let's catch the value to high
era E and then print E. So then, we will see that the message gets printed here. And usually
what you want to keep this classes small, but you can write it like any other class.
So you can, for example, let's make a value to small error. And also, as a subclass. It
has a sub base class, it has the exception class. And now you can, for example, define
a custom in it method. So it has the self argument, and then we give it the message
and value. And then we can store this variables here. So we can say self dot message equals
message, and self dot value equals value. And now, inside our test function, we make
another if statement. So let's check if x is smaller than five, and then we want to
raise a value to small error. And now we have to give it the message. So the message is
value is too small. And then as a value, we give it the x. And now if we catch this, then
we want to catch the value too small error as E. And now we have the information about
the error so we can print e dot message, and we can also print e dot value. So if I test
my function with one, then the value to small error will get raised. So I'm sorry, I'm Miss
colon. I forgot the column. So then, um, It catches the error here and prints all the
information that I defined here in my error class. Python already comes with a powerful
built in logging module. So you can quickly add logging to your application by simply
saying import logging. And then you can use this. And in this tutorial, we will have a
look at the different block levels, the different configuration options, how to lock in different
modules, how to use different lock handlers, how to capture stack traces in your log and
how to use rotating file handler. So let's start. So after importing the logging module,
you can import to five different, you can lock to five different log levels. So let
me copy this here. So the levels are debug info, warning, error and critical. And they
indicate the severity of the events. So if I run this, then we will see that only warning
error and critical are printed. And this is because by default, only levels of only messages
with level warning or above are printed. And if we want to change this, we can do that
by setting the basic configuration. And usually we want to do this right after importing them
logging module. And then we say logging dot basic config. And then we can specify some
arguments here. And for this, I would have a look at the documentation. So in the official
Python documentation, you find the different arguments for the basic configurations. So
for example, you can set the level and the format, and then the date format. And so in
this case, I said level two logging dot debug. And then for the format, I give a string,
and inside this string, I can specify this lock record attributes. So for
example, I can have the name being locked. And I do this by saying or by writing percent,
and then in parentheses, name and then S. Or I can say the ASC time, so this locks the
time, then the level name and the actual message. And then I can specify how the time should
be locked by saying date format equals and then give a string for the date format. And
for this, I can also have a look inside the documentation. So here are the different formatting
rules for how to lock the time. So for example, percent m will look the month, then the day
then the year, then the hour, the minute and the second. And now if I run this, then we
see our new format. And also that debug info, warning, error and critical are all locked.
And by default, our logger is called the root logger. So that's because the name here is
root. Now, if I want to log in different modules, then it's best practice to not use this root
logger. But create your own logger in your modules. So let's say we have a helper module
here. And what you do then, after importing, logging, you create your own internal logger
here by saying logger equals logging dot get logger. And then you give it a name. And it's
also good practice to use this double underscore and then name global variable. So this will
create a logger with the name of the module. So it's called helper in this case, and then
you can use this logger to lock something so as for example, say logger dot info. Hello
from helper and then in your main module. After importing, logging, and setting the
config, then for example, if I import this helper module, then it will lock the message
from the helper module with the name of this logger. So it's good practice to create your
own logger in your modules with this get logger function and then give this name with double
underscores here as a name. Now, if I create this log on here, then this will create a
hierarchy of loggers. It starts at the root logger, and all these new loggers get added
to this hierarchy. And they propagate its messages up to the base logger. So now if
I don't want to have this propagation, I can say logger dot propagate equals false. So
by default, this is true. And now this will not propagate to the base logger. And now
for example, if I run this module and import the helper module, then nothing gets locked
here because it doesn't propagate to our base logger. Now let's talk about lock handlers. So handler
objects are responsible for dispatching the appropriate lock message to the handlers specific
destination. So for example, you can use different handlers to send log messages to this standard
output stream to files via HTTP or via email. And ba you let me show you how you set different
lock handlers. So first, we create a our logger in our module by saying logger equals logging
dot get logger, and then the name of this module. And then I want to create my handler.
And let's say I want to have a handler that locks to this stream. So a stream handler.
And then this equals logging dot stream handler, though this is a built in class. And I also
want a file handler that locks to the file. The file handler equals logging dot file handler,
and then it needs a name. So let's say our lock file is called file dot lock. And then
typically, for each handler, you want to set the level and format. So we say, stream handler
dot set level, for example, set this to logging dot warning. And for the file handler, the
file handler should only lock method messages of level logging dot error. And now we also
specify some format. So we say, a stream format equals logging dot format. And then inside
here, we give it a string, just with the, the same like with the basic config. So let's
say we want to have this string here. So we want to have the name of the logger and the
level name and the message. And all let's also set the file handler to this format,
or just call this format. And then First, we set the formatter to our handler. So we
say stream handler, dot set format, formatter, and also we say file handler, dot set formatter
formatter. And then at the end, we have to add our handler to the logger. So we say logger
dot add handler. And first we want to add the stream handler, and then logger.at handler.
And now we want to add the file handler. And now if we use this logger and lock something
for example, say logger dot warning, and then we want to say this is warning. And now we
also want to have logger dot erawan and Block, this is an error. And now if we run this,
what will happen is in our stream, we have warning and error, because our stream handler
locks, messages of level warning and above. And then also file handler locks to a file.
So now if we have a look, in our folder, there is now this file lock. And this only has the
error message. So this is how we can define the front lock handler. Now let's talk about other configuration methods. So we've already
seen the basic config method. But we can also use the file config or dict config method.
And for this, you will create a file in your folder. And you specify it with this syntax.
So you call it logging.com, or logging dot ini. And then you define the loggers, the
handlers and the formatters. So in this case, we define two loggers with these names, one
handlers, and one format. And then you specify each of these further. So you say logger,
and then underscore and then the name of the logger. And then you give it its arguments.
So let's say for example, we have a logger called simple example. And this should lock
to level debug and above. And it should have a console handler. And then we come and we
define the console handler, and this is a stream handler with this formatter. And then
we define this formatter and give it the format. And now if we want to use this config file,
then in our file, we say, import logging, dot config. And then we can call logging dot
config dot file, config, and then give it the file name. So we say logging.com. And
now what we can do is we can create a logger with for example, with this name. So this
will get the simple example logger. So let's say logging dot get logger. Simple example.
And now if we lock something with this logger, say logger dot d back, because it also locks
debug, this is a D block message. And now if we run this, then we see we have the message
here with this format, the time then the name, the level, and the actual message, just like
we defined here for our formatter. And we can also use a dict config, but I won't cover
this now. So for this, you should also have a look at the documentation. So the config
is just a different syntax that you can use. And then you would right here, logging dot
config dot dict. config, and set this config from a dictionary. So with this two methods,
you don't have to hard code your configuration in your code. But you can use a separate file
that you can easily change without changing the code. So yeah, remember that you can also
use these tickets and file conflicts. Now let's talk about capturing stack traces in
your lock. So this can be very helpful for troubleshooting issues. So let's say you have
a, you run a code that raises an exception. So let's say we have
a list with some values 123. And we want to access a value, but we use an index that is
too large. So this will raise a index error. And we can catch this by saying except index
error as E and then we can say Logging dot error. And by default, this will only now
lock the error message live index out of range. But if we also want to lock the stack trace,
then we can set the argument e xe info equals true. So e xe underscore info equals true.
And this will also now if you run this, this will also include the stack trace in our logger.
So now we can see that trace back and the line where our exception occurs. And yeah,
so this is helpful for troubleshooting issues. And now let's say we don't know what kind
of error we raised. So let's say we just say, accept and catch everything. But we still
want to have our trace back, then we can import the trace back module. And we can, for example,
look a string. So the error is, and then we use string formatting. So we say percent s,
this is a placeholder. And then here, we call this trace back dot format, e x c method.
So this will now if we run this, then this will do the same thing. Basically, this will
also print this measure message to the lock the error is, and then includes the trace
back. So let's talk about rotating file handlers. So let's say you have a large application
with a lot of log messages, and you want to keep track of the most recent events, then
you can use a rotating file handler that keeps the files small. So for this, let's say, we
also have to import this. So we say from logging dot handlers import rotating file handler,
then, let me quickly copy this here. You create your logger. Here, you set level. And then
here, you create your file handler. So your handler is now a rotating file handler. And
then you give it the name of the lock file, then the max bytes. So this means that after
two kilobytes, it will roll over the lock to another log file. And it will also keep
five backup counts. And then we add our handler to the logger. And then for example, we log
a lot of messages. So we say, for underscore, this means that we don't care about this.
So for underscore in range 10,000, we look HelloWorld. And now if we run this, we see
that in our folder, we now have different log files, all with this Hello, world message.
And now if we also have a look at the folder, then we see that each of these files is two
kilobytes and after two kilobytes, it gets rolled over. So this is how you can use a
rotating file handler. And sorry, now, let's say your application will be running for a
long time, then you can use a time rotating file handler. So for this you say from logging
dot handlers import, timed rotating file handler, and this will create a lock, a rotating lock
based on how much time has passed. So what we will do here is we also create a our
handler that is now a timed rotating file handler. And then the name of the lock and
then we say when should it roll over. And therefore we can give for example, we can
give an S for seconds, an M for minutes, an H for hours, then a D for day. We can also
say midnight, or we can give it the weekdays. So, W zero means Monday, W, one means Tuesday,
and so on. So in this case, let me rotate this every seconds with an interval of five.
So every five seconds, a new file gets created. And we keep a backup of five files. So now
if we say, for example, for underscore in range, let's say six, and then we want to
lock something. And after this, we want to wait a specific amount of time. So let's import
time, and then say time dot sleep. And then we want to sleep five seconds. So now if we
run this, we see that our lock file got created. And now if some time has passed, so after
five seconds, another log file got created with this timestamp. And then after five seconds,
again, another, and so on. So this is the time rotating file handler. And as a last
thing, I want to mention that also if you have a lot of different modules, and lock
many, many different things, so especially if you use a micro service architecture, then
I would recommend to use not locked to this simple messages, but use the JSON format for
logging. And for this, I would recommend this open source, Python, Chase and logger. So
you can find this on GitHub. And you simply install it with pip install Python, Chase
and logger. And then you can define this format, and add this formatter to your handler. And
then you log in JSON format. Jason is short for JavaScript Object Notation. And it's a
lightweight data format that is used for data exchange. It's heavily used in web applications,
so you should be comfortable working with it. Luckily, Python already comes with a built
in JS module that makes working with JSON data very easy. So in this tutorial, we will
have a look at how we can encode and decode JSON data with this module. So let's dive
into it. And first of all, let's have a look at how Jason data looks. So here I have this
example file called example dot Jason. And here we see that chastened data looks very similar to
a dictionary. So it consists of several key value pairs. And as values it can take strings
or numbers, or Booleans, are also nested types, like here, a nested array or a nested dictionary.
And we can also have a look at the whole conversion table. And by the way, you can find this on
my website, Python minus engineer.com. There you can find written tutorials to all my other
video tutorials. And this is how Python is translated to chase and vice versa. So a dictionary
in Python is an object and chasen list and tuples are an array is string is a string,
integer, long and float are a number and chasten. True and False are also true and false, but
with a lowercase and none is now in Chase. And so these are all the conversions you have
to know. And let's start working with it. So let's say we have a Python dictionary and
want to convert it to a JSON format. And this is also called serialization or encoding.
So let's say let me copy this here. Let's say we have a dictionary called person. And
this has a name, an age, a city, a Boolean, if it has children, and then titles and this
is a nested list. And let's say I want to convert this to a chase an object. So first
of all, I have to import the chasen module. And then I can say, if I want to have this
in chasen format, I can say person chase Then equals and then I use this module and I use
chasen dot dump s, and then the person. So this will dump our object to a JSON string.
Now, if I print this, print our person and chasen then I will see that this is now in
chastened format. And we can see this, for example, because false has a lower case. Now
I can also specify an indent here. And I would recommend setting this indent to four. And
now this has a nicer format. I can also specify different separators. And this is a tuple
with two values. So here I can specify different separators. So instead of a comma here, I
use a semi colon in the space. And instead of a colon in the space here, I want to use,
let's say, an equal sign and the space. And now if I run this, then we can see that different
separators here, but I would not recommend using different separators, but instead use
the default ones. But what's also helpful argument here is to use this sort keys argument
and set this to true. So by default, this is false. And now if I run this, then we see
that our keys are sorted alphabetically. So this is how we can convert from a Python dictionary
to a JSON object. And in this case, to a string. Now, I can also convert it or dump it into
a file. And for this, I can say, I open a file. So let's say with open and let's call
our file person, the chasen and I want to open it in right mode, open it as file. And
then I can say chasen dot DOM, not dump s because S stands for our string, I want to
dump into a file. So let's dumb and I want to dump the person object into our file. So
now if I run this, then we see that this file got created in our folder. And this contains
our JSON data here. So for example, I can also specify the indent here, let's say indent
equals four, and run this and have a look at our file again, then we see that it has
not a much nicer format. So this is how you convert from Python object
to JSON data. And let's say we have chastened data and want to convert it back to a Python
object. And this is called D serialization or decoding. So let's say I have our person
in JSON format here and I want to convert it back into a dictionary and I will say person
equals Jason dot load. So in this case, I want to load from a string and then I will
give it the person chasing and chasing and now if I print our person again, and don't
print this, then we will see that now we have a Python dictionary again, because here we
can see that false is written with an uppercase. So this is how you convert from a chase string.
And, like before, if you want to convert from a chasen file and you use the chasen dot load
method. So for this we have to open our file so we still have our person dot chasen here
in our folder. So we say with open and let's open this file person, that chasen and now
we want to open it in read mode as file and then I want to I can say person equals chasen
dot load from our file. And then I can print this and if we run this, then we see that
this does the same thing. So this is how we can decode chastened data. And now, in this
case, we work with a dictionary by Let's say we have a custom object. So let's say we have
a custom class, let's call, let's create a class called user. And our user has two instance,
variables. So let's say it has a name and an age. Let's say, self dot name equals name,
and self dot age, equals age. So now let's create a user object user equals user. And
let's say the name max and the age 27. And now let's say I want to have this in JSON
format. And like before I call chasen dot dump. So dump from a string, dump as a string.
And I want to dump the user. Now if I run this, then this will give a very long error
here. And at the end, it says type error object of type user is not chased and serializable.
So what I have to do, I have to write a custom encoding function, and this is not very long.
So let's say let's create a function called encode. user. And this will take a object. And inside
our function, we check if our object is of with this is instant method. So this will
check whether an object is an instance of a class. So let's check if our object is of
class user. And if so, then we will return a dictionary with all the instance variables
as key value pairs. So let's say it has the name. And this equals it, this is our object
dot name. And then it has the key h with the value object dot h. And then as a little trick,
it will get also the class name as a key. So I can say, object dot with double underscores
class and then dot double underscores name. So this would give the name of the class as
a string. And then as a value, the value doesn't matter. So I simply put in true. And otherwise,
I will raise a type error. Let's raise a type error and as a string or message, I will put
this same message here. So now this is our custom encoding function. And now in our dump,
or dump s method, I give it that as a default argument. And now here, I use this encode
user function. So this now we'll use this function for how to encode the object. And
now if I run this, then this worked. So now I can print our user dot JSON. And now we
see that we have our dictionary with the name, the age and the user class is key with value
true. So this is how you encode a custom object with this default argument. And then there's
also a second way so you can implement a custom chasen encoder. So let's say we import from
Chase. We import the chase and encoder. And then we create a class called this user and
coder. And this is derived from this base chazan encoder. And then we override this default method. So let's say this is called
default and this takes self and an object here. And then inside we do the same thing.
So we check if our object is of the Last user, and then we'll we will return this dictionary
with the class name in it. And otherwise we will we let the base chasen encoder handle
it. So we say return chasen encode our default, self and object. And now in our dump or dump
s method, I can give it a class argument as a class. Now I use the user in encoder and
not the bass chasing encoder anymore. So now if I run this, then we see this also worked.
And as a last option, now I can use this encoder directly. So I can say user chasen equals
user encoder. Now let's create a user encoder. And then I can say dot encode our user. And
now if I run this, then we see this also worked. So this is how you encode custom objects.
Now let's say I want to decode our object back. Let's say I have here our user in JSON
format. And I want to have it in a normal Python object. So I can say user equals Jason
dot load s. And then I will give this user chase in here. Now, if I run this, then this
worked. So I can print the user. And now we see we have a dictionary here. So we don't
have a user object. So let's check the type of this user, then we see that this is a dictionary
here. So for example, I cannot call user dot name, because it's not a user object. But
what I have to do if I want to decode this into a user object, I also have to write a
custom decoding method. So let's call this decode user. And this will get a dictionary.
And now, inside this function, we check if our dictionary contains the user key. So now
here, in in our encoding function, we added the user class name as a key. So now, in here,
we check if this key is in our dictionary. So let's say if user dot double underscore
name in dictionary, and then we will create and return a user object. So let's return
a user. And as name, it will get the name from our dictionary, so name equals dictionary
and then the dictionary with the key name. And as a age, it will get the age of the dictionary.
So age equals Dictionary of age, and otherwise, it will simply return the dictionary. So then
still, the decoding will work but it will be decoded into a dictionary. And now we have
to use this custom decoding method. So we can say in our chasen dot load or chasen dot
load s method, we can specify an argument that is called object hook. And now we set
this object hook to our decoding message. And now if we run this, we see that we have
our user object so we can let's print that type of user. And then we see that this is
now a class user. And we can access its instance variables for example, user dot name, and
here it prints max. So this is how you decode custom objects. So Python comes with different built in modules
to generate random numbers. In this tutorial, we will have a look at the random module for
pseudo random numbers, the secrets module for cryptographically strong random numbers
and the NumPy random To generate arrays with random numbers. So let's start with the random
module. And first of all, we import random. And this is used to generate pseudo random
numbers for various distributions. And it's called pseudo random because the numbers seem
random, but they are reproducible. And we will see how we can reproduce the data in
a second. But first of all, let's have a look at the different functions. So the easiest
one is random dot random. So let's say a equals random dot random, this will print a random
float in the range from zero to one. So let's print a. So this is a random float in the
range from zero to one. Now, if you want to have a specific range, we can use random that
dot uniform and give it a start and a stop. So let's say our range is from one to 10.
Now this will produce a random float in this range. Now if you want to enter chess, we
can use random dot Rand int, and give it the range. And if we run this a couple of times,
hmm. Now it's not happening. But this range will actually now we got it, this will include
the upper bound, and you might expect a behavior with this is not included. So for this reason,
you can use the RAND range method, so this will do the same thing, it will pick a random
integer in this range. But here the upper bound is not included. So this will never
pick the 10 here. Then there's the random dot normal variate function with a mu and
a sigma. So let's give it zero as mu and one sigma. And this might be useful if you're
working in statistics. So this will pick a random value from a normal distribution with
a mean of zero and the standard deviation of one. So let's have a look at how this normal
distribution looks. This is the normal distribution for different means and standard deviations.
So in this case, we use zero and one, so we have to have a look at the red line. And this
will pick a random value somewhere in this range where our red line is not zero. So this
is the random normal variant. Now the random module also comes with different functions
to work with sequences on let's say, we have a list and call it my list equals. And let's
create a list with different characters. So if we print this, we will see that each character
is now a element in our list. And for example, now we can pick a random choice. So let's
say a equals random dot choice from our list. And print this so this will pick a random
element. Now if you want to pick more elements, we can use random sample and give it the number
of different elements we want to pick. And this will pick unique elements. So it will
for example, never pick a twice. And if we want to have a behavior where elements can
be can can be picked multiple times, we can use the random dot choices method. And here
we have to use k equals three. So this will do the same thing, but now we see it can pick
elements multiple times. Then there's also the random shuffle methods.
So let's say random dot shuffle our list so this will shuffle a list in place. Now if
we print this, then we see that the elements are now shuffled. So these are the most common
functions to generate random numbers. Now are Yeah, I said that these are pseudo random
numbers, because they are reproducible. And you can do this with the random seed method.
So I can say random dot seeds and give it a value here. So let's say for example, one,
and then I can do different random operations. So let's say I want to have, I want to print
random dot random. And I want also want to print some random integer. So let's say random
dot Rand int in the range from one to 10. Now, if I run this, this will produce some
random numbers. And then I can reseed again with this value with the same value here,
one, and then do the same set of operations. And now if I run this, then we see that these
are now exactly the same numbers here. Now, I can also, for example, now I can see it
with a different value here, let's say two. And then I do this operations. And then again,
I will see it with one and do these operations. And at the end, I will seed with two again
and do these operations. And then I run this and now let's have a look. And now we see
that all our operations are our random numbers, with a seed of one are now the same, and then
all, where I use the seeds to all these random picks are now the same. So this is how you
can reproduce your data with this random seed functions. And because these numbers are reproducible,
they are not recommended to use for security purposes. And for this purpose, you should
use the secrets module. So we can use import secrets. And this only has three functions.
And they should be used for things like passwords, or security tokens or account authentication
things. So for all these purposes, you should use the secrets module. The disadvantage is
that it's it takes more times for these algorithms, but but they will generate a true random number.
So and it only has three functions. So the first one is secrets dot ran below. So let's
say a equals secrets dot ran below, and then it has an exclusive upper bound. So this will
produce a random integer in the range from zero to 10. And 10 is not included. Then you
have the secrets dot random bits method. So this will return an integer with K random
bits. So for example, let's give it four bits. Now, if you you're familiar with bits and
bytes, so for example, here for bits means that it can has four different random random
binary values here. So the highest possible number here, and this case would be 1111.
So this is 15. So this is two to the power of three, which is eight, then this is two
to the power of two, which is four. So eight plus four plus two plus one equals 15. So
this will generate a random number in the range from one to 15. No from zero to 15, sorry. Then you also have
a secrets choice method. So let's say I have a list. My List equals list and with some
characters in here, and then I can use A equals secrets dot choice and my list. And this would
pick a random choice that is not reproducible. So this is the secrets module. And now if
you're working with arrays, then you can use the NumPy module. So if you have not installed
it, just use pip install NumPy. And then you can say import NumPy as NP. So usually, you
will do it like this. And then you can say, for example, you want a array with random
floats, then you say A equals NumPy dot random dot Rand, and then give it the dimensions.
So in this case, I will put in three here. So this will produce a 1d array with three
elements in a year. So three random floats here. Now, if I can, I can also use more dimensions
here. So I can three, this is now a three by three array. Now if I want to have random
integers in a range, I can say, Rand end and give it the range from, let's say, zero to
10. And here 10 is excluded. And then I can give it this size. So let's say also size
three a 1d array with random integers. Now, if I want to have a array with higher dimensions, I have to use
a tuple here, so I can use a tuple and say, three by four, for example. So this will create
a three by four array with random integers. Then, this will also have a random shuffle method. So let's say I have a NumPy array
with different dimensions. Now print this array. And then I can say NumPy, dot random
dot shuffle, and then our array and now print the array. And this will only shuffle the
elements along our along the first axis. So this will never switch elements in between,
but only switch elements in the first axis. So this is the NumPy random module. And one
important thing you have to know is that the ret NumPy random generator uses a different
number generator than the one from the Python standard library. And it also has a different
seat. Well, seat functions, so I can also say NumPy dot random dot seeds, and then give
it a value, let's say one. And then I can do some operations. So let's say NumPy of
print this print NumPy dot random dot Rand three by three. And then I can receipt and
do the same thing. And this will now reproduce the same, the same array. And the important
thing is that you should use the NumPy random seed method instead of the seed method from
the random module that we've seen previously. So these are two completely different seed
generators. decorators are a very powerful tool in Python and every advanced Python programmer
should know it. In this video, I show you the concept behind decorators how you can
write your own decorators the difference between function and class decorators and some typical
use cases. I promise you that once you have understood the concept, it is not as difficult
as it seems in the beginning, and it might improve your Python knowledge a lot. So let's
start there are two different decorators, function decorators and class decorators.
more common is the function decorator and it looks like this. So you have a function,
call it def. And let's say call it do something. And that does nothing in this case. And above
your function, you have an add sign, and then some other function name. So some decorator
function name, let's say my decorator. So this is how the decorator syntax looks. And
what this does a decorator is a function that takes another function as argument and extends
the behavior of this function without explicitly modifying it. So in other words, it allows
you to add new functionality to an existing function. So in this case, this function would
be extended with the functionality of this decorator. And in order to understand this
concept, we have to know that functions in Python are first class objects. This means
that like any other object, they can be defined inside another function passed as an argument
to another function, and even returned from other function. So now, let's have a closer
look at the concept. So let's say we want to, we have a function and call it print name.
And this will simply print LX. And then we have a decorator function, call it start and
decorator. And now as an argument, it takes a function. And inside our decorator function,
we have an inner function called and we call it wrapper. So def wrapper, this is a wrapper
function, which, which reps our function. So he inside this wrapper function, we execute
the function. And then as I said, I can extend the behavior. So I can do something before.
And I can do something after it. So before I say in this case, simply print start, and after it, I want to print and,
and then after creating this inner wrapper function, I also have to return it. And now,
to apply this, let's first of all, simply, let's execute the print name functions. And
if I run this, it prints LX. And in order to apply the decorator, I assign this print
name function to now to our decorator function. And as argument I take the print name function.
So now the print name function has this new functionality. So now if I run this, we will
see that it prints Dart then executes the function and prints Alex and then it prints
and, and now the decorator function will do the same thing as this line. So now if I write
at start, and decorada, then I don't need this anymore. So this now that's the same
thing. If I executed now, it will also print start Alex and end. And now this is how we
can extend the behavior of a function with a decorator. So let's see what happens if
a if our function has some arguments. So let's say we have a function, call it at five and
this takes an argument and then it returns X plus five. And now if I tried to run this
at five and this argument I give 10. Now, if I run this, I will get a type error because
our wrapper takes zero positional arguments, but one was given. So here, I need the same
arguments is here. And to fix this, I can use the arcs and quarks. I will talk about
this in another video in more detail. But basically with this syntax, I can use as many
arguments in keyword arguments as I want. And now inside our wrapper function, I also
call this function with the arguments and keyword arguments. So let's write it like
this. And now if I execute it, then it works. So this is how you apply arguments. And now
what about the return value, so let's store this in a result. So let's say result equals,
add five, and then print the result. Now, if I print this, this will print none here.
And to fix this, I also have to save the result of the function here, and then return it from
my inner wrapper functions return result. And now if I run this, it can print the result.
And now I said last thing, what about the function identity. So let's print the help
function of f5. The health information with this help function, and also let's print the
name of this function with this double underscore method. Now, if I run this, this will print
that help function wrapper, and the function name is also wrapper. So Python got confused
now about the identity of this function. So in order to fix this, I can import func tools.
And here before my wrapper, I apply also a decorator. That's called func tools, dot wraps,
funk. So this will now preserve the information of my used function. So now if I run this,
I see that it now knows the help on function at five. And also, our function name is now
again, at five. So this is all to complete the decorator funk decorator syntax. So this
now is a template for a decorator that you can use
for all your function decorators. So let's say call it my decorator, then you can do
something before the function, then you execute the function. And then you can do something
afterwards. And then you return the result and return the wrapper. So this is the template
for a nice decorator. And you can also have a look at this on my website, Python minus
engineer.com. Yeah. So now, as we've seen here, we see here a decorator that takes a
function that takes an argument so decorators can also take arguments. And what this means
this is basically now two inner functions, so an inner function within an inner function.
And to make this clearer, we'll look at another example. So let's say we have a function,
call it greet, and then it takes a name. And then inside it will print. And now we use
an F string. And I've shown this before in another video about strings, so we can say
hello. And then inside braces, we use the name. And now we use a decorator and call
it repeat and give it an argument num times and set it to three. So I want a repeat decorator
that executes this function three times. So how does this decorator now look? First of
all, we have the outer function repeat, which also takes num times and then inside it takes
out decorator function as we've seen it before, so we have a Decker Ray down. So define a
decorator. Repeat. And this takes a function. And then inside here we have our wrapper.
And this takes arcs, and Clark's, and we decorate this with our func tools dot wraps Decker
decorator. And then inside our wrapper, I simply want to repeat this the number of times
I've given here. So I say for underscore, because I don't need this for underscore in
range num times. And then I say result equals our function with the arcs and the quarks.
And then I return the result, then I return the wrapper, and then I return that decorator.
So now if I execute greed LX, then this will be executed the number of times I've given
here. So now if I say executed four times. So this is how the concept behind decorators
with arguments work. And now let's also talk about nested decorators. So you can stack
decorators on top of each other. So you can, let's say we have a function and call it let
me copy this here. So let's, we have a function, say hello, which gets a name, then it prints
a greeting and returns the greeting. And now we can debug this, we can decorate this with
our start. And decorator as we've seen it before. Now, let me copy this here inside.
This is our start end decorator, which will print start and end after our function. And
we also decorate this with a second decorator and call it D block. And now let me copy this debug decorator in
here. So, this debug decorator extracts the name and the arguments and the keyword arguments
and then it prints the information of this function it executes the function and then
it also prints the information about the return value. So, this will basically print some
more information about this function. And so now if I apply multiple decorators to add
a function, they will be executed in the order they are listed. So this means now if I say
for execute say hello LX this will first of all execute the debug function and then inside
the debug function, it will execute the start and decorator function. And then inside this
function it will execute the say hello function. So now if I run this, we will see that first
of all, it prints calling say hello this is from my Dubuc wrapper. Then it prints start
from the start and decorator. Then it prints Hello LX then ends and then again I'm here
I am prints the function name and the return value. Hello Alex. So this is how you can
apply multiple decorators. And now it's the last thing Let's talk about class decorators.
So instead of a function decorator, you can also define a class decorator. So let's say
we have our function, say hello. And then it simply print. Hello. And I want to decorate
this with a class decorator. And I call this count cause. So class decorators do the same
thing as function decorators, but they are typically used if we want to maintain and
update a state. So in this example, I want to keep track of how many times I have executed
this function. So let's create a class call it count calls. And this has a init method.
And it takes self Of course, and then it takes the function just like the decorator function.
And then inside the in it, I will save the function as class variable, or as member variable.
And I said self funk equals funk. And then I will also create a state. And I call this
self dot num calls. So and this is zero in the beginning, so I want to keep track of
how many times this got executed. And now in order to write a class decorator, I have
to implement the call method. So this also takes self, then the arcs and the quarks.
And this is the same as the inner function in our function decorator. And now, sorry,
this also has trailing double underscores. And now the call methods allows me to execute
a object of this class just like a function. So let's, as an example, let's just print
Hi, there, here. And now, let's say I create a object of this class called cc equals count
calls. And this takes a function here. So this example, I just use none. And now, since
I've implemented this call methods, I can say CC and execute this as a function. So now, if I run
this, it prints Hi there. So in our example, I don't want to print Hi there. So, what I
want to do now, I want to update the state. So I say self dot num calls plus equals one,
then I want to print the number of calls. So, I print this is executed
self dot num sorry self dot num calls times and then now this is my man, I also have to
execute and return the function. So I say return self dot func and now I call the function
with all the arguments and the keyword arguments. And now if I say, if I run this and I say
Say hello, then Oh, sorry, self num calls. Now, if I run this, then I will see this is
executed one times and now if I run this again, then I will see. Now this is executed two
times. So here I could keep can keep track of how many times this is executed. So this
is how you can implement class decorators. And now let's talk about some typical use
cases of decorators. So for example, you can implement a timer decorator To calculate the
execution time of a function, you can use a debug decorator like you've seen before.
To print out some more information about the called function and its arguments, you can
use a check decorator to check if the arguments fulfill some requirements and the depth the
behavior accordingly. You can register functions, like plug ins, with decorators, you can cache
the return values. Or you can add information or update the state generators or functions
that return an object that can be iterated over. And the special thing is that they generate
the items inside the object lazily, which means they generate the items only one at
a time and only when you ask for it. And because of this, they are much more memory efficient
than other sequence objects when you have to deal with large data sets. They are a powerful
advanced Python technique. So let's have a look at some examples. To understand how they
work. A generator is defined like a normal function, but with the yield keyword instead
of the return keyword. So let's define a function call it my generator. And here I can return
or I can yield some values. So here I use the yield statement and yield a value. So
I want to yield one. And then I can have multiple yield statements inside a generator function.
So I can, for example, also yield two, and then yield three. And now I can create a generator
object. So I can say ci equals my generator. And now if I print this, and this will only print that this is a generator
object. And now what I can do, for example, I can loop over this object. So I can say,
for i in ci, and then I print the value. So this will print one, two, and three. And I
can also get the values one at a time with the next function. So I can say value equals
next ci, and then I can print the value. So this will print one, and this will execute
the function and runs until until it reaches the first yield statement. And here, it returns
the value and pauses at this line. So the next time if I want to get the next value,
again with this next function, so again, I say value equals next ci, then it will continue
here and runs until the next yield statement. So it runs until here, and returns to and
pauses here. So if I run this, now, it will, it will print one and two. And if I do it
again, then it will also return and print three. And now what will happen if I try to
run it a fourth time. So now if I run it, this will raise a stop iteration, because
a generator object will always raise a stop iteration if it does not reach another youth
statement. So yeah, this is how generators work. And you can also for example, use them
as inputs to other functions that take iterables. So for example, the built in sum function
takes a iterable. So I can give the generator object here and I can print this. So this
will calculate one plus two plus three equals six. Or I can, for example, use the built
in sorted method and put the generator object here. So this will return. This will create
and return a new list with all the objects in a sorted order. So for example, if I have
it the other way around three to one, and then with this, I can sort it again. And then
it prints one, two, and three. And now let's have a closer look at the execution of a generator
function again. So let's say I have another generator, and I call it countdown, and it
takes a starting number. And then I say first of all I want to print starting. And then
I say while num is larger than zero, I yield, the num. And then I also want to update the
numbers. So I say num minus equals one. And then I create my generator object. So I say
CD equals countdown. And for example, I want to start at four. And now if I, let's first
of all, run this, and notice that this will not print starting here, so nothing will be
executed here. And now the first time, I want to get the first value with, let's say, value
equals next of this countdown generator object. Now if I run this, then now it will start
from the beginning of this function and execute it. So this will print starting, and then
it will run until it reaches the first yield statement. And here, it will return the number
and stops at this statement. So I can also print the value. And then it prints four.
And again, the next time, I want to continue here with again, with this next statement,
let's say print, seed print next CD, then it will continue here, it will remember the
current state, so the current number is four, then it will update the number now the numbers
three, then it will continue in the while loop. And then it stops again, at this line,
and now returns three. So now if I run this, this will also print three, and then again,
it remembers the state and the next time I continue, it will continue from here, and
so on. And again, if I run this a couple of times. Again, if I print next, then it will
also print to and now it will also print one, and now it will raise the stop iteration.
So this is the execution in detail. And now let's have a look at the big advantage of
generators. So as I said, generators are very memory efficient. So they save a lot of memory
when you work with large data. So what this means is, let's have a look at an example.
Let's say I want a function, call it first n and it takes a number as input. And this
will return a sequence with all the numbers starting from zero all the way up to n. So
usually what you would do is you create a list call it nums equals an empty list, then
you also say num equals zero. So, this is your start number and then you say while num
is smaller than n nums dot append num. So you at the current number to a list then you
update the current number. So you say num plus equals one and at the end, you will return
this list. So you return nums and now I can say for example, I can say my list equals
first n and give it for example 10 and then I can simply print this so now this will print
all the numbers from zero to nine in a list. And for example I can also calculate now the
sum of this. So this will print 45. And now here with this way, all the numbers are stored in this list. So this takes a lot
of memory. And now if I use a generator instead, I can say I define another function first
n underscore, Jenna rater. And now it also takes as input, and now I don't need the list
anymore, I simply say num equals zero and also the while loop while num is smaller than
n. And here, I simply yield the current number. So I yield num. And then I also have to update
the number. So I say num plus equals one. So this is the whole implementation of this
as a generator object. And now I can, for example, also print the sum of this first
and Jenner Raider object. And now you see this will give the same result. And this will
also print 45. But here, I don't have to save all the numbers inside this array. So I can
save a lot of memory here. And for example, if I analyze this, I can import sis. And now
I can get the size of this object. So I can see size persists precice dot get size of
this object, this will return the size of this object in bytes. And again, here, I also
say, print sis dot, get the size of this object. So first, I print the size of my list object.
And then I will print the size of the generator object. And here we see that already, the
generator object is smaller. And now let's say I don't have 10 numbers in here. But let's
say I have 1 million numbers in here. And the same number of elements in here, then
this you see, this takes way more memory. So and use cases like this, the generator
object is very useful. So remember this. And another advantage of the generator object
is that we do not have to wait until all the elements have been generated before we start
to use them. Because we can, for example, get the very first item with the first next
statement. And we don't have to calculate all the numbers. Yeah, so this is the big
advantage of generators. Now let's have a look at another example to practice the generators.
A typical example is the Fibonacci sequence. So we say define feeble, not cheap. And this
will give this will get a limit as argument. And the Fibonacci sequence works like this.
So the first two numbers are zero and one, and then all the following numbers are a sum
of the previous two numbers. So now, we have zero plus one is one. Now one plus one is
two, one plus two is three, and so on. So then we have five 813, and so on. And to implement
this as a generator, first of all, we have to store the first two values. So we say a
and b equals zero and one. And then we say while a is smaller than our limit, we yield
the current value, so the current value is a and then we update the current value. So
now we say A equals B. And also we in the same line, we update the B value, and now
the B value is the sum of A plus B, the sum of the previous two numbers. So we say a, b equals B, and so a is B, and B is a plus
b. So this is the whole implementation of the Fibonacci sequence. And now we can say
for example, fib equals Fibonacci and as a limit for example, like if it 30 and Now I
can loop over this object, I can say, for i in fib, and then print I. And now we see
this will print the sequence until, until this limit. And now as a last thing, let's
have a look at generator expressions. So generator expressions are written the same way, like
list comprehensions, but with parentheses instead of square brackets. And this is a
very simple syntax and shortcut to generate some generate to implement the generator expression.
So I can say, my generator equals and now I use parentheses and here I can use an expression
with a for in loop. So I can say I, for I, in range, for example, 10. And I can also
use an if statement, I can say, if I model two equals equals zero, so this will put all
the elements all the even elements from zero to nine in a in my generator object. And so
for example, I can print or I can loop over this object, so I can say for i in my generator,
and then print i. So this will print 0246, and eight. And this is similar to the list
comprehension. So the list comprehension works the same way, except that they use square
brackets here instead of the parentheses. So I can say, my list equals this expression.
And then if I print the list, this will on print the same sequence as a list. And by
the way, I can also say I can convert a generator object to a list with the list function. So
I can say print list, my generator, and this will do the same thing. And again, let's analyze
the size of this. So let's say print sis dot, get size of this object. and here also I want
CES dot get size off this objects. And now they here they are almost equal. But let's
say again, I have a large number 100,000 Then again, my generator object is much much smaller
and saves a lot of memory. So with threading and multi processing, you can run code in
parallel and speed up your code. And in this tutorial, we will learn what is the difference
between a process and a threat, the advantages and disadvantages of both how and why threads
are limited by the Gil and how we can easily use the built in threading and multi processing
module to create and run multiple threads or processes. So let's start with the difference
between a process and a threat. So a process is an instance of a program. So for example,
if I'm running one Firefox browser, then this is one process. Or if I'm running one Python
interpreter, then this is one process. And a thread on the other hand is an entity within
a process. So a process can have multiple threads inside processes take advantage of
multiple CPUs and cores. So you can execute your code on multiple CPUs and parallel processes
have a separate memory space. So memory is not shared, but between processes and they
are great for CPU bound processing. So this means for example, if you have to, if you
have a large amount of data and have to do a lot of expensive computations for them,
then with multi processing, you can proceed As the data on different CPUs and this way
speed up your code and new process is started independently started independently from other
processes and processes are easily interruptible and killable. And there's one Gil for each
process. So this avoids the Gil limitation. And I will come to the Gil or global interpreter
lock in a second. Now, there are some disadvantages. So process is heavyweight. So it takes more,
it takes a lot of memory and starting a process is slower than starting a threat. And since
processes have a separate memory space that memory sharing is not so easy. So the so called
inter process communication is more complicated. And now on the other hand, threats, so as
I said, a threat is an entity within a process that can be scheduled for execution. And it's
also known as a lightweight process. And a process can spawn multiple threads. So all
threads within a process share the same memory. And they are lightweight. So starting a thread
is faster than starting a process. And they are great for IO bound tasks. So this means
input output tasks. So for example, when your program has to talk to slow devices, like
a hard drive or a network connection, then with threading, your program can use the time
waiting for these devices and then intelligently switch to other threads and do the processing
in the meantime. So this is how you can speed up your code with threading. But on the other
hand, threading is limited by the Gil. So the Gil allows only one thread at a time.
So there is no actual parallel computation in multi threading. So threading has no effect
for CPU bound tasks. And they are not interoperable and kill killable. So be careful with memory
leaks here. And since threads share the same memory, you have to be careful with race conditions.
And a race condition occurs where when two or more threads want to modify the same variable
at the same time. So then this can easily cause bugs or crashes. And yeah, that's the
difference between processes and threats. And now I mentioned a couple of times the
Gil. So let's talk about the Gil. And this is also known as the global interpreter lock.
And this is a lock in Python that allows only one thread at a time to execute. And this
is very controversial in the Python community. But why is it needed. And this is needed because
in C Python, so C Python is the reference Python implementation that you get when you
download and install Python from python.org. So the gala is needed because in C Python,
there is a memory management that is not thread safe. So in C Python, there is a technique
that is called reference counting for memory that is used for memory management. And this
means that objects created in Python have a reference count variable that keeps track
of the number of references that point to the object. And when this count reaches zero,
the memory occupied by the object can be released. And the problem now in multi threading is
that this reference count variable needs needs protection from race conditions where two
threads increase or decrease the value simultaneously. So if this happens, it can either leak, it
can cause leaked memory that is never released. Or it can incorrectly release the memory while
a reference to that object still exists. So this is the reason why they introduced the Gil. And a couple
of ways to avoid the Gil if you want to use parallel computing is to use multi processing
Or you can use a different free threaded Python implementation and not c Python. So there's,
for example, Chai THON or iron Python. Or you can use Python, Python as a wrapper for
third party libraries. And this is the way it's it works in NumPy artists Sai pi modules.
So they are basically just wrappers in Python, that then call code that is executed in C.
So yeah, that's enough theory. And now let's jump right into code. So let's start with
multi processing. And for this, you simply say from multi processing, import a, the process
I'm sorry. And now I create a list called processes, where I will store all my processes.
And now I define a number of processes. And a good number usually is the number of CPUs
on your machine. So you can say import o s, and then we say num process processes equals
o s dot CPU count. So on my machine, there are four different CPUs. And then I will create
the processes, so create processes. So I will say, for i in range, num processes, P equals
a new process. And this takes two important arguments. Now the first one is target and
the target function. So this is a callable object or a function that is then executed
by this program process. So I have to define a function here. So I say, let's define this
up here. So let's say in this example, let's say their square numbers. And here, I will
say for i in range 100. I will simply say i times i. So this is a dummy example, that's
basically not useful, but just for how to show you to show you how to use different
processes. So this is the function that my process should execute. So I say target equals
square numbers. And if my function here has some arguments, so then I would also need
to specify arcs equals and then as a tuple. Give the arguments here. So in this case,
I don't need them. So now I created my process. And then I say processes dot append, my process.
And now I want to start each process. So I say for p in process, and then I say P dot
start. And then I also have to join the processes. So I say for P and process, P dot join. So
this means that I want to wait for a process to finish. And while I'm waiting, I am blocking
the main thread. So here I am waiting for all processes to finish. And I blocked the
main thread until these processes are finished. So now at the end, I can for example, simply
print and main. And I will only reach this point when all processes are done. And now
if I execute this, let's For example, let's also import time and tear. Just to show you the different processes,
I will wait some time and say time dot sleep 0.1 and now I am having a look at the activity
manager or the task manager. So here I can filter for processes. So I say I filter for
Python. And as you can see that I've already two Python processes running, they all have
a different process ID. And they all, it's also shown how many threads are inside my
process. So now if I'm executing this Python file, then we will see what will happen. So
it takes a couple of seconds. And now we see five Python processes coming up. So this is
the main process, and then the four process processes I created here. And now after a
couple of times, after this is finished, they will disappear again. So we can see that there
are actually different processes now running on my machine. And this is how we can use
multi processing. And now let's talk about multi threading. So the threading API is very
similar to the previous multi processing IPA API. So here, I say from threading, import
threat. And then here, let's call these threats, and num. Now I call this number of threats.
And let's simply say I want to have 10 different threats. And then for i in num threats, now
I create a threat. And this takes the same arguments. So it also has to define a target.
And if my target has some arguments, then I would also have to specify the arcs here.
And then I say, threats dot append my threat, then I will start each threat. So I will say
for t in threats, T dot start and also join them. So I will say for T and threats, T dot
join. And now let's have a look at the activity manager again. Now if I'm running this Python
file, then we will see takes a couple of seconds. Now we will see one process coming up with
11 threads inside so the main threads and the 10 child threads that I created here.
And now processing is finished and the threads disappear again. So this is how you can use
the threading module. In this video, we will go into more detail
about the threading module. So we will quickly recap how we can create and start multiple
threads, then we will learn how we can share data between threads and how to use locks
to prevent race conditions. We will also learn what is a daemon process and how we can use
a queue for thread safe data exchanges. So let's start and let's quickly recap from the
last video how we create and start threats. So this is the code where we left off. So
we say from threading import threat. And down here we define a num so we want 10 threats
here. And now we create our threats. And for each threat, we give them a target method.
So this is the function that the Stan executed by this threat. And then for each threat,
we also have to say thread dot start. And also threat dot join. So join means that we
wait and block the main threat until the threat is complete. So yeah, this is how we can can
use the threading module. And now let's go into more detail. And let's talk about how
we can share data between threats. So since threads live in the same memory space, they
have access to the same data. So this makes sharing data very easy. So we can for example,
just use a global variable here. So let's define a global variable. And in this case,
I will call this database value. So and I will set this to zero in the beginning, so
this should simulate a database now And now in our main code, what we will do is, we will
first of all we print, print the start value. So we print our database value here. And then
we will create two threats. So let's say threat one equals threat. And this will get a target
method that we will call increase, and also threat a second threat. So threat to that
does the same thing. And then for each threat, we say threat start. And also, threat join.
So we wait for the threads to complete. So thread two dot join. And at the end, we print
the end value. And then we again, want to print a database value at the end. So and
now we have to define this increase methods. So we say define increase in here, we want
to get and modify our database values. So in order to modify the global variable, we
have to say global database value. And now we can use it here. And now let's make some
dummy code. So we want to simulate some database access, we want to get the value from the
database and store it in a local copy. So we say local copy, equals and here, we can
simply copy it from our database value. And then we want to do some processing. So here,
we simply say, local copy plus equals one, so we want to increase it. And then we simulate
that this should processing should take some time. So we import time. And then we wait
some time here. So we say time dot sleep 0.1. And then when we are done, we want to write
our new value back into our database. So we simply copy it back and say database value
equals local copy. So this is our increase function. And now let's run this. So now we
have two threats. And if we run this, let's clear our console.
And let's run this again. So we he sees start value is zero, and end value is one. So now
you might be wondering, why is this one because we have two threats. And both threats should
increase our database value. So now the end value should actually be two. And now why
is it one and this is because we have a race condition here. So a race condition happens
when two or more threads try to try to modify the same variable at the same time. And now
let's step through this code what is happening here. So when we say thread one dot start,
then it will get the database value and store it in a local copy. So in the beginning, this
is zero, and then we will modify the local copies. So now our local copy is one. And
now since we say time dot sleep, our program can intelligently switch to the other threads
and use the waiting time. So now it switches to threat number two. And now thread number
two invokes this increase method. So it also copies the database value in the local copy.
And the local and the database value is still zero because we didn't write it back here.
So now thread two also has a local copy, which is zero and then it increases it's to one
and then we again say time that sleep so we can switch back to threat number one. And
now threat number one copies it's it's a Copy that is one into our database, and then it's
done. And then we are switched back to thread two again. And that now also copies its local
copy that is also one here into the database value. So this is why the end value is one.
And now how can we prevent this. So for this, we use the lock object. So we say from threading,
import, lock, and then we create a lock here. So we say lock, dot lock equals lock. And
now we say our increase method gets a lock. So we have to give this year in the arguments.
So we say arcs equals lock. And since this is a tuple, with only one element, we also
need a comma here. So Python needs this comma here in order to know that this should be
a tuple. And also, for our second threat, this will now get the lock as an argument.
And now with a lock, we can so a lock prevents another threat to access this to access this
code part at the same time. So now we can say lock dot acquire. So it basically has
two, only two methods. So we say lock dot acquire. And now we can process and modify
the value. And at the end, when we are done, we say lock dot release. So and we should
always, every time we acquire a lock, we always have to release it. So Otherwise, this will
block and never release. So then we are stuck here. And now what is happening with this
lock. So now our let's run this and see if this works. Lock dot acquire. So Oh sorry. This is a lock
object. So now let's run this. And now we see that it's correct, our end value is two.
So what happened here, so now our first threat got here. And since it locked the state, now
it can modify the value. And it will not switch back to our threat number two here, because
it's the state is locked. So it can count it continues and runs and copies the local
copy that is now one into our database, and then it releases the lock. So now our second
threat can enter this code part. So it also gets the database value. And this is now already
one and then it modifies it to two and writes it back. So now this is working fine. And
as I said, you should never forget to lock to say lock dot release. So there's a recommended
way to use locks. And this is to use a lock as a context manager. So you can simply say
with lock colon, and then use the part of the code here. And then we don't need to say
lock that release. So let's also get rid of this. So if we run this, then we see this
also works correctly. So this context manager acquires and releases the lock for you. So
yeah, this is the concept of locks. And now let's talk about how we can use queues in
Python. So queues are excellent for thread safe and process safe data exchanges and data
processing in multi threaded or multi processing environments. And for this, we simply we have
to import the queue so we say from queue import queue. And now let's get rid of this. And
first let's have a look at how a queue is working. So a queue is a linear data structure
that follows the feefo or First In First Out principle. So a good example of a queue is
a queue of customers that are waiting in line where the customer that came first is also
served first. So, let's create a queue object. So we say q equals Q, and then we can put
in some elements. So we say q dot put one, and Q dot put to two. And also, let's put
in us a third object, so say q dot put three. And now our code looks like this. So first,
the one is, enters our Q, then we put in the two, and then we put in the three. And here,
this is our front. So the beginning of the queue. So now if we want to get the first
value, we can do this by saying first equals q dot get. So this will get and remove the
first item. So if we print first, then this will print one. And now our thread, our queue
only has three and two inside. So this is how the queue principle is working. And there
are a couple of other important methods. So first, you can check
if a queue is empty with Q dot empty, this will return true if the queue is empty. And
then in a threat threading environment, whenever you get a object with queue dot get, and then
you process this object. When you are done processing, you should always call queue dot
task done. So this now tells the program that we are done processing with this object and
can't can can't, can continue. And there's also a Q dot join method. So this blocks until
all items in the queue have been gotten and processed. And this is similar to the thread
dot join methods. So with this, we block the main thread and wait until all the elements
in our queue are processed. So these are the the important methods you have to know. And
now let's look at an example to how we can use this. So we say Um, also, we want to define
a couple of threats. So we say num threats equals 10. And then we say for i in range,
num threats. And here we create our threat. So now we say threat equals threat. And as
a target, it needs to get a function. So we will define this in a second. And then we
say thread dot start. And we will also now use a demon threat. So we say threat dot demon
equals true. And I will explain what this will do in a second. So by default, it is
not a daemon thread. And then let's define our function. So our function, let's call
them worker, and define this worker function up here. So we say def worker. And now we
use an infinite loop. So we say while true, and then we say value equals q dot get. So
this will get two arguments, this will get the queue and also a lock. And here, we will
get the first value inside our queue with Q dot get and then we will do some processing
with it. So in this case, we simply want to print the values so we say print and let's
import the current so let's say from threading import current threads, so we want to print
this here. Let's use an F strings so we can say we are in and then we are in our current
threat dot name. And in this threat we got the value. So we simply want to print this
here. And then we are done. Remember, we have to say q dot task done. And now what this
is doing, this is an infinite loop that is now starting. And since we don't have values
inside our queue, this Q get method will block and wait until items are available. So now
we wait here, so we have to fill our queue with elements. So we simply say for i in range,
let's say one to 21. So we want to fill this with all the numbers from one to 20. And we
say q dot put AI. And then at the end, we have to say q dot join. So we block the main
thread and want to wait until all the items have been gotten and processed. And yeah,
then we print and main. And now let's run this code and see what's happening here. And
here, we also have to, now this worker gets no, let's leave this lock, and only gave it
a cue. And now as arguments, we have to give it in a tuple. Again, we give it the cue and
don't forget the comma here with one item. And now let's run this and see what's happening.
And now we see that we have threads with different
names from one to 10. So we have 10 different threads. And they get the values from our
queue and can process the item here. And the order might not be sequential. But what is
important here is that with a queue, we can easily exchange the data in a thread safe
environment. So this value, this queue gets call is thread safe. And also the queue put
calls are thread safe, so no other thread can write at the same time into this queue
position. And now let's run this again. So in this time, we, this time, we got lucky,
and we got lucky again. So what might happen here is that multiple threats might try to
print at the same time. So there might be print statements that are in the same line.
So two statements in the same line and no line break. So to make it work correctly with
we should say with lock and use a lock here. So let's also give this a lock. Let's say
down here, lock equals lock. And then as argument, it also gets the lock. And now this should
work fine and never produce confused lock statements. And yeah, let's have a look at
what is happening here again, and why we use a demon threat. So we are our threats, entered
this infinite loop. And then it blocks here because we have no items inside our queue.
And then as soon as items are available, then it can continue here and process the items.
So in this case just prints it and then after all the items are done, we can continue here
then this will unblock and then we will continue and print n main and then we will leave the
main threat. And now a demon threat is a back ground threat that will die when the main
thread dies. So you might be wondering, we have an infinite loop here. Why do we Why
does our program correctly stop so we say and main and then it's it's done and I can
use my command line here again. So a daemon thread dies when the main thread dies. So
if I reach this statement and then exit the main thread, then all the threads die. And
so the worker method and the wild true loop no longer gets invoked. And this is why we
use a demon threat here. So by default, this is false. And if we don't use a demon threat
here, then our program will still continue here in our wild true loop. So, what we should
do then is we should use another mechanism, for example, some some signaling mechanism
like an event to say that now we are done and we can exit the wild true loop. So then
we should have used here, if some condition and then break. So yeah. In this video, we will go into more detail
about the multi processing module. So we will quickly recap how we can create and start
multiple processes. Then we will learn how we can share data between processes, and we
will recap how to use locks to prevent race conditions and how to use queues. And at the
end, we will learn how to use a process pool to easily manage multiple processes. So let's
start and let's quickly recap from the last video how to create and start processes. So
we say from multi processing import process. And then down here we define a number of processes.
So a typical good choice for this is the number of processes on your machine. And you get
this with Oh s dot CPU count. And then you create your different processes with process
equals process. And this takes a target is a function argument and the target is a callable
function. This is that this then executed by this process. So we define a function appear,
this simply squares, some numbers. And then we give this to our process here. And then
for each process, we call process dot start. And also process dot join. So this says says
that we want to wait for all processes to finish and block the main program until all
these processes are done. So this is all we need to set up multi processing. And now let's
go into more detail. And first, let's talk about how we can share data between processes.
So in the last video with multi threading, we learned that we can easily share data between
threads with a global variable. And now with processes, processes don't live in the same
memory space, so they don't have access to the same public data. And because of that,
they need special shared memory objects to share data. And there are two shared memory
objects that we can use, we can use a value for a single value, or we can use an array.
So we say from multi processing, import value and import array. And down here, let's first
start with a single shared values. So we say shared number. And this is now a value. And
this takes two arguments. First, we have to give it the data type as a string, so we give
it an i for integer and a starting value. This is no cirro. And first of all printers
so we say number at the beginning is and then we say we access this shared number with or
this value with share number dot value. So now if we run this, and we see that this is
zero, and now let's create two processes that should modify this number. So we say process
one equals process. And as a target, it gets a function that we call at 100. So let's define
this up here. So let's define adds 100 and this gets a number and then it should modify
this number a couple of times. So we say for i in range 100. So 100 times it should say
number though value plus equals one. So it should increase this by one. And we also want
to modify a behavior that takes some time. So we say, time dot sleep and 0.01. And so
here we give it this at 100 to our process, and it also needs arguments. So we say art
equals, and this is a tuple. So here, we give it this shared number. And be careful. Since
this is a tuple, with one element, it also needs a comma here so that Python knows that
this is a tuple. And then we create a second process that will do the same thing. So process
two, that should add 100 to our shared variable. And then we say process, one dot start, and
process two dot start. And then we wait for them to complete. So process one dot join, and process to the chain. And now at the end,
we again, print our numbers. So we say number at end is and then access it with share number
dot value. So let's execute this and see what happens. So the beginning is 100. It's zero.
And now we got lucky. Now let's run this a second time. And now it's not 200. So it's
only 168. And why is that because here a race condition happened. And I will not explain
this in details, please have a look at the previous video. There I explained in detail
how race conditions occur. So a race condition occurs when two threads or processes try to access and modify the
same shared variable at the same time. So in this case, both processes try to read and
write into this object at the same time. So some operations might get lost here. So in
this case, it's only 168. And to prevent this, we must use a lock. So we say from multi processing,
import lock. And I also talked about this in the last video, so please check this out.
So a lock prevents another process from accessing this at the same time. So to use this, we
create a lock object, so we say lock equals lock. And then we give this to our function
to function. Yes. So this now also takes a lock. And a lock has two important methods.
So first, we say lock dot acquire. And then at the end, we say lock dot release. So as
soon as we say lock dot acquire, it will set this in a locked state. So this means that
while this is running, no other process has access to this code and can execute this part
here. And then when we unlocked the state, again, with lock that released and the second
process can also execute this. So this is all we need to prevent multiple process to
modify this at the same time. So now let's run this. And now we get 200. Let's run this
again. And yeah, still working. And a better way to use locks is to use locks as a context
manager. So whenever you say lock acquire, you always have to call locked release, then
at the end, otherwise, this will block and your program cannot continue. So don't forget
this. And you can use a lock as a context manager. So we say with lock, colon, and then
your code. So this automatically will acquire and release this for you. So yeah, this also
works. And now this is how we can share a single value. And now let's share a array.
So we say shared array equals array, and this also needs a data type. So in this case, that's
given a D for double And here given a list as initial values, so we say, put in 0.0,
here, 100.0, and 200.0. And then we say our array, at the beginning is, and let's say
shared array. And then we have to access each element with inside brackets and then with
the index. And we can also use slicing here to access all in the indices. So let's just
put in a colon here, and then again, print this at the end. So at the end, we want to
print our array. So array, at the end, is this. And now we have to change our functions,
this now takes multiple elements. So this takes numbers. And then we have to go over
each number here and increase it. So first of all, let's also change this parameter here.
So let's say shared array. And now, in our, in our function, what we want to do is we
want to go over each number and increase it. But be careful here. So we cannot say for
number in numbers, and then simply say number plus equals one. So now if we run this, this
will print our error at the beginning and at the end, and this is still the same. And
this is because this loop here will create a local variable called number that is then
increased. So this has nothing to do with our shared value object. So in order to do
this, we have to say for, and let's say for i in range, and then the range has the length
of our array. And then we access each element with numbers, dot i, and save plus equals
one. And now let's run this. And now we see that it got modified. But we also have race
conditions here. So don't forget the lock. So we say with lock, and then our modification operation.
And now we increased each element in our array by 200. So this is how we can use the shared
value and shared array. And we can also use a queue to exchange elements between processes.
And I also already showed this in the last video. So a queue can be used for process
safe data exchanges. And so in the last video, we set from Q, import Q. And there, we have
to use a slightly drif different queue that was formed from the multi processing module.
So this has all the same methods except the the tasks done and the tasks and the join
method. So a queue is a linear data structure that follows the first in first out principle.
So the first element that you put into your queue, that is then also the first element
that gets retrieved when you want to get elements. So let's make an example to use a queue and
exchange data between multiple processes. So in this case, let's say q equals Q, and
then create two processes that should do should access and write to this queue. So we have
a process that gets as a targets, it gets a function that we call square. And as arcs
it gets some numbers and our queue and then we curate a second process that has In a second
different function here, so we call this make negative, and it has the same arguments. So
it will write to the same queue. And now let's define our functions here. So we say that
f square, and this will get some numbers, and Q. And then we say, so in this case, for
i in numbers, we calculate the square and put it into our Q with Q dot put i times i,
and then a second function make negative. And there we also it also takes some numbers
and a Q. And here, we also go over our numbers for i in numbers, and then we say q dot put
minus one times i. And then let's say, let's start our processes. So pros, one dot start,
and process two dot start, and then process one dot shine, and process two that shine.
And here, we don't have to call q dot join, because there is no methods cue that join.
But what we can do is we say while our queue is not empty, so while not Q dot empty, and
then we want to print each element. So print q dot get. So this will return and also remove
the first element in our queue. And yeah, now let's run this and see what happens. numbers
is not defined, oh, sorry, I have to create a numbers variable. And I will say this is
a range object from one to five. So or five should be included. So I say from one to six.
And now this will print each element. And we see that both processes have access to
this queue and can write put elements into it. And then in our main
process, we can also access the queue and get the elements back. So this is how we can
a can use a queue. And now as a last thing, let's talk about a process pool. So a process
pool can be used to manage multiple processes. So a process pool object controls a pool of
worker processes to which chops can be submitted. And it can manage the available processes
for you and split, for example, data into smaller chunks, which can then be processed
in parallel by different processes. So let's have an example how this works. Basically,
a pool takes care of a lot of things for you. So you don't have to consider a lot. So we
simply say from processing, import pool. And then down here, we create a pool. And
we say, so we say pool equals pool, and then it has two or four, let's say it has four
important methods that you have to know. And for the rest, I would recommend to have a
look at the documentation because there are a lot of more methods but the most important
ones are map, apply, join and close. So what we want to do is we want to create multiple
process that should access a or execute a function. So we call define a function cube.
And this takes a number and returns the cube. So it will will return number times number
times number. And now here we can simply say or we create some numbers. So numbers equals
a range object from zero to 10 or 10 or only to nine. And then we say, pool dot map. And
now we map we have to give it a function, so we give it the cube, and the numbers. And
this will return a result that we can then print. So print our result. But first of all
we have to, so this will, what this will do this will automatically allocate them the
maximum number of available processes for you and create different processes. So typically,
this we'll create as many processes as you have cores on your machine. And then it will
split this iterable into an equal into equal sized chunks, and submit this to this function.
And this function is now executed in parallel by different processes, or by different processes.
So this is all you need to write and then the pool will take care of the rest. So this
will allocate the pools, it will split the data and then run this method in parallel.
And when it is done, it will return the result. And we have to call pool dot close. And then
we can call pool the join. So this means that we want to wait for the pool to process all
the calculations and return the results. And we have to remember that we should call pool
that close before. So now if we run this, we can print our result and we can see that
it has the cube here. So yeah, this is how we can easily use the the pool to run different processes with a function.
And if we simply want to have one function executed by a pool, then we can say pool dot
apply. And then also the cube. And then in this case, it will only has one number year.
So we can for example say Apply numbers, the first element. So number zero, so this will execute a process
with this function with one argument. And yeah, so this is the most important things
about pools. And there are also asynchronous calls to this map and apply functions, but
I will not cover them here. First of all, we will learn what is the difference between
function arguments and function parameters. Then we will talk about positional and keyword
arguments, then about default arguments and variable length arguments. So what are the
arcs and quarks arguments for then we will talk about container unpacking, we will also
talk about the difference between global and local arguments inside of functions. And finally,
we will have a look at how arguments are passed to functions and if they can be modified within
a function. So let's start and let's quickly talk about the difference between our arguments
and parameters. So parameters are the variables that are defined or used inside parentheses
where defining a function. And arguments are the values passed for these parameters while
calling a function. So let's make an example. Let's say we have a function called print
name, and it gets a name. And then we simply print this name, then this name here is our
parameter. And when we call this function, let's call print name with a string LX. Then
this is the argument for this function. So there is a difference when we talk about them.
Now let's talk about positional and keyword arguments. So we can pass arguments as positional
or keyword arguments. And let's make another function as an example. So let's say we have
a function foo, that has three parameters A, B, and C. And we simply want to print them
Print A, B, C, then we will call this function with positional arguments. So we can say foo,
and then just one, two, or three. So this will print 123. Or we can also use keyword
arguments. So we say A equals one, b equals two, and C equals three. So this will also
work. And note that if we use keyword arguments, then the order is not important. So I can
say, for example, C is one, and a is three, and a is the first one that is printed. So
let's see what happens. So it prints 321, and not 123. Like I like the orders here.
So when using keyword arguments, then only the keywords matter and not the position.
We can also use a mix of both. So I can use a positional argument first, that's a one
and then I can use keyword arguments. So b equals two, and C equals three. So this will
also work. But I cannot use another positional argument after a keyword argument. So if I
try to call it like this, then this will raise an error. And also, if I try to assign a a
again, so A is the first positional argument. And now if I use a as keyword argument, then
this will also raise an error. So yeah, that's the difference between positional and keyword
arguments. And yes, sometimes it's better to use keyword arguments, because it makes
it more clear what they present. Or we can rearrange the arguments in a way that makes
the most readable. So yeah, then we have the possibility to add default arguments. So I
can say D and give them give this parameter a default value. So let's say d equals four.
And now if I, I can call this fool, and just with three arguments now, one, two, and three. And let's also print D here. So if I ran it
like this, then it will take the default value for D. And I don't need it here. But I can
also give it a different value. So I can say, seven here. So this will print 1237. Yeah,
so default arguments must be at the end of your function parameters. So for example,
if I have one here, b equals two, and I try to run this, then this will give an error.
And now let's talk about variable length arguments. So probably, you've also already seen functions
that looked like this. So they have some parameters A B, and then have at this star, and arcs
argument, and sometimes also with with double stars and quarks. And now what these are,
so this is a function. If you mark a parameter with one asterisk, or one star, then you can
pass any number of positional arguments to your function. And if you mark your parameter
with two stars, then you can pass any number of keyword arguments to this function. And
typically they are called arcs and quarks, but you can call them whatever you like. So
also, for example, C. So inside this function, let's print a and b first, and then this is
a tuple inside your function. So we can go over this tuple and say for arc in arcs, and
then print arc. And the quarks argument is a dictionary so I can say For key in quarks,
and then I want to print the key and the value of this dictionary entry. So I say quarks
key. And now I can call this function. For example, at least it needs the two arguments
A and B. So I can say, one and two. But then I can also use as many positional arguments
as I want. So I say, maybe 345. And then I can use some keyword arguments. So I can,
for example, say six equals six, and seven, equals seven. And now let's run this. And
let's see what it prints. So first, it prints the two positional arguments one and two,
then it goes over our, our arcs. So this is 345. So it prints each number in a different
in a new line, and then it goes over the keyword arguments and prints the keywords and the
value. So this is how we can use variable length arguments. And for example, I don't
need them. So I can also simply use keyword arguments here. Or I can use some more positional
arguments and don't use the keyword arguments. Sorry. So this is also possible. Now, let's
talk about force keyword arguments. So sometimes you want to have keyword only arguments, and
you can force enforce that. So you can, for example, give a write a star here, and then
some more arguments after this. So let's say C, and D. And then I want to print them here,
print ABCD. And now every parameter after this star must be a keyword
argument. So if I write it, like call it like this, then these are positional arguments.
And then this will raise an error. So I have to say C equals three and D equals four. And
then it works. Or, if you, for example, use the arcs variable here. And then each parameter
after that is also a keyword only parameters. So let's say C, and D, and then simply print
the last two. So if I write it like this, or for example, to make this more clear, let's
call this last. And then for arc, in arcs, print, arc, and then print. Last. So if I
caught it like this, then it's missing, then sees this parameter only as your arcs. And
then it says that the last keyword parameter is missing. So I need another one. And now
I needed as keyword arguments. So I say last equals 100. And then it's working. So this
is how you can enforce to have keyword only arguments. Now let's talk about unpacking
arguments. So if we have a function, let's say again, fool with three arguments, A, B,
C, and we simply print them a, b, and c. Now let's say we have a list, my list equals 1012.
Here, then we can easily unpack this list into our function in a function in the function
call. Have this list unpacked into the arguments. So I can say, star and then my list. So this
will unpack the first item into a second into B and the third into C. And this also works
with a tuple here, so I can have a tuple. Here. The only thing is that is important
is that the length of your container must match the number of parameters here. So for
example, if I have another item here, then this won't work. Now if I have a dictionary,
so let's say my dict equals, and then the, it must have the keys with the same names
as your parameter names here. So a, and then some well you want, then the second B, and
some well you and also see. And some well you then I can unpack a dictionary with two
stars here. So I say two stars, and then my dict. And then this will also work. But here,
also, the length of this dictionary must match the number of parameters here. And also, the
keys must match the name the parameter names here. So for example, if I use e here, then
this will also raise a type error. So yeah, this is how we can quickly unpack a dictionary
or a list into our function arguments. And now, let's talk about local versus global
variables. So let's say we have a function. Again, foo.
And this now First, let's say we have a global variable somewhere. So we call this number
and say, the number is zero. And then inside, we create a local variable x and access this
allow a global variable, so we can say x equals number, and then let's print let's say number
inside function, and then print x. So and then we can call this. So let's call foo,
then this will print the number inside the function, so we can access this number here.
But if we want to modify it, so let's say number equals three, then what will happen,
then this will raise a, a arrow here, because then what this will do here, this will create
a local variable that is now different than this global variable. So if
you want to modify this, then we first have to say, global number, and this is the name
of this global variable. And then we can say number equals three. So this will work. So
now if we print the number after our function call, then this will print the new value three.
And if we now what will happen if we don't write this global here, and we don't have
this and simply assign number two three. Then now what will happen if we run this now this
x is not available anymore. So let's run this. So this will Print zero, it prints the number
that is still zero even after the function call where we set number equals three. And
this is because here, we create a new local variable. So this has nothing to do with this
global variable. And this is the, this only lifts inside your function. And it will not
modify your global variable. So if you want to modify the global one, then you have to
write global number here. And now it will print three. Yeah, so this is the difference
between local and global variables. Now let's talk about parameter passing. So maybe you've
heard already of the term, call by value or call by reference. And in Python, it's a little
bit different. So it uses a mechanism, which is known as call by object or call by object
reference. And there are two rules that must be considered. So parameters are passed are
the parameters passed in? No, sorry, the parameter passed in is actually
a reference to an object, but the reference is passed by value. And there is a difference
between mutable and immutable data types. So this might be a little bit confusing. But
this basically means that mutable objects like lists or dictionaries can be changed
within a method. But if you rebind, the reference in the method, then the outer reference will
still point to the original object and is not changed. And immutable objects like integers
or strings cannot be changed within a method. But immutable objects contained within a mutable
object can be reassigned within a method. So let's look at some examples to make this
clearer. So let's say we have a function foo. And this takes an argument x, and then it
reassigns. This so it says x equals five. And now let's say we have a bar equals 10.
So this is an integer, and then we call foo with this bar. And after this, we want to
print all of our now this will still print 10, even if we assign X to five here. So because
what happens here that var is an integer, and this is an immutable, immutable type,
so it cannot be changed. And that this will create a local variable called x here, that
has nothing to do with this. So this is the same with the global and local variable difference.
But yeah, so immutable objects cannot be changed, but mutable objects can be so let's say this
will get a list. And then we can modify this list. So we can say a list dot append an item,
so let's append four, and let's create my list. And this is, has three elements one,
two, and three. And then we passed this list and then call this function with the list
and then afterwards, print the list. So then we see the list got modified. So immutable
objects can be modified within a function. And also immutable objects within a mutable
object can be changed, so that immutable integers within this list can be changed. So I can
for example, also say, list and access the first index, so index zero, and this is now
let's say minus one 100. So this will also change the global list here. And but what
is what is not possible so if we rebind a mutable reference here. So if I say for example,
first if I say, a list equals, and then let's say 200 300 400. And now I call, I create
my global list here, I call this function, and then I print it, and it will still print
the original list 123. And this is because I rebind, the reference here. So this is now
a local variable, a list with this new ways and new values. So this has nothing to do
again with the global variable. So yeah, maybe now the four points are more clearer. So again,
mutable objects can be changed. immutable objects cannot be changed. But immutable objects
contained within a mutable objects can be changed. And like here, if we rebind, the
reference in the method, then the altar reference will not be changed. And let's have a last
very quick difference that how this can affect your list. So first, if
we say, a list, instead of writing append items, we can, for example, say plus equals
and then a new list. So if I write it like this, and now if I run this, then my outer
list here, my global list, got affected by this. But now if I say a list equals a list,
plus this, then if I run this, then this will not change the original list. So this is a
slight difference, but it can have a big effect. Because here again, this will create a local
variable. So be careful with this slight difference. So plus equals, again, will change the list.
So in this tutorial, we will talk about the different use cases of the asterisk or star
sign in Python. So it can be used for multiple different cases like multiplication and power
operations, the creation of lists or tuples, with repeated elements, for arts quarks, and
keyword only parameters for unpacking lists tuples, or dictionaries into function arguments,
for unpacking containers, and for merging containers into a list or merging two dictionaries.
So we will have a look at all of these use cases. First of all, of course, there is the
simple multiplication operation. So let's say result equals five times seven. And then
if I print the result, then this will print the multiplication of these two. Or if I use
two stars, or two asterisks, let's say two, and then two stars and then a four, this will
be a power operation. So this is two to the power of four equals 16. This is one use case,
then it can be used to create lists tuples, or strings with repeated elements. So let's
say I want to have a list called Ciro's equals, and then I write one element. So I say, one
item here, so zero, and then I write times 10. So this will create a list with 10 elements,
and each element has a zero. So this is my list. I can also put in multiple initial items
here. So if I write it like this, then this will repeat zero and 110 times. I can also
use a tuple here. And it also works with strings. So if I say, let's say a B here, then this
will create a new string with 10 times A B So next is to use the star or asterisk for
the arcs and quarks and keyword only arguments. So if you don't know what this means, please
watch my last video about function arguments. So probably you've seen a function that looks
like this. So that define a function called foo. And then it has some arguments. And then
also some arcs with one star, and with quarks with two stars. So and then let's print A,
and now arcs is a tuple. So I can go over this tuple for arc, in arcs, and then print
arc. And quarks here is a dictionary. So I can say four key in quarks, and then print
the key and also the dictionary value of this key. And now, I can call this function with the
A and B arguments. So let's say one and two. And then for this arcs, I can use as many
positional arguments as I like, so I can say 345. And then I can also use as many keyword
arguments as I want. So I can say, for example, six equals six, and seven, equals seven. So
this will print my function here. Um, forgot the beat here. And then if I only use one
star here, and then another parameter here, then all parameters after this star are keyword
only parameters. So if I want to print C, I cannot call call the function like this.
So because here, the last item must be a keyword argument, so I have to write C equals three,
and then it will work. So this is another use case of the star operator to enforce keyword
only arguments. Then we can also use the asterisk for argument unpacking. So let's say I have
a list, my list equals and it has three elements. So 012, then I can call this function and
unpack this list here with one star, and then my list. So this will work. And the only thing
that is important here is this is that the number of arguments must match the number of parameters here, the number of elements
in the list must match the number of RF parameters here. So if I have another one, then this
will raise an error. And this will also work with a tuple. And if I have a dictionary,
so let's say my dict. And then this must have the parameter names as keys, so a and then
a value, one, B, and the value, and C and the value, then I can unpack this dictionary
with two stars, and then my dict. So this will work. And also the number of elements
must match the number of parameters here, and also the key, the keys, the name of the
keys must match the name of the parameters. So if I have a different key here, then this
won't work. Then the asterisk can be used for unpacking containers, so it can unpack
the elements of a list tuple or steps into single and multiple remaining elements. So
let's say I have a list called numbers and This is, let's say 123456, then I can unpack
them, let's say I write, star and then beginning and then a last value. And this is equal numbers.
So let's print beginning. And let's print last. So this will unpack all the elements
except the last one into a list, and then it will unpack the last item into a single
number. And, yeah, be careful here, this will always unpack your elements into a list. So
if I have a tuple, here, then unpacking works, but it will still be a list here. So if I
run this, and it looks like this, I can also unpack the, or put the star sign for the last
item. So this will unpack the first number into the first element into one number and
all the remaining elements into a list that is now called last. Or I can use this in the
middle, so I can say beginning and then star, middle, and then last. So and then I can print
the middle here. So now middle is my list with the elements between so if I run this,
it will print this. And for example, I can also unpack more numbers into single element.
So I can say second, last, and then here print. Second, last. So this is how we can unpack
multiple items into a list. And we can also use the star operator to merge iterables into
a list. So for example, if I have one tuple with elements,
one, two, and three, and then I have another list, so my list equals 456. And then I can
say second, or let's say, new list, equals and then I say I, in brackets, I put my first
iterable here, so I can say star, and then my tuple. And then I can put
in the second iterable here, so my list. So if I print the new list, then this will be
a new match list. And I can also use a set here. So if I use a set, here, my set, then
this will also work. So this merging, works for lists tuples and sets into a list. Or
I can merge two dictionaries. So if I have one dictionary, call it dict, a equals and
then some elements here. So let's say a and one, and B and two, and then I have a second
dictionary, so let's say dict b, this has the keys C and D with the values three and
four. And then I can create another dictionary. So let's say my dict equals and then inside
these square brackets, I use two stars and then the first dictionary and then comma and
then again two stars to unpacked this Second dictionary. So this will merge multiple dictionaries
into one dictionary. Now if I print this, then I can see that I have one dictionary
now. And yeah, I think that's all the important use cases of the asterisk sign. This tutorial,
we will talk about copying. So we will learn how we can copy mutable elements with a built
in copy module, and the difference between shallow and deep copies. And we will also
have a look at how to make actual copies of custom objects. So let's start. And first
of all, let's have a look at the assignment operator. So let's say we have a variable
called orc, and this is now a number. And now if we want to make a copy with an assignment,
so we say copy equals original, then this will not make a real copy, it will only create
a new variable with the same reference. So now both variables point to the same number.
And now for immutable types like this integer, this is not a problem. So let's say if we
change the copy, and say copy equals six, then this assignment will again, create a
new variable. So they they are now both independent. So if we print the copy, and if we print the
original, they are different. But when we deal with mutable types, so for example, a
list then we have to be careful. So let's say we have a list here with some elements.
So let's say 01234. And now we make a copy with this assignment operator. And then if
we change elements of our copies, so let's say we want to change the first item and say
this is now minus 10. And now if you print both the copy and the original, we see that
also the original has the value minus 10. Here, and this is because this assignment
operator doesn't make an actual copy. So to make an actual copy, we can use the built
in a copy module, so we can say import, copy. And then we have to make a difference between
shallow and deep copying. So a shallow copy is only one level deep. So at the first level,
it makes an actual copy. But then it only copies references of the nested child objects.
And then there's the deep copy. So this will be an a full independent copy. So let's start
with an with a shallow copy. So to make a shallow copy, we can say copy equals, copy
that copy, and then the original. And now if we print both, we see that the
original didn't get affected. So only the copy here has minus 10. And, for example,
with a list, there are several different options to make shallow copies. So we can also say
copy equals original dot copy. So this will also work. Or we can use the list function
and give it the original as an argument. This is also possible. Or we can use list slicing,
so we can say pork, and then the slicing operator. So this will simply be from start to end.
So this will copy all elements. And this will also make an actual copy or a shallow copy.
So this works fine if our element is only one level deep. And now let's say we have
a nest or nested lists. So let's say we have a first lists here, a list inside a list and
then a second list here. So with some more elements, three, so this is our original list.
And now we make a shallow copy. And now we change an object or an item that is at the
second level. So we say copy at index zero, so in this list, and then again at index zero,
so this element, or for another example, let's make index one here, so this is this element.
And now this we want to set to minus 10. And now let's see what happens. So if we run this,
we see that both the copy and the original now have minus 10. Here. And this is because
a shallow copy is only one level deep. So to make an actual copy in all the levels,
we have to make a deep copy. So we can say copy dot, deep copy. And now if we run this,
we see that the original didn't get affected. So this is the difference between shallow
and deep copying. And for the built in types, like lists, dictionaries, or tuples, we can
use these methods. But we can also use it for custom objects. So let's say we have a
custom class and call it person. And now in the in it, it gets self Of course, and then
it gets a name and an age. And then we say self dot name equals name, and self dot age,
equals age. And now let's create two persons person one equals person. And now as the name
it gets LX, and as an age, let's say 27. And now let's make a copy, simply by assigning
it, so let's say person two equals person one. And now if we change person two, dot
h, equals 28. And now if you print person two dot h, and we also print person, one dot
h, then we see again, both got affected because this is not an actual copy. So here, we can
use copy, copy. And now if we run this, we see we have a shallow copy here, so the original
person didn't get affected. But again, now if we have a deeper structure, so let's say,
let more let's first create our person class, and let's say we also have a class company.
Um, this gets this has an init method, so in it, self and now this gets two persons,
it gets a boss, and an employee. So self dot boss equals boss, and self dot employee equals
employee. And now we create two persons. So one boss, so boss, might be older. And now a second person.
Cho was a little bit younger. And now let's say we want to have a company so we say, company
equals company, with our person one, and our person too. And now if we want to make a clone
of this, so if we say, company, clone equals company, or let's right away, make a shallow
copy, so we can say copy dot copy. And now if we change some variability here, so let's
say one boss turns a year older, so let's say company clone, dot boss dot age, equals
56. Now, and now let's print this print company clone dot boss dot age, and also print the
age of the boss of the original company. So let's say company, boss, ah, then again, we
see it got affected because this is only a shallow copy, and the age is at the level
two. So this will, again only be a copy of the reference here. And in order to make this
independent, we have to say copy dot deep copy, and now if we run this, we see that
the Original bosses still 55. So this is the difference between shallow and deep copying.
So we will learn about the concept of context managers, and what are they used for, we will
then have a look at typical examples of context managers and how we can implement our own
context manager. So, context managers are a great tool for resource management, they
allow you to allocate and release resources precisely when you want to. So a well known
example is the width open statement. So, in order to open a file, we can say, with open,
and then a file name. So let's call it note stuff. txt. And we open it in write mode,
and s, a, and give it a name here. So, inside our width statement, we can use this name
now. So we can say file dot write, so we write something into our file, some some to do.
And now when we leave this with statement, again, this width statement or just context
manager will make sure to correctly close our file again, even if there is an exception
somewhere here. So if we would have to write this as a full code, it would look something
like this. So we say file equals open, and then note stuck text in write mode. And then
we have a try block. So we try to write into our file. So write some to do. And now we
have an A Finally, clause. So this will be executed with or without an exception. So
no matter what happens, this will, will be executed every time. so here we can say file,
dot close our file again. And then our resource is freed up again correctly. So now if we
compare this and this, then our with open statement looks much cleaner, and much, much
more concise. So this is the recommended way to open a file. And this is a typical example
how we can use context managers in order to open an AI file and allocate the resources.
And then after leaving, it's also make sure to correctly free up our resources again.
So typical examples is like in this case that with open statements, then, for example, to
open and close database connections, or another typical example is the lock. So if you've
watched my tutorials about multi threading and multi processing, you already know how
to use a lock. So if we have a lock, so we say, from threading, import, lock. And now
if we create a lock, so lock equals lock. So whenever we acquire a lock, so we say lock
dot acquire, and then we can do something here safely.
So this is now thread safe. But after that we always have to call lock dot release. And
if we forget this, we might run into a deadlock here, and our program won't continue. So never
forget to say lock dot release when we had locked out acquire. So a better way to do
this. And also much simpler is to say with lock, and then do something here. This will
automatically acquire our lock when we enter this with statement and then it will make
sure to say locked at release when we leave this with statement again. So this is also
a typical example. And now let's say how we can implement a context manager for our own
classes. So in order to do that, we have to implement the ENTER and the exit methods.
So let's say we have a class and call it managed file. Now of course this has an in it and
it will get a file name here. So we simply store the file name, say self dot file name,
equals file name. And now we re implement the same functionality as With the with open
statement, just in order to show you how this is done. So, now what we have to implement
is we have to implement the Enter method. So this will get self. And then we have to
implement the exit method, this will also get self, and then it will get an exit exception,
type, an exception value, and also an exception trace back. Now, I will talk about this in
a second. But first of all, let's implement both of them. So, the Enter methods will be
executed as soon as we enter the width statement. So, here we want to allocate our resource.
So, in this case, first, let's print enter, to have a look at where this will happen.
And now we allocate our resource. So we say self, we create a file and say self dot file
equals and now we open it here. So we open it with the file name, and open it in write
mode. And then also inside the Enter method, we want to return the allocated resource.
So in this case, we return self dot file. And now in our exit methods, we want to make
sure that we correctly close the file. So we say if self dot file, so if this is not
none, then we say self dot file, dot close. And then print, exit. And here, let's print
in it. And now this is all we need to use this class as a context manager. And now we
can say we can use a width statement. So we can say, with Managed File, and this will
get the file name notes dot txt, s file. And then we can say file dot write, some to do.
And now let's say let's see what happens. So this will, let's also make a print statement
here. So let's say print, do some stuff. So we see here, that init method gets called
when our object gets created. Then as soon as we enter this width statement, the Enter
method gets called so enter is printed, then our resource is allocated, then we can do
some stuff. And afterwards, our exit method is called as soon as we leave this with context
here again. So now let's talk about what will happen if
an exception occurs. So we see here that Python Python passes the type the value and the trace
back to the exit method. So you can handle the exception here. And if anything other
than true is returned by this exit method, then the exception is raised by the width
statement. So let's say let's print continuing here. So in order to see if we reach this
code, and now, let's also print. The, for example, let's print, we want say, exit exception,
and then print the exception type. And the exception value. So now if we run this, we
see that our exception here is none. So no exception here, exception type, and the exception
value is none. And now if we try something here, this that won't work, so let's say file
dot some methods, so this will not exist inside our class here, so it doesn't notice some
method method. So this will raise an exception. So now if we run this, then we see inside
our exit function, it still can close our file even if there is an exception. So it
reaches this code. So then it prints the ACC chat exceptions. In this case, it's an attribute
error. And the error is that we don't have this some methods. And then we can exit this
function. But then our width statement will raise an exception. So we won't reach this
continuing here. And now if we want to handle this exception ourself, we can, for example,
say, we check if exception, type is not none, then prints that here's an exception. So let's
say exception. Exception has been handled. And now in order to not raise an exception,
we have to return true here. So let's say return true. And we don't want to print this
anymore. And now let's run this. Now we see, we did prints exception has been handled,
then it exits our width statement again, and then no exception here from our width statement,
and we can continue. So continuing is printed. So yeah, this is an example of how to write
our own class as a cost as a context manager. And we can achieve this with implementing
it as a class with the ENTER and the exit functions. But we can also implement it as
a function. And to do so we have to say we have to import something. So we say from context
lip import context manager, and we have to use this as a decorator. So and then we will
create a function that is a generator. So if you don't know, or are not familiar with
generators and decorators already, and please have a look at my other tutorials, because
I already talked about them. So now let's create a generator here and call this open
Managed File. And this also will get a file name. And then here, oh, sorry, I misspelled
it. So Managed File, and then inside here, we want first, of course, want to open our
file, so we say f equals open file name, in write mode. And here we have to write a try
and a finally clause. And inside the try statement, we want to yield the file. So here, we would, we want to write everything that
would otherwise end up in our enter function. And then we want to have a finally clause
and here we write all the content of the exit method to free up the resource, the resource.
So here, we say F dot close. And then we also need to decorate it with our context manager
decorator. And now we can use this function in a width statement. So we can say, with
open Managed File and call it notes dot txt, and then S f and then we can say F dot write
and then write something. So this will also work. And now let's go over this again what
will happen here, so is because this is a generator, so this will first make sure to
allocate our resource and then it will try to yield our resource. So and by yielding
it it will temporarily suspend its own execution. So we can continue here and use this file.
So then we can do some operations with this file. And then when we exit the width statement
again, then our function here continues running. And then the finally clause will be executed,
and our file will be closed again. And also we can handle exceptions here. And yeah, so
this is the second way how to use a context manager. And that's all I wanted to show you
about context manager. I hope you enjoyed this tutorial and if you liked it, please
leave a like and subscribe to the channel. See you
Is the course any good?