<jupyter_start><jupyter_text>ClassesA class is just another name for an Object Creating A ClassWhen creating classes, we have two main things to consider. The `__init__` function and the self keyword. The self keyword used within the class to refer to the class itself. For example, if a Car has an attribute of tires, you would access that within the class as `self.tires`. When referencing it from outside the Car class, you would see it as `Car.tires`Let's take a look at how these are built! \_\_init__As the name implies, the init refers to the initialization of the class. When we create the class, we typically have some attributes to give it. Thinking in our Car example, we may want to specify:* make* model* is_electric* yearGiven these data fields, we can create the function like below:<jupyter_code>class Car:
    def __init__(self, make, model, is_electric, year):
        self.make = make
        self.model = model
        self.is_electric = is_electric
        self.year = year<jupyter_output><empty_output><jupyter_text>selfAs mentioned previously, the self name is reserved to specify the class itself. Typically, class methods all require the self in the method creation. AttributesAttributes are data variables that are assigned to the class. In the example above, we are assigning attributes to the Car class from the parameters. We can refer to a classes attributes with a period after the class name followed by the attribute name. Check out the example below<jupyter_code>class Car:
    def __init__(self, make, model, is_electric, year):
        self.make = make
        self.model = model
        self.is_electric = is_electric
        self.year = year

    def __str__(self):
        if self.is_electric:
            return f"A {self.year} {self.make} {self.model} that is electric"
        else:
            return f"A {self.year} {self.make} {self.model} that is gas powered"            

tesla_model_three = Car(make="Tesla", model="3", is_electric=True, year=2020)
honda_accord = Car(make="Honda", model="Accord", is_electric=False, year=2009)

print(tesla_model_three)
print(honda_accord)<jupyter_output>A 2020 Tesla 3 that is electric
A 2009 Honda Accord that is gas powered<jupyter_text>When running the code above, you'll notice that we use the `__str__` method to print out some details about the car. When setting and calling the attributes of the Car class, we use the period to specify that we want an attributeWe can also get/update the attributes by referencing them. This can be a bit dangerous though<jupyter_code>tesla_model_three.model = 5

print(tesla_model_three)<jupyter_output>A 2020 Tesla 5 that is electric<jupyter_text>Looking at the example above, you can see that we are allowed to just change attributes which define the class. As the programmer, you may want to have further control on how this is done. PropertiesTo have further control on how attributes are controlled, we can take leverage properties. You can think of these as attributes that have further control on how they are accessed and/or updated.<jupyter_code>class Car:
    def __init__(self, make, model, is_electric, year):
        self.make = make
        self._model = model
        self.is_electric = is_electric
        self.year = year

    @property
    def model(self):
        return self._model
    
    @model.setter
    def model(self, model_value):
        if type(model_value) != str:
            raise TypeError('Model must be a string')
        else:
            self._model = model_value
        
    def __str__(self):
        if self.is_electric:
            return f"A {self.year} {self.make} {self.model} that is electric"
        else:
            return f"A {self.year} {self.make} {self.model} that is gas powered"
        
tesla_model_three = Car(make="Tesla", model="3", is_electric=True, year=2020)

print(tesla_model_three)
tesla_model_three.model = "5"
print(tesla_model_three)<jupyter_output>A 2020 Tesla 5 that is electric
{'fraction_non_alphanumeric': 0.06768998490186211, 'fraction_numerical': 0.008807247106190236, 'mean_word_length': 3.6274738067520373, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 14, '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': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '213163', 'license': 'MIT', 'n_tokens_mistral': 1152, 'n_tokens_neox': 1085, 'n_words': 485, 'path': 'lessons/lesson06-object-oriented-programming/classes.ipynb', 'repo_name': 'nairraghav/Introduction-To-Python', 'size': '3974'}