<jupyter_start><jupyter_text>Object Oriented Programming (OOP)- we've been using procedural programming paradigm; focus on functions/procedures- OOP paradigm is best used in large and complex modern software systems which make it easy to maintain and improve over time- focus is on creation of objects which contain both data and functionality together under one name- typically, each class definition corresponds to some object or concept in the real world with some attributes/properties that maintain its state; and the functions/methods correspond to the ways real-world objects interact  class- we've used classes like str, int, float, dict, tuple, etc.- class keyword lets programmer define their own compound data types- e.g., a clas that represents a point in 2-D coordinatesclass className:    [statement-1]    .    .    [statement-N]<jupyter_code>class Point:
    pass
a = Point()
a.x = 0
a.y = 0
print(a.x, a.y)
# OK but NOT great and NOT common!
class Point:
    """
    Point class to represent and manipulate x, y coords
    """
    count = 0 # class variable/attribute
    
    # constructor to customize the initial state of an object
    # first argument refers to the instance being manipulated;
    # it is customary to name this parameter self; but can be anything
    def __init__(self, xx=0, yy=0):
        """Create a new point with given x and y coords"""
        # x and y are object variables/attributes
        self.x = xx
        self.y = yy
        Point.count += 1 # increment class variable
     
    def __del__(self):
        Point.count -= 1
# instantiate an object
p = Point()
# what is the access specifier for attributes?
print(p.x, p.y) 
print(Point.count) # access class variable outside class
p1 = Point(2, 3)
print(p1.x, p1.y)
print(Point.count)

# Run this cell few times and see the value of Point.count
# How do you fix this problem? Use __del__ destructor method.<jupyter_output>0 0
2
2 3
2<jupyter_text>visualizing class and instance attributes using pythontutor.com- https://goo.gl/aGuc4r<jupyter_code>from IPython.display import IFrame
src = """http://pythontutor.com/iframe-embed.html#code=class%20Point%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Point%20class%20represents%20and%20manipulates%20x,y%20coords%0A%20%20%20%20%22%22%22%0A%20%20%20%20count%20%3D%200%20%23%20class%20variable/attribute%0A%20%20%20%20%0A%20%20%20%20%23%20constructor%20to%20customize%20the%20initial%20state%20of%20an%20object%0A%20%20%20%20%23%20first%20argument%20refers%20to%20the%20instance%20being%20manipulated%3B%0A%20%20%20%20%23%20it%20is%20customary%20to%20name%20this%20parameter%20self%3B%20but%20can%20be%20anything%0A%20%20%20%20def%20__init__%28self,%20xx%3D0,%20yy%3D0%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22Create%20a%20new%20point%20with%20given%20x%20and%20y%20coords%22%22%22%0A%20%20%20%20%20%20%20%20%23%20x%20and%20y%20are%20object%20variables/attributes%0A%20%20%20%20%20%20%20%20self.x%20%3D%20xx%0A%20%20%20%20%20%20%20%20self.y%20%3D%20yy%0A%20%20%20%20%20%20%20%20Point.count%20%2B%3D%201%20%23%20increment%20class%20variable%0A%20%20%20%20%20%20%20%20%0Ap%20%3D%20Point%28%29%0Aprint%28p.x,%20p.y%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"""
IFrame(src, width=900, height=600)<jupyter_output><empty_output><jupyter_text>exercise: add a method dist_from_origin() to Point class- computes and returns the distance from the origin- test the methods<jupyter_code>class Point:
    """
    Point class represents and manipulates x,y coords
    """
    count = 0
    
    def __init__(self, xx=0, yy=0):
        """Create a new point with given x and y coords"""
        self.x = xx
        self.y = yy
        Point.count += 1
        
    def dist_from_origin(self):
        import math
        dist = math.sqrt(self.x**2+self.y**2)
        return dist
    
    def __str__(self):
        return "({}, {})".format(self.x, self.y)
    
    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Point(x, y)
    
p1 = Point(2, 2)
print(p1.dist_from_origin())<jupyter_output>2.8284271247461903<jupyter_text>sameness - alias or deep copy<jupyter_code>import copy
p2 = Point(3, 4)
p3 = p2 # alias or deepcopy?
print(p2 is p3) # checks if two references refer to the same object
p4 = copy.deepcopy(p2)
print(p2 is p4)<jupyter_output>True
False<jupyter_text>passing objects as arguments and parameters<jupyter_code>def print_point(pt):
    #pt.x = 100
    #pt.y = 100
    print('({0}, {1})'.format(pt.x, pt.y))
p = Point(10, 10)
print_point(p)
#print(p)
print(p.x, p.y)<jupyter_output>(100, 100)
100 100<jupyter_text>are objects passed by value or reference?- how can you tell?- write a simple program to test.  returning object instances from functions<jupyter_code>def midpoint(p1, p2):
    """Returns the midpoint of points p1 and p2"""
    mx = (p1.x + p2.x)/2
    my = (p2.x + p2.y)/2
    return Point(mx, my)

p = Point(4, 6)
q = Point(6, 4)
r = midpoint(p, q)
s = p + q
print_point(r) # fix this
print(r)
print(s)<jupyter_output>(5.0, 5.0)
(5.0, 5.0)
(10, 10)
{'fraction_non_alphanumeric': 0.1272481144846258, 'fraction_numerical': 0.10017404757300329, 'mean_word_length': 4.615635179153094, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 20, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '11156', 'license': 'MIT', 'n_tokens_mistral': 2188, 'n_tokens_neox': 1919, 'n_words': 470, 'path': 'Ch15-OOP.ipynb', 'repo_name': 'rjhakes/rhakesAdvPy', 'size': '5171'}