- Published on
Python Classes
- Authors
- Name
- João Pedro Martins
- @jmrtins82
Python supports the object-oriented programming (OOP) paradigm through classes. They provide an elegant way to define reusable pieces of code that encapsulate data and behavior in a single entity. With classes, you can quickly and intuitively model real-world objects and solve complex problems.
Table of Contents
- Defining a Class in Python
- Class Inheritance
- @staticmethod and @classmethod decorators
- How to built your decorator
- How to built decorators with params for classes
Defining a Class in Python
In python everything is an object, including classes. Data Classes
are the building blocks of a Data Model
. Within each Data Class lies several Data Elements
and these are the descriptions of an individual field
, variable
, column
or property
. They also can have functions called methods
which are used to manipulate the data within the class.
You can also have a Data Class within a Data Class, known as a Nested Data Class
, which can be a useful way of managing complex sets of data. There is no limit on the number of Nested Data Classes you can include.
class Device:
def __init__(self, name, connected_by):
self.name = name
self.connected_by = connected_by
self.connected = True
def __str__(self):
return f"Device {self.name!r} ({self.connected_by})"
def __repr__(self):
return f"Device: ({self.name})"
def disconnect(self):
self.connected = False
print(f"{self.name} disconnected.")
def main():
printer = Device("Printer", "USB")
print(printer) # Device 'Printer' (USB)
print(repr(printer)) # Device: (Printer)
if __name__ == "__main__":
main()
__init__()
: This is the constructor method. It is called when an object is created from the class and it allows the class to initialize the attributes of the class.
__str__()
: This is the string representation method. It is called when the object is passed to the print() function or when str() is called on the object. It allows the class to define a string representation of the object.
__repr__()
: This is the object representation method. It is called when the object is passed to the repr() function. It allows the class to define an object representation of the object.
disconnect()
: This is a method that allows the device to disconnect from the network.
Class Inheritance
Inheritance
allows us to define a class that inherits all the methods
and properties
from another class.
Parent class
is the class being inherited from, also calledbase class
.Child class
is the class that inherits from another class, also calledderived class
.
...
class Printer(Device):
def __init__(self, name, connected_by, capacity):
super().__init__(name, connected_by)
self.capacity = capacity
self.remaining_pages = capacity
def __str__(self):
return f"{super().__str__()} ({self.remaining_pages} pages remaining)"
def print(self, pages):
if not self.connected:
raise TypeError("Device is disconnected at this time, cannot print.")
print(f"Printing {pages} pages.")
self.remaining_pages -= pages
def main():
printer = Printer("Printer", "USB", 500) # output: Device 'Printer' (USB) (500 pages remaining)
printer.print(20) # output: Printing 20 pages.
print(printer) # output: Device 'Printer' (USB) (480 pages remaining)
printer.print(50) # output: Printing 50 pages.
print(printer) # output: Device 'Printer' (USB) (430 pages remaining)
printer.disconnect() # output: Printer disconnected.
printer.print(30) # output: Error: Device is disconnected at this time, cannot print.
Device
is a Parent class. A device has a name and it can be connected by either USB or Ethernet. It can also be connected or disconnected. It is a base class
for other devices.
Printer
is a device or a Child class of the Device
parent class. Also has a name, can be connected by either USB or Ethernet, and also can be connected or disconnected. However, has an additional parameter, an original capacity of printing a specific amount of pages. When we call the method print, the device prints the number of pages and reduces from the total capacity of printing. It is a derived class
from the Device
parent class.
@staticmethod and @classmethod Decorators
A decorator
is a function that takes another function as input, extends its behavior, and returns a new function
as output. This is possible because, in Python, functions are first-class objects
, which means they can be passed as arguments to functions and also be returned from functions, just as other types of objects such as string
, int
, or float
. A decorator
can be used to decorate a function or a class.
@staticmethod
, @classmethod
and @property
are “magical” decorators and can become very handy for our development work and make your code more clean.
@staticmethod
: a static method is a method that does not require the creation of an instance of a class. For Python, it means that the first argument of a static method is notself
, but a regular positional or keyword argument. Also, astatic method
can have no arguments at all. Static methods are used to just place a method inside a class because you feel it belongs there (i.e. for code organisation, mostly!)
class Cellphone:
def __init__(self, brand, number):
self.brand = brand
self.number = number
@property
def number(self):
_number = "-".join([self.number[:3], self.number[3:6], self.number[6:]])
return _number
@staticmethod
def get_emergency_number():
return "911"
def main() -> None:
Cellphone.get_emergency_number() # '911'
@classmethod
a class method is created with the @classmethod decorator and requires the class itself as the first argument, which is written ascls
. A class method normally works as afactory method
and returns aninstance
of the class with supplied arguments. However, it doesn't always have to work as a factory class and return an instance. You can create an instance in the class method, do whatever you need, and don’t have to return it.
class Cellphone:
def __init__(self, brand, number):
self.brand = brand
self.number = number
@property
def number(self):
_number = "-".join([self.number[:3], self.number[3:6], self.number[6:]])
return _number
@staticmethod
def get_emergency_number():
return "911"
@classmethod
def iphone(cls, number):
_iphone = cls("Apple", number)
print("An iPhone is created.")
return _iphone
def main() -> None:
iphone = Cellphone.iphone("1112223333") # An iPhone is created.
iphone.number() # "111-222-3333"
iphone.get_emergency_number() # "911"
iphone.name # "Apple"
class CrawlSpider(Spider):
rules: Sequence[Rule] = ()
...
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = super().from_crawler(crawler, *args, **kwargs)
spider._follow_links = crawler.settings.getbool(
'CRAWLSPIDER_FOLLOW_LINKS', True)
return spider
@property
is the decorator that turns a specific function to return a property of the class object. You cannot change a property unless you setter conditions to change a property.
class Cellphone:
def __init__(self, brand, number):
self.brand = brand
self.number = number
@property
def number(self):
_number = "-".join([self.number[:3], self.number[3:6], self.number[6:]])
return _number
@number.setter
def number(self, number):
if len(number) != 10:
raise ValueError("Invalid phone number.")
self._number = number
....
def main()-> None:
cellphone = Cellphone("Samsung", "1112223333")
print(cellphone.number) # 111-222-3333
How to built your decorator
A decorator is a function that returns another function if pre-set of conditions applies. We can build a safety decorator such as @login_required decorator from the Flask-Login package.
from typing import Callable, Any
def get_admin_password():
return "1234"
def make_secure(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
def secure_function():
if user["access_level"] == "admin":
return func()
else:
return f"No admin permissions for {user['username']}."
return secure_function
# `get_admin_password` is now `secure_func` from below
get_admin_password = make_secure(get_admin_password)
user = {"username": "jose", "access_level": "guest"}
print(get_admin_password()) # No admin permissions for jose.
user = {"username": "bob", "access_level": "admin"}
print(get_admin_password()) # 1234
How to built decorators with params for classes
import functools
# create make_secure decorator for classes
def make_secure(func):
@functools.wraps(func)
def secure_function(*args, **kwargs):
if user["access_level"] == "admin":
return func(*args, **kwargs)
else:
return f"No admin permissions for {user['username']}."
return secure_function
@make_secure
def get_password(role: str):
if role == "admin":
return "1234"
elif role == "billing":
return "super_secure_password"
user = {"username": "jose", "access_level": "guest"}
print(get_password("admin")) # No admin permissions for jose.
print(get_password("billing")) # No admin permissions for jose.
user = {"username": "bob", "access_level": "admin"}
print(get_password("admin")) # 1234
print(get_password("billing")) # super_secure_password.