<jupyter_start><jupyter_text>Python  Objects Everything in Python is an object Almost everything has attributes Let's prove this<jupyter_code>i = 1
type(i)
i.__doc__
i??
dir(i)<jupyter_output><empty_output><jupyter_text>Classes Classes are a way to group data and functionality all > under one roof  Properties vs. Methods Lets start with a simple example, suppose we want to be able to convert Celsius to Fahrenheit, so we write a class _Example from [here](https://www.programiz.com/python-programming/property)_  Naive Public Class<jupyter_code>class Celsius:
    """Celsius is WAY buttah then fahrenheits
    """
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        """Convert yo
        """
        return (self.temperature * (9/5)) + 32
c1 = Celsius(temperature=37)
c1.temperature
c1.to_fahrenheit()
c1.to_fahrenheit??<jupyter_output><empty_output><jupyter_text>What if we wanted to implement a limit on the temperature, as in, we can't g lower than `-273` celsius  Class With Getters and Setters<jupyter_code>class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)

    def to_fahrenheit(self):        
        return (self.get_temperature() * (9/5)) + 32

    # new update
    def get_temperature(self):
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value<jupyter_output><empty_output><jupyter_text>Lets make sure the original stuff still works<jupyter_code>c2 = Celsius(37)
c2.get_temperature()
c2.to_fahrenheit()<jupyter_output><empty_output><jupyter_text>And lets prove our new limit is in place<jupyter_code>c3 = Celsius(-277)<jupyter_output><empty_output><jupyter_text>We see the error happened, which is expected  Using Property<jupyter_code>class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    
    @property
    def to_fahrenheit(self):
        return (self.temperature * (9/5)) + 32

    def get_temperature(self):
        print("Getting value")
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value


c6 = Celsius(100)
c6.to_fahrenheit
c4 = Celsius(37)
c4.to_fahrenheit()<jupyter_output><empty_output><jupyter_text>Initialization Another initialization example<jupyter_code>class Car(object):
    def __init__(self, model, color, company, speed_limit):
        print("Initialized!")
        self.color = color
        self.company = company
        self.speed_limit = speed_limit
        self.model = model

    def start(self):
        print("Started!")
        
    def stop(self):
        print("Stopped!")

    def accelarate(self):
        print("Accelarating!")

    def change_gear(self, gear_type):
        print("Gear changed!")
car = Car("Camry", "Blue", "Toyota", "110")<jupyter_output><empty_output><jupyter_text>Create or Extend a Class "Closed for modification, open for extension" Code shouldn't be changed once being used, it should be extended. What if we had to write code to send SMSs?_Example from [here](https://hashedin.com/blog/open-closed-principle-in-python-designing-modules-part-4/)_ Lets say we wrote the following code:<jupyter_code>class SmsClient:
    def send_sms(self, phone_number, message):
        # send the SMS
        return<jupyter_output><empty_output><jupyter_text>And then later we are requested to resend the SMS if delivery failed. We could just change our code to be:<jupyter_code>class SmsClient:
    def send_sms(self, phone_number, message):
        # send the SMS
        # retry if failed
        return<jupyter_output><empty_output><jupyter_text>But this would be modifying what we already proved worked, instead we can extend our original code<jupyter_code>class SmsClientWithRetry(SmsClient):
    def __init__(self, username, password):
        super(SmsClient, self).__init__(username, password)
        
    def send_sms(self, phone_number, message):
        # this is the original sending code
        super(SmsClient, self).send_sms(phone_number, message)
        
        # this is our extension
        # retry if failed<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09319727891156462, 'fraction_numerical': 0.013378684807256236, 'mean_word_length': 3.763498920086393, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 42, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, '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': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '448555', 'license': 'MIT', 'n_tokens_mistral': 1421, 'n_tokens_neox': 1346, 'n_words': 381, 'path': 'Design_Patterns/Examples/Python/Notebook.ipynb', 'repo_name': 'GregHilston/Presentations', 'size': '4410'}