Let's start with understanding some concepts of classes.
type()
function, this function has 2 signatures, one that we saw in Chapter 2 that returns a bool
output given an object as input, another one is used to create classes.type()
function is actually a metaclass. Metaclasses are basically used to create/modify class objects. They are a complicated concept and are very rarely required to be created, so we'll not be covering them here. I found some explanations on stackoverflow, which is worth checking out if you're further interested in metaclasses.## Classes
# Define a class using the 'class' keyword followed by its name,
# class names are recommended to be in "CapitalCamelCases"
class MyClass:
# methods are defined here
def my_function(self):
# Notice: The 'self' parameter in 'myfunction()'.
# function body
pass
# Define a class with default constructor
class MyClass1:
# a constructor is defined with the special method '__init__()'
def __init__(self):
# instance variables are created with 'self.' prefix
self.my_var = 30
self.other_var = 10
# defining instance methods
def modify_vars(self):
# access instance variables using the 'self' object
print(f"my_var is {self.my_var}")
# change/define new variables inside any instance method using
# 'self' object
self.my_var = 42
self.my_var1 = 92
def return_my_var(self):
# then can use them inside another method
return self.my_var
# Define a class with parameterized constructor
class MyClass2:
# passing parameters ('para1', 'para2') and
# saving them as instance variables ('self.para1', 'self.para2')
def __init__(self, para1, para2, para3=None):
self.para1 = para1
self.para2 = para2
# here 'var1' is a method parameter
def my_func(self, var1):
return var1 + max(self.para1, self.para2)
# Note: Python defines a empty constructor automatically in background,
# if it is not defined
## Instances
# Create a instance of 'MyClass1' by adding rounded brackets
some_instance = MyClass1()
# now the variable 'some_instance' is pointing to the object of 'MyClass1'
# use the '.' dot operator to access methods/attributes of an object
print(some_instance.other_var) # 10
# if attribute is not found, 'AttributeError' is returned
# Note: Comment this line to continue program execution further
print(some_instance.other_var_42) # AttributeError
# call methods with the rounded brackets
print(some_instance.return_my_var()) # 30
some_instance.modify_vars() # my_var is 30
print(some_instance.return_my_var()) # 42
# create a new instance, pass arguments for a parameterized constructor
my_instance = MyClass2(22, 35)
# calling 'my_func()' of 'my_instance'
print(my_instance.my_func(40)) # 75
## In Python, the invocation of the instance method is operated via
# a class calling a method by passing the instance as an argument,
# so this is the same as above instance calling a method
print(MyClass2.my_func(my_instance, 40)) # 75
# the 'self' resembles the instance object, which we are passing
# as 'my_instance'
def my_fun(some_var, some_class=None):
print("Values is %d" % some_var)
# create a instance of 'some_class'
if some_class:
instance = some_class()
print(instance.my_var)
print(type(instance))
# A single liner class
class MyClass: my_var = 42
## Assigning a class to a variable
new_class = MyClass
# 'new_class' variable is now pointing to 'MyClass'
print(new_class.my_var) # 42
## Passing a class as an argument to a function
my_fun(24, MyClass) # Values is 24 \
# 42 \
# <class '__main__.MyClass'>
## Returning a class
def some_fun():
class MyClass:
my_var = 42
return MyClass
# Now calling 'some_fun()' will return a class
SomeClass = some_fun()
print(SomeClass.my_var) # 42
type()
function.# Syntax: type(Class_name, bases, attrs)
# "Class_name" is a user defined name
# "bases" is a tuple that contain parent classes
# "attrs" is a dictionary that contain attributes
## Creating a class without parents classes and parameters
MyClass = type("MyClass", (), {})
my_instance = MyClass()
print(type(my_instance)) # <class '__main__.MyClass'>
## Class with parameter 'my_var'
MyClass = type("MyClass", (), {"my_var": 42})
my_instance = MyClass()
print(my_instance.my_var) # 42
## Adding methods to a class, add 'self' as first parameter
# define methods
def my_fun(self):
return self.a
# can also define a constructor method
def __init__(self):
self.a = 34
print("constructor was called")
# and pass them just like attributes
MyClass = type("MyClass", (), {"my_var": 42, \
"my_fun": my_fun, "__init__": __init__})
my_instance = MyClass() # constructor was called
print(my_instance.my_fun()) # 34
## Every object/class in Python is created using a class of classes which is ...
print(MyClass.__class__.__class__) # <class 'type'>
print(float().__class__.__class__) # <class 'type'>
print(range(42).__class__.__class__) # <class 'type'>
## Example: Create a decorator class that limits the number of function calls
# and also maintains unique values
import math
# A class decorator should have two methods,
# 1. '__init__()' with the function to be decorated as parameter
# 2. '__call__()' for decorator being callable,
# with parameters of the decorated function
class CallLimiter:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args):
# to maintain unique values, add items to a set
args = set(args)
# checking limit of numbers
if self.call_count >= 2:
raise ValueError("Maximum calling limit reached")
self.call_count += 1
result = self.func(*args)
print("Your total is %d" % result)
# Decorate a function using '@<class_name>'
@CallLimiter
def sum_of_numbers(*args):
return sum(args)
@CallLimiter
def sum_of_sqrt(*args):
return sum([math.sqrt(a) for a in args])
## Now calling the functions would result in calling 'CallLimiter.__call__'
sum_of_numbers(34, 21, 65, 32) # Your total is 152
sum_of_numbers(34, 34, 34, 8) # Your total is 42
# Note: Comment this line to continue program execution further
sum_of_numbers(5, 34, 21, 65) # ValueError: Maximum limit reached
sum_of_sqrt(54, 20, 45, 38) # Your total is 24
sum_of_sqrt(54, 89, 12, 90, 12, 62) # Your total is 37
sum_of_sqrt(54, 20, 45, 38) # ValueError: Maximum limit reached