<jupyter_start><jupyter_text>Object Oriented Programming Object Oriented Programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs. (Wikipedia)There are some basic programming concepts in OOP:* Abstraction* Polymorphism* Encapsulation* Inheritance  What's an Object?* An ***object*** is created by *instantiating* a ***class**** A ***class*** is created using Python's ***class statement*** (usually)* In general, any number of objects can be created from a single class ...* ... unless the class is given some [special magic][1] to make it a *"Singleton"*[1]: http://c2.com/cgi/wiki?PythonSingleton  Oh, great...  Lots of Jargon...  Some OOP Terminology* $\bf Class: $ A user-defined prototype for an object that defines a set of attributes that characterize any object of the class. The attributes are data members (class variables and instance variables) and methods, accessed via dot notation.* $\bf Class\ variable: $ A variable that is shared by all instances of a class. Class variables are defined within a class but outside any of the class's methods. Class variables are not used as frequently as instance variables are.* $\bf Data\ member: $ A class variable or instance variable that holds data associated with a class and its objects.* $\bf Function\ overloading: $ The assignment of more than one behavior to a particular function. The operation performed varies by the types of objects or arguments involved.* $\bf Instance\ variable: $ A variable that is defined inside a method and belongs only to the current instance of a class.* $\bf Inheritance: $ The transfer of the characteristics of a class to other classes that are derived from it.* $\bf Instance: $ An individual object of a certain class. An object obj that belongs to a class Circle, for example, is an instance of the class Circle.* $\bf Instantiation: $ The creation of an instance of a class.* $\bf Method : $ A special kind of function that is defined in a class definition.* $\bf Object: $ A unique instance of a data structure that's defined by its class. An object comprises both data members (class variables and instance variables) and methods.* $\bf Operator\ overloading: $ The assignment of more than one function to a particular operator.  Lets look at some objects...<jupyter_code>import sys

def function(): pass

print type(1)
print type("")
print type([])
print type({})
print type(())
print type(object)
print type(function)
print type(sys)<jupyter_output><empty_output><jupyter_text>Python programs can have different styles* Simple, short scripts can just be straight-line code -- **Do this one thing! BAM!*** A somewhat longer task might have a recurring sub-task: **a function*** If more **functions** appear, they might be grouped into one file (a **module**)* **Classes** come in when higher levels of structure are needed ...  Ok ... so what's a Class?  *  A "Class" is a code structure that contains ...    1. ***Data*** in the form of ***Fields***, a.k.a. ***"attributes"***, and    2. ***Procedures***, a.k.a. ***"methods"***    *  One way to think of these is:    1. An object's ***attributes*** represent its ***state***    2. Its ***methods*** represent its ***behavior***   Everything in Python is an object. Objects are the basic building blocks of a Python OOP program.   When should I write my own classes?* When you have some functions that are passing one or more parameters around* When you have some functions that need to access the same "state" information* When you are developing a Graphical User Interface (GUI)* When you need to represent and process some highly structured data, e.g.:  - a document or web page  - data about a real-world "thing" (either man-made or natural)  The class keywordThe previous objects were mostly built-in objects of the Python programming language. The user defined objects are created using the class keyword. The class is a blueprint that defines a nature of a future object. From classes we construct instances. An instance is a specific object created from a particular class. For example, Phydoux might be an instance of a Dog class.<jupyter_code># first.py

class First:
   pass

fr = First()

print type(fr)
print type(First)<jupyter_output><empty_output><jupyter_text>This is our first class. The body of the class is left empty for now. It is a convention to give Classes a name that starts with a capital letter.  Here we see that fr is an instance object and First is a class object.Inside a class, we can define attributes and methods. An attribute is a characteristic of an object. This can be for example the wavelength of a sensor. A method defines operations that we can perform with our objects. A method might define a turning the sensor on and collecting an image.  Technically, attributes are variables and methods are functions defined inside a class.   AttributesAttributes are characteristics of an object. A special method called __init__() is used to initialize the attributes of an object.<jupyter_code>class Dog:
   def __init__(self, name):
      self.name = name

phydoux = Dog('Phydoux')
lucky = Dog('Lucky')

print phydoux.name
print lucky.name<jupyter_output><empty_output><jupyter_text>In this code example, we have a Dog class. The special method __init__() is called automatically right after the object has been created.   * def __init__(self, name): Each method in a class definition begins with a reference to the instance object . It is by convention named self. There is nothing special about the self name. We could name it this, for example. The name is the argument. The value is passed during the class instantiation.  * `self.name = name`Here we pass an attribute to an instance object.  * `phydoux = Dog('Phydoux')`  * `lucky = Dog('Lucky')`Here we instantiate two objects: dogs Phydoux and Lucky. The number of arguments must correspond to the `__init__()` method of the class definition. The `'Phydoux'` and `'Lucky'` strings become the name parameter of the `__init__()` method.  The attributes can be assigned dynamically, not just during initialization. This shows the next example.<jupyter_code>class Dynamic:
   pass

