Last active
December 29, 2024 14:07
-
-
Save spinfish/3e531c255c8e5c112f6d1c8abd41b2cf to your computer and use it in GitHub Desktop.
Here we have an example of how subclasses work in Python (how to override methods of a parent class), how to set attributes for the subclass, how class methods/static methods work and how they can be useful, and a basic coverage of what the property decorator can do.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Car: | |
"""A class written to showcase what instance/static/class methods/properties are.""" | |
# All of the methods defined below (including __init__) will be inherited by subclasses. | |
# Here in the __init__ constructor there is a positional parameter, `name`, | |
# followed by two keyword-only arguments (or "kwargs"), `doors` and `max_speed`, | |
# which have default values of 4 and 150, respectively. | |
# In Python, any parameters following a * in a function definition will be kwargs. | |
def __init__(self, name: str = None, *, doors: int = 4, max_speed: int = 150): | |
# These have "_" before them as they are not intended to be used outside of this class, | |
# and are instead accessible though properties. | |
self._doors = doors | |
self._max_speed = max_speed | |
self._name = name | |
# This is an instance method, which takes the instance, `self`, as its first argument. | |
# An instance of a class is created when you call it, e.g. `Car()`. | |
def start(self): | |
"""Starts the car.""" | |
print('Starting car...') | |
# The @property decorator returns the method wrapped in a property object. | |
# Properties can be accessed on an instance via normal attribute notation (.), and if you do not | |
# set an explicit `setter` method for one and you try to assign a new value to it, it will | |
# raise AttributeError - "can't set attribute". Also, properties are not functions, and therefore | |
# are not callable (i.e. `Car(...).doors`, not `Car(...).doors()`). | |
# Property manipulation can be further customized via its getter, setter, and deleter methods. | |
# If you would like to see an example of these, I suggest you visit the gist I made about them. | |
@property | |
def doors(self): | |
"""Returns the instance's number of doors.""" | |
return self._doors | |
@property | |
def max_speed(self): | |
"""Returns the instance's max speed.""" | |
return self._max_speed | |
@property | |
def name(self): | |
"""Returns the instance's `_name`, or if this evaluates to ``False``, | |
returns the instance's class name.""" | |
return self._name or self.__class__.__name__ | |
# The @classmethod decorator on the following method transforms it into a class method. | |
# Class methods can be accessed on both the class itself and an instance, meaning that both | |
# Car.create(...) and Car().create(...) can be used. | |
# A class method receives the class as its first parameter (usually called `cls`), just like | |
# an instance method receives the instance (usually called `self`). | |
# This means that class methods have access to the class itself, but not instance variables | |
# (it cannot access variables created in __init__, for example). | |
# Here in this class method, we can call the class (`Car`) with args and kwargs (`name`, `doors`, | |
# and `max_speed`) and return this new instance. | |
@classmethod | |
def create(cls, *args, **kwargs): | |
"""Returns a new instance of Car.""" | |
return cls(*args, **kwargs) | |
# The @staticmethod decorator on the following method transforms it into a static method. | |
# Static methods do not receive an implicit first argument (`self` or `cls`). | |
# This means that they *do not* have access to the class itself nor the instance (they can't | |
# access __init__ variables or class variables), meaning they cannot modify it. | |
# I use static methods when I want a function to be associated with a class, but the | |
# `self` or `class` parameters are of no use (i.e. I don't need access to the instance | |
# nor the class). | |
# Here in this static method we can check if a car is fast or not based on a speed provided. | |
@staticmethod | |
def is_fast(speed): | |
"""Returns whether the speed is greater than 150.""" | |
return speed > 150 | |
# Here we are declaring a class that inherits from the Car class | |
class FourDoorCar(Car): | |
"""A subclass of `Car` that updates the `door` kwarg in __init__ to 4, | |
and overrides `start` to print 'The four door car is started.'""" | |
def __init__(self, *args, **kwargs): | |
# Because kwargs are transformed into a dictionary, they can be updated; here, | |
# we ensures that the kwarg `doors` is forced to be 4, since this is, after all, | |
# is a four door car. | |
kwargs.update(doors=4) | |
# In this line we are calling the super class's (in this case, `Car`) __init__ method | |
# (to ensure proper instantiation) with our updated kwargs. | |
super().__init__(*args, **kwargs) | |
# Of course, subclasses can define their own methods and properties, just like any other class | |
self._special_property = 'I am for 4 door cars only!' | |
# When a method that is defined in a base class is defined in a child class, it means that | |
# method is overridden. However, within the function definition, we can access the super class | |
# via the builtin function `super` (like we did when overriding __init__), and can access the | |
# original method from that. | |
def start(self): # Here by declaring `start` we are overriding `Car`'s method, `start` | |
super().start() # In this line we call the `start` method of the Car super-class | |
print('The four door car has started.') # And here we are adding something | |
@property | |
def foor_door_property(self): | |
"""A property just for a foor door car.""" | |
return self._special_property | |
class EightDoorCar(Car): | |
def __init__(self, *args, **kwargs): | |
kwargs.update(doors=8) | |
super().__init__(*args, **kwargs) | |
def start(self): | |
super().start() | |
print('The eight door car has started.') | |
created_car = Car.create('new car', doors=10, max_speed=5) | |
created_car.start() | |
print(f'The created car has {created_car.doors} doors.') | |
print(f'The created car has a max speed of {created_car.max_speed}.') | |
print(f"The created car's name is {created_car.name}.") | |
print(f"The created car is fast, true or false? {Car.is_fast(created_car.max_speed)}!") | |
four_door = FourDoorCar() | |
four_door.start() | |
print(f'The foor door car has {four_door.doors} doors.') | |
print(f'The foor door car has a max speed of {four_door.max_speed}.') | |
# This will print `FourDoorCar`, the name of the class, since we have not specified a `name` argument | |
print(f"The foor door car's name is {four_door.name}.") | |
# Here in this line we can use `FoorDoorCar.is_fast` because subclasses inherit all methods from their superclasses | |
print(f"This car is fast, true or false? {FourDoorCar.is_fast(four_door.max_speed)}!") | |
eight_door = EightDoorCar.create('eight door', doors=10, max_speed=1000) | |
eight_door.start() | |
print(f'The eight door car has {eight_door.doors} doors.') | |
print(f'The eight door car has a max speed of {eight_door.max_speed}.') | |
print(f"The eight door car's name is {eight_door.name}.") | |
# Remember that static methods can be called on an instance as well as the class itself | |
print(f"The eight door car is fast, true or false? {eight_door.is_fast(eight_door.max_speed)}!") | |
# Hope that helped with understanding subclasses, properties, classmethods and staticmethods! | |
# If you have any other questions feel free to DM me on Discord, my username is peanut#8714. | |
# Happy coding! :D |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Car: | |
"""A class written to showcase what instance/static/class methods/properties are.""" | |
def __init__(self, name: str = None, *, doors: int = 4, max_speed: int = 150): | |
self._doors = doors | |
self._max_speed = max_speed | |
self._name = name | |
def start(self): | |
"""Starts the car.""" | |
print('Starting car...') | |
@property | |
def doors(self): | |
"""Returns the instance's number of doors.""" | |
return self._doors | |
@property | |
def max_speed(self): | |
"""Returns the instance's max speed.""" | |
return self._max_speed | |
@property | |
def name(self): | |
"""Returns the instance's name, or if None, returns | |
the instance's class name.""" | |
return self._name or self.__class__.__name__ | |
@classmethod | |
def create(cls, *args, **kwargs): | |
"""Returns a new instance of Car.""" | |
return cls(*args, **kwargs) | |
@staticmethod | |
def is_fast(speed): | |
"""Returns whether the speed is greater than 150.""" | |
return speed > 150 | |
class FourDoorCar(Car): | |
"""A subclass of `Car` that updates the `door` kwarg in __init__ to 4, | |
and overrides `start` to print 'The four door car is started.'""" | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
self._foor_door_property = 'I am for 4 door cars only!' | |
def start(self): | |
super().start() | |
print('The four door car has started.') | |
@property | |
def foor_door_property(self): | |
"""A property just for a foor door car.""" | |
return self._foor_door_property | |
class EightDoorCar(Car): | |
def __init__(self, *args, **kwargs): | |
kwargs.update(doors=8) | |
super().__init__(*args, **kwargs) | |
def start(self): | |
super().start() | |
print('The eight door car has started.') | |
created_car = Car.create('new car', doors=10, max_speed=5) | |
created_car.start() | |
print(f'The created car has {created_car.doors} doors.') | |
print(f'The created car has a max speed of {created_car.max_speed}.') | |
print(f"The created car's name is {created_car.name}.") | |
print(f"The created car is fast, true or false? {Car.is_fast(created_car.max_speed)}!") | |
four_door = FourDoorCar() | |
four_door.start() | |
print(f'The foor door car has {four_door.doors} doors.') | |
print(f'The foor door car has a max speed of {four_door.max_speed}.') | |
print(f"The foor door car's name is {four_door.name}." | |
print(f"This car is fast, true or false? {FourDoorCar.is_fast(four_door.max_speed)}!") | |
eight_door = EightDoorCar.create('eight door', doors=10, max_speed=1000) | |
eight_door.start() | |
print(f'The eight door car has {eight_door.doors} doors.') | |
print(f'The eight door car has a max speed of {eight_door.max_speed}.') | |
print(f"The eight door car's name is {eight_door.name}.") | |
print(f"The eight door car is fast, true or false? {eight_door.is_fast(eight_door.max_speed)}!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment