Everything is object in Python. Even primitive data type such as integer and string are object. Function is also an object.
In this tutorial, you will learn about:
Function isinstance() tests if an object is an instance of a certain class, or inherited from a class. The syntax is
isinstance(object, Class)
print(isinstance(2,int))
print(isinstance(2,object))
print(isinstance('abc',str))
print(isinstance('abc',object))
True True True True
We can get the help on the class or object
help(object)
Help on class object in module builtins: class object | The base class of the class hierarchy. | | When called, it accepts no arguments and returns a new featureless | instance that has no instance attributes and cannot be given any. | | Built-in subclasses: | anext_awaitable | ArgNotFound | async_generator | async_generator_asend | ... and 113 other subclasses | | Methods defined here: | | __delattr__(self, name, /) | Implement delattr(self, name). | | __dir__(self, /) | Default dir() implementation. | | __eq__(self, value, /) | Return self==value. | | __format__(self, format_spec, /) | Default object formatter. | | __ge__(self, value, /) | Return self>=value. | | __getattribute__(self, name, /) | Return getattr(self, name). | | __gt__(self, value, /) | Return self>value. | | __hash__(self, /) | Return hash(self). | | __init__(self, /, *args, **kwargs) | Initialize self. See help(type(self)) for accurate signature. | | __le__(self, value, /) | Return self<=value. | | __lt__(self, value, /) | Return self<value. | | __ne__(self, value, /) | Return self!=value. | | __reduce__(self, /) | Helper for pickle. | | __reduce_ex__(self, protocol, /) | Helper for pickle. | | __repr__(self, /) | Return repr(self). | | __setattr__(self, name, value, /) | Implement setattr(self, name, value). | | __sizeof__(self, /) | Size of object in memory, in bytes. | | __str__(self, /) | Return str(self). | | ---------------------------------------------------------------------- | Class methods defined here: | | __init_subclass__(...) from builtins.type | This method is called when a class is subclassed. | | The default implementation does nothing. It may be | overridden to extend subclasses. | | __subclasshook__(...) from builtins.type | Abstract classes can override this to customize issubclass(). | | This is invoked early on by abc.ABCMeta.__subclasscheck__(). | It should return True, False or NotImplemented. If it returns | NotImplemented, the normal algorithm is used. Otherwise, it | overrides the normal algorithm (and the outcome is cached). | | ---------------------------------------------------------------------- | Static methods defined here: | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature. | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __class__ = <class 'type'> | type(object) -> the object's type | type(name, bases, dict, **kwds) -> a new type
Python has many build-in functions that always available. You can check all build-in using
dir(__builtins__)
or
__builtins__.__dict__
dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EncodingWarning', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__IPYTHON__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display', 'divmod', 'enumerate', 'eval', 'exec', 'execfile', 'filter', 'float', 'format', 'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'runfile', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
To list all the properties of an object, you can use vars(objName) which is the same as __dict__
vars(object)
mappingproxy({'__new__': <function object.__new__(*args, **kwargs)>, '__repr__': <slot wrapper '__repr__' of 'object' objects>, '__hash__': <slot wrapper '__hash__' of 'object' objects>, '__str__': <slot wrapper '__str__' of 'object' objects>, '__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>, '__setattr__': <slot wrapper '__setattr__' of 'object' objects>, '__delattr__': <slot wrapper '__delattr__' of 'object' objects>, '__lt__': <slot wrapper '__lt__' of 'object' objects>, '__le__': <slot wrapper '__le__' of 'object' objects>, '__eq__': <slot wrapper '__eq__' of 'object' objects>, '__ne__': <slot wrapper '__ne__' of 'object' objects>, '__gt__': <slot wrapper '__gt__' of 'object' objects>, '__ge__': <slot wrapper '__ge__' of 'object' objects>, '__init__': <slot wrapper '__init__' of 'object' objects>, '__reduce_ex__': <method '__reduce_ex__' of 'object' objects>, '__reduce__': <method '__reduce__' of 'object' objects>, '__subclasshook__': <method '__subclasshook__' of 'object' objects>, '__init_subclass__': <method '__init_subclass__' of 'object' objects>, '__format__': <method '__format__' of 'object' objects>, '__sizeof__': <method '__sizeof__' of 'object' objects>, '__dir__': <method '__dir__' of 'object' objects>, '__class__': <attribute '__class__' of 'object' objects>, '__doc__': 'The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.\n'})
# we can also put the attributes into strings of key and value separated by commas
attrs=object.__dict__
print(', '.join("%s: %s" % item for item in attrs.items()))
__new__: <built-in method __new__ of type object at 0x00007FFC8E5AABB0>, __repr__: <slot wrapper '__repr__' of 'object' objects>, __hash__: <slot wrapper '__hash__' of 'object' objects>, __str__: <slot wrapper '__str__' of 'object' objects>, __getattribute__: <slot wrapper '__getattribute__' of 'object' objects>, __setattr__: <slot wrapper '__setattr__' of 'object' objects>, __delattr__: <slot wrapper '__delattr__' of 'object' objects>, __lt__: <slot wrapper '__lt__' of 'object' objects>, __le__: <slot wrapper '__le__' of 'object' objects>, __eq__: <slot wrapper '__eq__' of 'object' objects>, __ne__: <slot wrapper '__ne__' of 'object' objects>, __gt__: <slot wrapper '__gt__' of 'object' objects>, __ge__: <slot wrapper '__ge__' of 'object' objects>, __init__: <slot wrapper '__init__' of 'object' objects>, __reduce_ex__: <method '__reduce_ex__' of 'object' objects>, __reduce__: <method '__reduce__' of 'object' objects>, __subclasshook__: <method '__subclasshook__' of 'object' objects>, __init_subclass__: <method '__init_subclass__' of 'object' objects>, __format__: <method '__format__' of 'object' objects>, __sizeof__: <method '__sizeof__' of 'object' objects>, __dir__: <method '__dir__' of 'object' objects>, __class__: <attribute '__class__' of 'object' objects>, __doc__: The base class of the class hierarchy. When called, it accepts no arguments and returns a new featureless instance that has no instance attributes and cannot be given any.
The code below uses build-in functions to show all callable methods of an object that would be inherited in all classes.
obj=object
[method for method in dir(obj) if callable(getattr(obj, method))]
['__class__', '__delattr__', '__dir__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Class is the blueprint design of an object. Object is an instance of a class.
Every class (and therefore every object) would have method and property. Thus, in simple term,
object = methods + properties
A method is basically a function or an operation. We have to use parenthesis to call a function. Class methods determine the behavior of the objects of that class. Thus, whenever you hear the word "method", it is equivalent to "function" or "behavior". The syntax to define a class method is
def methodName(self, parameters):
The first parameter of any class method is usually called self. This parameter is a reference to the current instance of the class. We can replace the name self into any other name as long as it is the first parameter.
Class property is the attribute or the data. This is basically a constant or a variable. We don't use parenthesis to call the property. Whenever you hear the word "attribute", it is equivalent to "property" or "data". The syntax to set the value of attribute is
object.attribute=value
The syntax to get the value of attribute is
object.attribute
When we design a rectangle class, we know that every rectangle would have width, height and area. two rectangle objects, A and B may have different width, height and area but they share the same design of rectangle class.
To define the input parameters of a class, we use a special method call __init__(). This constructor and initializer function is called automatically every time the class is being used to create a new object.
class Rectangle():
'''
rectangle geometry
'''
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rectangleA=Rectangle(width=2,height=4)
print('Area rectangle A is ',rectangleA.area())
rectangleB=Rectangle(width=3,height=7.5)
print('Area rectangle B is ',rectangleB.area())
Area rectangle A is 8 Area rectangle B is 22.5
Python support doctstring. Thus, the comments we put after the class definition would be automatically put into the help.
help(Rectangle)
Help on class Rectangle in module __main__: class Rectangle(builtins.object) | Rectangle(width, height) | | rectangle geometry | | Methods defined here: | | __init__(self, width, height) | Initialize self. See help(type(self)) for accurate signature. | | area(self) | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
The idea of encapsulation is to bundle data and functions that operate on that data into a single unit called object while hiding the details on how they work together. The programmer can use class without knowing the details of how it was implemented.
Using object, the idea is to hide the internal representation, or state, of an object from the outside. This idea is called information hiding. Encapsulation prevents from accessing accidentally (unintentionally). The information hiding can be done through private methods and private properties.
Private attributes can only be modified through intentionally defined setter method and accessed explicitly only through getter method.
class Vehicle():
def __init__(self):
self._maxSpeed=0
# setter method
def setMaxSpeed(self,maxSpeed):
self._maxSpeed=maxSpeed
# getter method
def getMaxSpeed(self):
return self._maxSpeed
help(Vehicle)
Help on class Vehicle in module __main__: class Vehicle(builtins.object) | Methods defined here: | | __init__(self) | Initialize self. See help(type(self)) for accurate signature. | | getMaxSpeed(self) | # getter method | | setMaxSpeed(self, maxSpeed) | # setter method | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
All methods and attributes| in a class are public. It means we can always access the class method.
Private method cannot be accessed outside the class. We make agreement regarding private method and private property by adding single or double underscore prefix before the method name or the property name. The private method and property are not actually private, internally Python is just rename it such that it cannot be accessed easily. If we know how Python rename it, we can still access it from outside the class.
class Example():
def __init__(self):
self.__privateProperty="abc"
def myPublicMethod(self,num):
return self.__privateMethod(num)
def __privateMethod(self,num):
return str(num)+"a"
try:
obj=Example()
print(obj.myPublicMethod(20))
print(obj.__privateMethod(20)) # private method
except Exception as e:
print(e)
try:
print(obj.__privateProperty) # private property
except Exception as e:
print(e)
20a 'Example' object has no attribute '__privateMethod' 'Example' object has no attribute '__privateProperty'
obj.__dict__ # private method and property are not actually private
{'_Example__privateProperty': 'abc'}
obj=Example()
obj._Example__privateProperty # if we know how Python rename it, we can still access it
'abc'
# we can list all methods in class Example
dir(Example)
['_Example__privateMethod', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myPublicMethod']
A class can inherit another class. Using inheritance, we can reuse the parent's properties and methods. When a Derived class inherits from a Base class, all attributes and methods of Base are automatically inherited by Derived. Derived can overide some attributes or methods of the Base class.
Inheritance models an is a relationship. When a Derived class inherits from a Base class, we created a relationship where Derived is a specialized version of Base. The Derived class is also called the Child class while the Base class is also called Parent class. The child class is said to derive, inherit, or extend a parent class.
For example: horse is an animal, car is a vehicle, rectangle is a shape, teacher is a person.
The syntax of inheritance is
class ChildClass(ParentClass):
All classes in Python are inherited implicitly from object class. Thus,
class Rectangle():
is equal to
class Rectangle(object):
Inheritance mechanism will create class hierarchies.
A Square is a Rectangle with equal side. Thus, Square inherits Rectangle. When a class Square inherits from Rectangle class, Square inherits the attributes and methods of Rectangle class. In the example, below we never define area in class Square but we can print it.
class Square(Rectangle):
def __init__(self, side):
# accessing parent's class function
Rectangle.__init__(self, side, side)
squareC = Square(4)
print('Area square C is ', squareC.area) # area is inherited from Rectangle
Area square C is <bound method Rectangle.area of <__main__.Square object at 0x0000026ABB2B32E0>>
As an alternative, you can also access the parent method using
super(childClass,self).method(parameters)
class Square(Rectangle):
def __init__(self, side):
# accessing parent's class function
super(Square, self).__init__(side, side)
squareC = Square(4)
print('Area square C is ', squareC.area) # area is inherited from Rectangle
Area square C is <bound method Rectangle.area of <__main__.Square object at 0x0000026ABB2B3F10>>
We can still shorten the super()
class Square(Rectangle):
def __init__(self, side):
# accessing parent's class function
super().__init__(side, side)
squareC = Square(4)
print('Area square C is ', squareC.area) # area is inherited from Rectangle
Area square C is <bound method Rectangle.area of <__main__.Square object at 0x0000026ABB1D4B20>>
A parent class can have multiple class children.
class Person():
def __init__(self, firstName, lastName):
self.name = "{} {}".format(firstName, lastName)
class Teacher(Person):
def teach(self, subject):
self.course = subject
class Student(Person):
def learn(self, subject):
self.course = subject
me = Teacher("Kardi", "Teknomo") # access init of the parent
me.teach("Computer Science")
he = Student("Roy", "Vanderbilt") # access init of the parent
he.learn("Mathematics")
print(me.name, "teaches", me.course)
print(he.name, "studies", he.course)
Kardi Teknomo teaches Computer Science Roy Vanderbilt studies Mathematics
To test if a certain class inherited from another parent class, we can use
issubclass(ChildClass, ParentClass)
print( issubclass(Student, Person))
True
To test if an object is also inherited from a certain class, we can use
isinstance(ChildObject, ThisClassOrParentClass)
print( isinstance(Teacher("John","Little"), Person))
print( isinstance(Teacher("John","Little"), Teacher))
print( isinstance(Teacher("John","Little"), object))
True True True
Python allows multiple inheritance. It means a child class can have multiple parent classes. The syntax of multiple inheritance is
class ChildClass(ParentClass1,ParentClass2,ParentClass3,...):
Multiple inheritance could lead to Diamond Problem. Python solves this problem through Method Resolution Order (MRO) algorithm such that the priority of the parent is from left to right order.
In the example below, notice that Base is called twice, one by Left and one by Right. This type of problem can lead to a bug such as deposited to the same bank account twice unintentionally.
# example of diamond problem
class Base():
num_base=0
def callMe(self):
print("calling Base")
self.num_base+=1
class Left(Base):
num_left=0
# overide callMe of Base
def callMe(self):
Base.callMe(self)
print("calling Left")
self.num_left+=1
class Right(Base):
num_right=0
# overide callMe of Base
def callMe(self):
Base.callMe(self)
print("calling Right")
self.num_right+=1
class Bottom(Left,Right):
num_bottom=0
# overide callMe of which one? Base, Left, or Right?
def callMe(self):
Left.callMe(self)
Right.callMe(self)
print("calling Bottom")
self.num_bottom+=1
b=Bottom()
b.callMe()
print(b.num_bottom,b.num_left,b.num_right,b.num_base)
calling Base calling Left calling Base calling Right calling Bottom 1 1 1 2
The solution of Diamond problem is to use super() rather than calling the sane of the parent class directly. Compare the following code with the above code.
# example of solution of diamond problem
class Base():
num_base=0
def callMe(self):
print("calling Base")
self.num_base+=1
class Left(Base):
num_left=0
# overide callMe of Base
def callMe(self):
super().callMe()
print("calling Left")
self.num_left+=1
class Right(Base):
num_right=0
# overide callMe of Base
def callMe(self):
super().callMe()
print("calling Right")
self.num_right+=1
class Bottom(Left,Right):
num_bottom=0
# let the MRO select which super to be called
def callMe(self):
super().callMe()
print("calling Bottom")
self.num_bottom+=1
b=Bottom()
b.callMe()
print(b.num_bottom,b.num_left,b.num_right,b.num_base)
calling Base calling Right calling Left calling Bottom 1 1 1 1
Polymorphism means having more than one form. Polymorphism is an ability to use a common interface for multiple forms. The same method, may behave differently or accepting different input depending on the type of object. Polymorphism is useful to simplify our code in handling various data type and allow us to write more generic functions and classes.
For example, obj[1:4] produces three characters if the obj is a string and it produces three elements of a list if the obj is a list. Similarly, left+right would produce a number, string or list depending on the type of left and right.
To use polymorphism in Python, we need to create common interface that accept object from different classes that contain the same method name.
In the example below, the same sound() method would have different behavior depending on the class of the object that we inputted.
class Cat():
def sound(self):
return "meauw"
class Dog():
def sound(self):
return "wof-wof"
# common interface
def soundTest(pet):
return pet.sound()
kitty = Cat()
buddy = Dog()
print("kitty sounds", soundTest(kitty))
print("buddy sounds", soundTest(buddy))
kitty sounds meauw buddy sounds wof-wof
Aside from inheritance, which model is-a relationship, the relationship between classes can also be modeled as either composition or aggregation. In Composition or Aggregation we have Container (as if it is the parent class, the Component) and Part (as if it is the child class, the parts or the Composite).
What is the difference between composition and aggregation? In composition, if we delete the Container object, then the Part object is automatically deleted. In aggregation, Container and Part objects are living independently. Deleting Container does not delete the Part. The Part knows nothing about the Container.
Aggregation models consist of relationship.
For example, a car consists of a chassis, an engine, wheels, and seats. If the car object is deleted, the wheels may still live and be used for other cars. A chess consists of a board and chess pieces.
Composition models has-a relationship. The Container strongly owns Part. When a class Container contains an object of another class Part, this relationship means that a Container has a Part.
For example, a dog has a tail, a chess board has 64 squares. If the dog dies, the tail is buried together with the dog. If the chessboard is deleted, all the 64 squares are also deleted.
# example of Composition
class Component():
def __init__(self):
self._name="component"
@property
def name(self):
return self._name
class Composite():
def __init__(self):
self._name="composite"
self.comp=Component()
def print(self):
return self._name+" is using "+self.comp.name
part=Composite()
part.print()
'composite is using component'
# example of Aggregation
class Component():
def __init__(self):
self._name="component"
@property
def name(self):
return self._name
class Composite():
def __init__(self):
self._name="composite"
def print(self, componentName):
return self._name+" is using "+componentName
container=Component()
part=Composite()
part.print(container.name)
'composite is using component'
last update: 04 June 2023