In Python everything is an object, for an example
print(type([1,2,3]))

Now what is a class and what is an object ?
A class is a blueprint/structure plan for the object.
We can think of class as a sketch of a class room with children. It contains the details about the name, roll numbers, heights etc.
An object (instance) is an instantiation of a class. When class is defined, only the description for the object is defined. Therefore, no memory or storage is allocated. Think of an object as an instance of a class,
# Create a new object type called Sample
class Sample():
pass
# Instance of Sample
x = Sample()
print(type(x))
Now we need to have functions inside the class, there are some special functions in classes which contain “__” before and after them,

some special functions in classes :
- The
__init__()
function is called automatically every time the class is being used to create a new object. Consider it similar to a Constructor in cpp and java. Python creates an object for you, and passes it as the first parameter to the__init__
method self
variable represents the instance of the object itself. Most object-oriented languages pass this as a hidden parameter to the methods defined on an object; Python does not. You have to declare it explicitly. This is very similar to ” this ” keyword in cpp and java.- class object attribute is a global variable inside a class
- The __str__(self) : It is used to print or display the string version of the object
- The __len__(self): It is used to display the length of a class attribute
- The __del__(self): It is used to delete an object.
For example we can create a class called Dog. An attribute of a dog may be its breed or its name, while a method of a dog may be defined by a .bark() method which returns a sound.
class Book():
def __init__(self, title, author, pages):
print "A book is created"
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return "Title:%s , author:%s, pages:%s " %(self.title, self.author, self.pages)
def __len__(self):
return self.pages
def __del__(self):
print "A book is destroyed"
Now you can also code functions inside the class,
class Circle():
pi = 3.14
# Circle get instantiated with a radius (default is 1)
def __init__(self, radius=1):
self.radius = radius
# Area method calculates the area. Note the use of self.
def area(self):
return self.radius * self.radius * Circle.pi
# Method for resetting Radius
def setRadius(self, radius):
self.radius = radius
# Method for getting radius (Same as just calling .radius)
def getRadius(self):
return self.radius
c = Circle()
c.setRadius(2)
print ('Radius is: ',c.getRadius())
print ('Area is: ',c.area())
Now to note is if instead of c.area() i just call c.area, it will return the location of the function, not call it.

Inheritance
Inheritance is a way to form new classes using classes that have already been defined. The newly formed classes are called derived classes, the classes that we derive from are called base classes. Important benefits of inheritance are code reuse and reduction of complexity of a program. The derived classes (descendants) override or extend the functionality of base classes (ancestors).
For example, the Animal is the base class and dog is the sub class.
class Animal():
def __init__(self):
print "Animal created"
def whoAmI(self):
print "Animal"
def eat(self):
print "Eating"
class Dog(Animal):
def __init__(self):
Animal.__init__(self)
print "Dog created"
def whoAmI(self):
print "Dog"
def bark(self):
print "Woof!"
d = Dog()
d.whoAmI()
d.eat()
d.bark()
Q.Why do we use inheritance ?
Well we can use a larger base class and inherit from it to derived class and modify or specialize it for our custom use.

For more information,
- https://www.jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/
- https://developer.mozilla.org/en-US/Learn/Python/Quickly_Learn_Object_Oriented_Programming
- https://docs.python.org/3/tutorial/classes.html
Now with this basic knowledge, you can make a mini python project you can check it at https://github.com/kakabisht/Python_Mini_Projects

Regular Expressions
Regular expressions are text matching patterns described with a formal syntax. Regular expressions can include a variety of rules, from finding repetition, to text-matching, and much more. As you advance in Python you’ll see that a lot of your parsing problems can be solved with regular expressions.

Some important function inside re module are,
1.re.search () : it is used to find if a substring is present inside main string, It returns Match or None type.
import re
pattern = 'term1'
# Text to parse, if i remove the term1 from text1 i will get None type
text = 'This is a string with term1, but it does not have the other term.'
match = re.search(pattern, text)
print(type(match))

