class Car:
"Car Class"
# Class Attribute
= "Electric"
fuel # initialize dunder
def __init__(self, color: str, mileage: int):
self.color = color
self.mileage = mileage
# Print dunder
def __str__(self) -> str:
return f"color: {self.color}, mileage: {self.mileage}"
# Instance method
def drive(self):
return f"Ventured {self.mileage} miles"
19 OOP in Python
19.1 Class
19.1.1 Define Class
19.1.2 Create instance
Create instance of a class Car
= Car("blue", 20000)
car_blue print(car_blue)
#> color: blue, mileage: 20000
# Doc string
car_blue.__doc__#> 'Car Class'
car_blue.__dict__#> {'color': 'blue', 'mileage': 20000}
Access Attribute
car_turbo.fuel#> NameError: name 'car_turbo' is not defined
Access method
car_turbo.drive()#> NameError: name 'car_turbo' is not defined
19.1.3 Class attribute vs Instance attribute
- Class Attributes:
- Defined directly in the class body.
- Shared across all instances of the class.
- Accessed using the class name or through an instance.
- Changes to a class attribute affect all instances that haven’t overridden the attribute.
Example:
class Manual:
= "hi"
A = "there"
B
# Accessing class attributes
print(Manual.A) # Output: hi
#> hi
print(Manual.B) # Output: there
#> there
# Creating instances
= Manual()
m1 = Manual()
m2
# Accessing class attributes through instances
print(m1.A)
#> hi
print(m2.A)
#> hi
# Modifying class attribute
= "hello"
Manual.A print(m1.A)
#> hello
print(m2.A)
#> hello
- Instance Attributes:
- Defined within the
__init__
method. - Unique to each instance of the class.
- Accessed using the instance name.
- Changes to an instance attribute affect only that instance.
- Defined within the
Example:
class Manual:
def __init__(self):
self.A = "hi"
self.B = "there"
# Creating instances
= Manual()
m1 = Manual()
m2
# Accessing instance attributes
print(m1.A)
#> hi
print(m2.A)
#> hi
# Modifying instance attribute
= "hello"
m1.A print(m1.A)
#> hello
print(m2.A)
#> hi
19.1.3.1 Key Differences
- Scope and Sharing:
- Class Attributes: Shared by all instances of the class. If you change a class attribute, the change is reflected in all instances unless overridden.
- Instance Attributes: Unique to each instance. Changing an instance attribute affects only that particular instance.
- Definition and Initialization:
- Class Attributes: Defined directly in the class body, outside any methods.
- Instance Attributes: Defined within the
__init__
method, which is called when a new instance of the class is created.
- Usage Context:
- Class Attributes: Useful for constants or attributes that should be shared across all instances.
- Instance Attributes: Used for attributes that need to be unique to each instance, such as data specific to that instance.
19.1.3.2 Summary
- Use class attributes when you want to share data across all instances of the class.
- Use instance attributes when you need each instance of the class to have its own unique data.
19.1.4 Interitance
class Parent:
= "brown"
hair_color
class Child(Parent):
pass
= Child()
ch1
ch1.hair_color#> 'brown'
Overwrite Parent
class Parent:
= "brown"
hair_color
class Child(Parent):
= "purple" # Overwrite hair_color
= Child()
ch2
ch2.hair_color#> 'purple'
Extend Parent Attribute
class Parent:
= ["English"]
speaks
class Child(Parent):
def __init__(self):
super().__init__()
self.speaks.append("German")
= Child()
ch3
ch3.hair_color#> AttributeError: 'Child' object has no attribute 'hair_color'
# Check parent class
type(ch3)
#> <class '__main__.Child'>
isinstance(ch3, Parent)
#> True
19.1.5 Multiple Child from Parent Class
Parent Class
class Dog:
= "Canis familiaris"
species
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound="..."):
return f"{self.name} says {sound}"
Child Class
Each dog breed bark differently
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound)
class Bulldog(Dog):
def speak(self, sound="Woof"):
return super().speak(sound)
class Dachshund(Dog):
pass
= JackRussellTerrier("Miles", 4)
miles = Dachshund("Buddy", 9)
buddy = Bulldog("Jack", 3)
jack = Bulldog("Jim", 5) jim
miles.speak()#> 'Miles says Arf'
buddy.speak()#> 'Buddy says ...'
19.2 Class Method (Dunder)
19.2.1 String Representation __repr__
, __str__
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x!s}, {0.y!s})'.format(self)
= Pair(3, 4)
p
p#> Pair(3, 4)
the special !r formatting code indicates that the output of __repr__()
should be used
print('p is {0!r}'.format(p))
#> p is Pair(3, 4)
print('p is {0}'.format(p))
#> p is (3, 4)
19.2.2 String Formatting __format__()
= {
_formats 'ymd' : '{d.year}-{d.month}-{d.day}',
'mdy' : '{d.month}/{d.day}/{d.year}',
'dmy' : '{d.day}/{d.month}/{d.year}'
}
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == '':
= 'ymd'
code = _formats[code]
fmt return fmt.format(d=self)
= Date(2012, 12, 21)
d format(d)
#> '2012-12-21'
format(d, 'mdy')
#> '12/21/2012'
'The date is {:ymd}'.format(d)
#> 'The date is 2012-12-21'
19.2.3 Setter / Getter
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function (optional)
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
= Person('Guido')
a
a.first_name#> 'Guido'
= 42
a.first_name #> TypeError: Expected a string