d = Dynamic()
d.name = "Dynamic"
print d.name<jupyter_output><empty_output><jupyter_text>We define and create an empty Dynamic class.  The line of code:  * `d.name = "Dynamic"`creates a new name attribute, which is ***very*** different that other languages where you have to declaire the variables up front.  MethodsMethods are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects. Methods are essential in encapsulation concept of the OOP paradigm. For example, we might have a connect() method in our AccessDatabase class. We need not to be informed how exactly the method connect connects to the database. We only know that it is used to connect to a database. This is essential in dividing responsibilities in programming, especially in large applications.<jupyter_code>class Circle:
   pi = 3.141592

   def __init__(self, radius=1):
      self.radius = radius 

   def area(self):
      return self.radius * self.radius * Circle.pi

   def setRadius(self, radius):
      self.radius = radius

   def getRadius(self):
      return self.radius

c = Circle()

c.setRadius(5)
print c.getRadius()
print c.area()<jupyter_output><empty_output><jupyter_text>In the code example, we have a Circle class. We define three new methods.  * `area()`* `setRadius()`* `getRadius()`The `area()` method returns the area of a circle.  The `getRadius()` method returns the current radius.   Special MethodsClasses in Python programming language can implement certain operations with special method names. These methods are not called directly, but by a specific language syntax. This is similar to what is known as operator overloading in C++ or Ruby.<jupyter_code>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"

book = Book("Inside Steve's Brain", "<NAME>", 304)

print book
print len(book)
del book<jupyter_output><empty_output><jupyter_text>In our code example, we have a book class. Here we introduce four special methods. The `__init__()`, `__str__()`, `__len__()` and the `__del__()` methods.  You can imagine how similar special methods can be used to implement `__add__()`,  `__sub__()`, etc.  InheritanceThe 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).<jupyter_code>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()<jupyter_output><empty_output><jupyter_text>In this example, we have two classes: Animal and Dog. The Animal is the base class, the Dog is the derived class. The derived class inherits the functionality of the base class. It is shown by the eat() method. The derived class modifies existing behaviour of the base class, shown by the whoAmI() method. Finally, the derived class extends the functionality of the base class, by defining a new bark() method. We put the ancestor classes in round brackets after the name of the descendant class. If the derived class provides its own `__init__()` method, it must explicitly call the base class `__init__()` method.   PolymorphismThe polymorphism is the process of using an operator or function in different ways for different data input. In practical terms, polymorphism means that if class B inherits from class A, it doesn’t have to inherit everything about class A; it can do some of the things that class A does differently.<jupyter_code>class Animal:
   def __init__(self, name=''):
      self.name = name

   def talk(self):
      pass

class Cat(Animal):
   def talk(self):
      print "Meow!"

class Dog(Animal):
   def talk(self):
      print "Woof!"

a = Animal()
a.talk()

c = Cat("Missy")
c.talk()

d = Dog("Rocky")
d.talk()<jupyter_output><empty_output><jupyter_text>Here we have two species: a dog and a cat. Both are animals. The Dog class and the Cat class inherit the Animal class. They have a talk() method, which gives different output for them.   EncapsolationIn computer programming languages, encapsulation is used to refer to one of three related but distinct ideas:* A language __mechanism__ for restricting direct access to some of the object's components.* A language __construct__ that facilitates the bundling of data with the methods (or other functions) operating on that data.* A programming __convention__ for respecting the intended scope of the informationSome programming language researchers and academics use the first meaning alone or in combination with the second as a distinguishing feature of object-oriented programming, while other programming languages which provide lexical closures view encapsulation as a feature of the language orthogonal to object orientation.The second definition is motivated by the fact that in many OOP languages hiding of components is not automatic or can be overridden; thus, information hiding is defined as a separate notion by those who prefer the second definition.Python doesn’t have any mechanisms that would effectively restrict you from accessing a variable or calling a member method.  All of this is a matter of culture and convention.  Data Encapsolation | Name | Notation | Behaviour| :- | :- | :-| name | Public | Can be accessed from inside and outside| _name | Protected | Like a public member, but they shouldn't be directly accessed from outside.| __name | Private | Can't be seen and accessed from outside If an identifier is only preceded by one underscore character, it is a protected member. Protected members can be accessed like public members from outside of class. It can be seen in the following example:<jupyter_code>class Encapsulation(object):
    def __init__(self, a, b, c):
        self.public = a
        self._protected = b
        self.__private = c<jupyter_output><empty_output><jupyter_text>The following interactive sessions shows the behaviour of public, protected and private members:<jupyter_code>x = Encapsulation(11,13,17)
x.public
x._protected
x._protected = 23
x._protected
x.__private<jupyter_output><empty_output><jupyter_text>Note, that if you try accessing \____private__ from outside the class, you’ll get an error. But you can still stumble upon something like this:<jupyter_code>>>> x._Encapsulation__private<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0744920993227991, 'fraction_numerical': 0.002106847253574116, 'mean_word_length': 4.42047308319739, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '28131', 'license': 'MIT', 'n_tokens_mistral': 3450, 'n_tokens_neox': 3308, 'n_words': 1824, 'path': 'Day_02/01_ObjectOrientedProgramming/00_Object_Oriented_Programming.ipynb', 'repo_name': 'saudijack/unfpyboot', 'size': '13298'}