<jupyter_start><jupyter_text>Previously you would ask:  Given a pair of coordinates, how do we calculate the displacement?   Now we would ask:  Given a pair of coordinates, what are some things we can do/calculate?   Anatomy  Initializer/Constructor  this is where you initialize your instance variables   all python classes ought to start with this method  methods attached to the object this is where your design comes in magic methods comparison methods (recall we could say one list is equal/less than another?)  string conversion methods (recall we could apply the print function directly)<jupyter_code>import math 

class Coordinate:
    
    def __init__(self,x = 0, y = 0):
        self.x = x 
        self.y = y 
    
    def magnitude(self):
        mag = math.sqrt( self.x*self.x + self.y*self.y)
        return mag 
    
    def translate(self,dx,dy):
        self.x = self.x + dx
        self.y = self.y + dy
        '''no need to return anything'''
    
    def __eq__(self, other):
        is_x_equal = self.x == other.x 
        is_y_equal = self.y == other.y 
        return is_x_equal and is_y_equal 
    
    def __str__(self):
        '''up to you to define what
        you want the print statement to show'''
        output = 'x = ' + str(self.x) + ',y = '+ str(self.y)
        return output 
    
    def __lt__(self, other):
        is_x_less = self.x < other.x
        is_y_less = self.y < other.y 
        return is_x_less and is_y_less
    
p1 = Coordinate(3,2)
print(p1)
p1.translate(0.5,0.3)
print(p1)
p2 = Coordinate(3.5,2.3)
print(p1 == p2)

p3 = Coordinate()
print(p3)
print(p3 < p2)
'''Cohort Question 2'''
class Celsius:
    
    def __init__(self, temp):
        self._temperature = temp #<-- make one change
    
    '''getter'''
    def get_temperature(self):
        print("getter is called") #<---
        return self._temperature
    
    '''setter: takes care of validity'''
    def set_temperature(self, t):
        print("setter is called") #<----
        if( t < -273):
            self._temperature = -273
        else:
            self._temperature = t 
            
    temperature = property(get_temperature,
                          set_temperature)

t = Celsius(-300)
t.temperature = -300
print(t.temperature)<jupyter_output>setter is called
getter is called
-273<jupyter_text>Reminders for Mid-Term Put your code in Vocareum regularly. It autosaves your work.  Do not wait till the end to submit or put your code at the last 15 mins  because of network congestion.  Please read the announcement on Piazza  More on the python property The python property makes classes easier to update.  There will not be a need to make users of your class change code.  Let's say your version 1 of Celsius class was written like this. The temperature attribute is designed to be modified directly.<jupyter_code>class Celsius:
    
    def __init__(self, t):
        self.temperature = t
    
    def to_fahrenheit(self):
        pass<jupyter_output><empty_output><jupyter_text>Then users of your class could write code like that.<jupyter_code>c = Celsius(100)
c.temperature = -300
print(c.temperature)<jupyter_output>-300<jupyter_text>Hey, the temperature is invalid.  We'll put in code to tackle that.  We'll put in a "private" attribute with getter & setter methods. A private attribute means that users are not allowed to access it directly. Attributes with a leading underscore is a convention that indicates that it is private. It tells programmers using your class not to access it directly. However, in practice, Python doesn't stop you from doing so. It's like, you can cross the road at the red light if there are no cars, but you are actually not supposed to do it, and you should not do it :D This is Version 2<jupyter_code>'''Cohort Question 2'''
class Celsius:
    
    def __init__(self, temp):
        self._temperature = temp #<-- make one change
    
    '''getter'''
    def get_temperature(self):
        print("getter is called") #<---
        return self._temperature
    
    '''setter: takes care of validity'''
    def set_temperature(self, t):
        print("setter is called") #<----
        if( t < -273):
            self._temperature = -273
        else:
            self._temperature = t<jupyter_output><empty_output><jupyter_text>Notice the property definition is not included. So, at this stage, if your class was left as it is, it will not work with the original code that used Version 1 of your class. Instead, users would have to modify their code like this to use Version 2. This is a problem when there are many lines of code to maintain.<jupyter_code>c = Celsius(100)
c.set_temperature(-300)
print(c.get_temperature())<jupyter_output>setter is called
getter is called
-273<jupyter_text>By putting in the property definition in your class, the user of your class need not change any code, and can use the same code that makes use of Version 1 of your class.  The following cell shows Version 3 of the Celsius class with the property definition.<jupyter_code>class Celsius:
    
    def __init__(self, temp):
        self._temperature = temp #<-- make one change
    
    '''getter'''
    def get_temperature(self):
        print("getter is called") #<---
        return self._temperature
    
    '''setter: takes care of validity'''
    def set_temperature(self, t):
        print("setter is called") #<----
        if( t < -273):
            self._temperature = -273
        else:
            self._temperature = t 
    
    temperature = property(get_temperature,set_temperature)<jupyter_output><empty_output><jupyter_text>Users of your class can now go back to the original version 1 code. Everyone is happy because you modified your class but it doesn't translate into more work for those programmers who use your class ...<jupyter_code>c = Celsius(100)
c.temperature = -300
print(c.temperature)<jupyter_output>setter is called
getter is called
-273<jupyter_text>Ok, this is the end of the discussion on the property.  Side note: The __eq__ magic method is up to you to define. In an extre<jupyter_code>class Money:
    def __init__(self, value):
        self.value = value
    
    def __eq__(self, other):
        return other.value == 2*self.value
    
print(Money(100) == Money(200))
print(Money(200) == Money(100))
print(Money(100) == Money(100))<jupyter_output>True
False
False
{'fraction_non_alphanumeric': 0.08662160037729917, 'fraction_numerical': 0.016821254519729604, 'mean_word_length': 2.9862155388471177, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 28, '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': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '80520', 'license': 'MIT', 'n_tokens_mistral': 1980, 'n_tokens_neox': 1846, 'n_words': 734, 'path': 'Lesson 8 - Classes (Cohort 1 & 4).ipynb', 'repo_name': 'tlkh/DigitalWorld', 'size': '6361'}