Skip to content

Instantly share code, notes, and snippets.

@spinfish
Last active March 2, 2023 21:05
Show Gist options
  • Save spinfish/3e531c255c8e5c112f6d1c8abd41b2cf to your computer and use it in GitHub Desktop.
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.
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
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