Python Classes

#Python supports the object-oriented programming paradigm through classes
#class is like the blueprint for a house
#create several houses and even a complete neighborhood. Each concrete house is an object or instance that’s derived from the blueprint

#Each instance can have its own properties, such as color, owner, and interior design
#properties carry what’s commonly known as the object’s state
#Instances can also have different behaviors, such as locking the doors

# In Python, attributes are variables defined inside a class with the purpose of storing all the required data for the class to work.

#Methods are functions that you define within a class
#Attributes and methods are collectively referred to as members of a class or object

#Finally, you can use classes to build class hierarchies. This way, you’ll promote code reuse and remove repetition throughout your codebase.


				
					class ClassName:
      #classbody
      pass
				
			
				
					#As an example of how to define attributes and methods, say that you need a Circle class to model different circles in a drawing application.
#Initially, your class will have a single attribute to hold the radius. It’ll also have a method to calculate the circle’s area:
#In Python, the first argument of most methods is self
#This argument holds a reference to the current object so that you can use it inside the class

import math

class Circle:
  def __init__(self, radius): #This method is known as the object initializer because it defines and sets the initial values for your attributes
    self.radius = radius

  def calculate_area(self):
    return round(math.pi * self.radius **2, 2) #this is a method



				
			
#The action of creating concrete objects from an existing class is known as instantiation.
# In Python, the class constructor accepts the same arguments as the .__init__() method. In this example, the Circle class expects the radius argument
				
					circle_1 = Circle(42)
circle_2 = Circle(7)
				
			
				
					circle_1
				
			
#Accessing Attributes and Methods
#obj.attribute_name
#obj.method_name()
				
					circle_1.radius
				
			
				
					circle_1.calculate_area()
				
			
				
					circle_1.radius = 100
				
			
				
					circle_1.radius
				
			
#Public vs Non-Public Members
#In Python, all attributes are accessible in one way or another.

 

#Public Use the normal naming pattern. radius, calculate_area()
#Non-public Include a leading underscore in names. _radius, _calculate_area()
#https://realpython.com/python-classes/
#Name Mangling

#add two leading underscores to attribute and method names. This naming convention triggers what’s known as name mangling.
#In other words, mangled names aren’t available for direct access. They’re not part of a class’s public API.
				
					class SampleClass:
     def __init__(self, value):
         self.__value = value
     def __method(self):
         print(self.__value)
				
			
				
					sample_instance = SampleClass("Hello!")
				
			
				
					vars(sample_instance)
				
			
				
					vars(SampleClass)
				
			
				
					#both of these error out:
sample_instance.__value
sample_instance.__method()
#In this class, .__value and .__method() have two leading underscores, so their names are mangled to ._SampleClass__value and ._SampleClass__method()
#Because of this internal renaming, you can’t access the attributes from outside the class
				
			
				
					#correct way to do it
sample_instance._SampleClass__value
sample_instance._SampleClass__method()
				
			

#Classes are the building blocks of object-oriented programming in Python.

#In short, Python classes can help you write more organized, structured, maintainable, reusable, flexible, and user-friendly code.
#Class attributes: A class attribute is a variable that you define in the class body directly.
#All the objects that you create from a particular class share the same class attributes with the same original values.
#f you change a class attribute, then that change affects all the derived objects.


				
					class ObjectCounter:
     num_instances = 0
     def __init__(self):
         ObjectCounter.num_instances += 1
         #type(self).num_instances += 1
				
			
				
					ObjectCounter()
				
			
				
					ObjectCounter.num_instances
				
			
#Instance attributes: An instance is a variable that you define inside a method. Instance attributes belong to a concrete instance of a given class
				
					class Car:
    def __init__(self, make, model, year, color):
        self.make = make
        self.model = model
        self.year = year
        self.color = color
        self.started = False
        self.speed = 0
        self.max_speed = 200
				
			
				
					toyota_camry = Car("Toyota", "Camry", 2022, "Red")
				
			
				
					toyota_camry.make
				
			
				
					toyota_camry.speed
				
			
				
					ford_mustang = Car("Ford", "Mustang", 2022, "Black")
				
			
				
					ford_mustang.make
				
			
#dict attribute
#In Python, both classes and instances have a special attribute called .__dict__. This attribute holds a dictionary containing the writable members of the underlying class or instance.
				
					class SampleClass:
    class_attr = 100

    def __init__(self, instance_attr):
        self.instance_attr = instance_attr

    def method(self):
        print(f"Class attribute: {self.class_attr}")
        print(f"Instance attribute: {self.instance_attr}")
				
			
				
					SampleClass.class_attr
				
			
				
					SampleClass.__dict__
				
			
				
					SampleClass.__dict__["class_attr"]
				
			
				
					instance = SampleClass("Hello!")
instance.instance_attr
instance.method()
instance.__dict__
instance.__dict__["instance_attr"]
instance.__dict__["instance_attr"] = "Hello, Pythonista!"
instance.instance_attr

				
			

#Dynamic Class and Instance Attributes

				
					class Record:
     """Hold a record of data."""
				
			
				
					john = {
     "name": "John Doe",
     "position": "Python Developer",
     "department": "Engineering",
     "salary": 80000,
     "hire_date": "2020-01-01",
     "is_manager": False,
 }
				
			
				
					john_record = Record()

for field, value in john.items():
     setattr(john_record, field, value)

john_record.name
'John Doe'
john_record.department
'Engineering'

john_record.__dict__
{
    'name': 'John Doe',
    'position': 'Python Developer',
    'department': 'Engineering',
    'salary': 80000,
    'hire_date': '2020-01-01',
    'is_manager': False
}
				
			
				
					class User:
     pass

# Add instance attributes dynamically
jane = User()
jane.name = "Jane Doe"
jane.job = "Data Engineer"
jane.__dict__
{'name': 'Jane Doe', 'job': 'Data Engineer'}

# Add methods dynamically
def __init__(self, name, job):
     self.name = name
     self.job = job

User.__init__ = __init__

User.__dict__

linda = User("Linda Smith", "Team Lead")
linda.__dict__
				
			
#you’ve just used a pass statement as a placeholder, which is Python’s way of doing nothing.
#Even though this capability of Python may seem neat, you must use it carefully because it can make your code difficult to understand and reason about.
				
					#validate the radius to ensure that it only stores positive numbers. How would you do that without changing your class interface?
#The quickest approach to this problem is to use a property and implement the validation logic in the setter method.

import math

class Circle:
    def __init__(self, radius):
        self.radius = radius


    #To turn an existing attribute like .radius into a property, you typically use the @property decorator to write the getter method
    # The getter method must return the value of the attribute. In this example, the getter returns the circle’s radius, which is stored in the non-public ._radius attribute.
    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if not isinstance(value, int | float) or value <= 0:
            raise ValueError("positive number expected")
        self._radius = value

    def calculate_area(self):
        return round(math.pi * self._radius**2, 2)
				
			
				
					circle_1 = Circle(100)
circle_1.radius
				
			
				
					circle_1.radius = 0
				
			
				
					circle_2 = Circle(-100)
				
			
				
					class Square:
    def __init__(self, side):
        self.side = side

    @property
    def side(self):
        return self._side

    @side.setter
    def side(self, value):
        if not isinstance(value, int | float) or value <= 0:
            raise ValueError("positive number expected")
        self._side = value

    def calculate_area(self):
        return round(self._side**2, 2)
				
			
				
					
class PositiveNumber:
    def __set_name__(self, owner, name):
        self._name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, int | float) or value <= 0:
            raise ValueError("positive number expected")
        instance.__dict__[self._name] = value

class Circle:
    radius = PositiveNumber()

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

    def calculate_area(self):
        return round(math.pi * self.radius**2, 2)

class Square:
    side = PositiveNumber()

    def __init__(self, side):
        self.side = side

    def calculate_area(self):
        return round(self.side**2, 2)
				
			
				
					circle = Circle(100)
				
			
				
					circle.radius = 0
				
			
#https://realpython.com/python-classes/
#time to watch youtube videos

Ryan is a Data Scientist at a fintech company, where he focuses on fraud prevention in underwriting and risk. Before that, he worked as a Data Analyst at a tax software company. He holds a degree in Electrical Engineering from UCF.

Leave a Reply

Your email address will not be published. Required fields are marked *