This Match object returned by the search() method is more than just a Boolean or None, it contains information about the match, including the original input string, the regular expression that was used, and the location of the match. Let’s see the methods we can use on the match object:
# Show start of match
match.start()
# Show end
match.end()
2.re.split () : we can split with the re syntax. This should look similar to how you used the split() method with strings.
split_term = '@'
phrase = 'What is the domain name of someone with the email: hello@gmail.com'
# Split the phrase
re.split(split_term,phrase)

3.re.findall() : it is used all the terms present inside the text
import re
pattern = 'term1'
# Text to parse
text = 'This is a string with term1, but it does not have the other term1.'
match = re.findall(pattern, text)
print(match)

Regular expressions supports a huge variety of patterns the just simply finding where a single string occurred. We can use *metacharacters* along with re to find specific types of patterns.
There are five ways to express repetition in a pattern,
- A pattern followed by the meta-character * is repeated zero or more times.
- Replace the * with + and the pattern must appear at least once.
- Using ? means the pattern appears zero or one time.
- For a specific number of occurrences, use {m} after the pattern, where# m is replaced with the number of times the pattern should repeat.
- Use {m,n} where m is the minimum number of repetitions and n is the maximum. Leaving out n ({m,}) means the value appears at least m times, with no maximum.
def multi_re_find(patterns,phrase):
'''
Takes in a list of regex patterns
Prints a list of all matches
'''
for pattern in patterns:
print 'Searching the phrase using the re check: %r' %pattern
print re.findall(pattern,phrase)
print '\n'
test_phrase = 'sdsd..sssddd...sdddsddd...dsds...dsssss...sdddd'
test_patterns = [ 'sd*', # s followed by zero or more d's
'sd+', # s followed by one or more d's
'sd?', # s followed by zero or one d's
'sd{3}', # s followed by three d's
'sd{2,3}', # s followed by two to three d's
]
multi_re_find(test_patterns,test_phrase)
We can use ^ to exclude terms by incorporating it into the bracket syntax notation. For example: [^…] will match any single character not in the brackets.
re.findall('[^!.? ]+',test_phrase)
A more compact format using character# ranges lets you define a character set to include all of the contiguous characters between a start and stop point. The format used is [start-end]
test_phrase = 'This is an example sentence. Lets see if we can find some letters.'
test_patterns=[ '[a-z]+', # sequences of lower case letters
'[A-Z]+', # sequences of upper case letters
'[a-zA-Z]+', # sequences of lower or upper case letters
'[A-Z][a-z]+'] # one upper case letter followed by lower case letters
multi_re_find(test_patterns,test_phrase)
You can use special escape codes to find specific types of patterns in your# data, such as digits, non-digits,white spaces, and more. Using raw strings, created by prefixing the literal value with r, for creating regular expressions eliminates this problem and maintains readability.
test_phrase = 'This is a string with some numbers 1233 and a symbol #hashtag'
test_patterns=[ r'\d+', # sequence of digits
r'\D+', # sequence of non-digits
r'\s+', # sequence of whitespace
r'\S+', # sequence of non-whitespace
r'\w+', # alphanumeric characters
r'\W+', # non-alphanumeric
]
multi_re_find(test_patterns,test_phrase)

For more information,
Decorators
A function can take a function as argument (the function to be decorated) and return the same function with or without extension.
A decorator takes a function, extends it and returns. Yes, a function can return a function.

def hello(func):
def inner():
print("Hello ")
func()
return inner
def name():
print("Alice")
obj = hello(name)
obj()
In the above example, hello() is a decorator. It wraps the function in the other function.

Python can simplify the use of decorators with the @ symbol.
@hello
def name():
print("Alice")
if __name__ == "__main__":
name()
@hello
def name():
# this is simply equal to writing obj = hello(name)
Q. What is name == “main” ?
Sometimes when you are importing from a module, you would like to know whether a modules function is being used as an import, or if you are using the original .py file of that module.
The top-level code is an if block. __name__ is a built-in variable which evaluate to the name of the current module. However, if a module is being run directly (), then __name__ instead is set to the string “__main__”. Thus, you can test whether your script is being run directly or being imported by something else by testing
For more information,