When a class definition is entered, a new namespace is created, and used as the local scope — thus, all assignments to local variables go into this new namespace. In particular, function definitions bind the name of the new function here
Class objects support two kinds of operations: attribute references and instantiation.
Simple OOP examples
Here is the simplest class for Cat and two objects of this class:
<__main__.Cat object at 0x7ff57dd25360> 140692354650976
<__main__.Cat object at 0x7ff57dd25540> 140692354651456
Cat class and object is "empty" - doesn't define any attributes and methods. They even don't have their proper name.
Let's add names:
🪄 Code:
agata.name ="Agata"print(agata.name)
📟 Output:
Agata
We can assign attributes during initialization of the object - it would simplify things a lot.
The method starts with __ ("dunder") is called a magic method. __init__ is one example, __str__ - method that defines a string representation of the object - is another.
self in examples below is the reference to the object itself so we could use it in the function code - for example during str(agata) Python will call agata.__str__() which will need to return a string including agata.name, and it does this using the reference self.
Creation of an instance of the class - like calling a function (in fact it is exactly like this - firstly we calling magic method __new__() then __init__()
Inheritance from some class (called "base" or super class) allows to create a new class "borrowing" all attributes and methods definitions as they were in the super class. It allows to:
Reuse the code (DRY principle).
Create abstractions.
For example here is the class for the Robot:
classRobot: sounds = ["Beeep","Bzzzt","Oooooh"]def__init__(self,name,weigth=1000): self.weigth = weigth self.name = namedef__str__(self):"""Info about this robot"""return"Robot {obj.name} ({obj.weigth} kg)".format(obj=self)defsay(self):"""Say something"""import random returnf"{self.name} says: {random.choice(self.sounds)}"
It is completely usable:
🪄 Code:
bip =Robot("Bip 1.0")print(bip)print(bip.say())
📟 Output:
Robot Bip 1.0 (1000 kg)
Bip 1.0 says: Oooooh
We can re-use this class to create a robot from Futurama using the inheritance. For this we need to specify base/super class in parenthesis during new class definition.
classBendingRobot(Robot): sounds = ["Kill all humans","Kiss my shiny metal face","Oh, your God!","Oh wait you’re serious. Let me laugh even harder."]
As we can we still can use say method defined in the base class.
Multiple Inheritance
Python supports a limited form of multiple inheritance as well. A class definition with multiple base classes looks like this:
🪄 Code:
classA: a ="a from A"classB(A): x ="x from B"classC(A): a ="a from C" x ="x from C",classD(B,C): # change to D(C, B) and check...passd =D()print(D.__mro__)# D.mro()print(d.a, d.x)
📟 Output:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
a from C x from B
Let's enhance our Robot example by inheriting from two classes at once.
classBendingMailingRobot(Robot,Mail): sounds = ["Kill all humans","Kiss my shiny metal face","Oh, your God!","Oh wait you’re serious. Let me laugh even harder."]bender2_0 =BendingMailingRobot("Bender 2.0")bender2_0.send_message(bender2_0.say())
📟 Output:
*** SENDING MESSAGE: <<<Bender 2.0 says: Kiss my shiny metal face>>> ***
*** SENDING MESSAGE: <<<Bender 4.0 says: Kiss my shiny metal face>>> ***
Methods
Methods can be:
instance methods
class methods
static methods
Instance method
By default all methods (except of __new__ are instance methods).
The method that should be called with the instance as it's first argument. This method is bound to instance so if calling as it's method passing instance is not required. Example:
🪄 Code:
classExample:defcool_method(self):print(f"I am instance method, my instance is: {self}")ex =Example()ex.cool_method()# Example.cool_method(ex) # <-- same print(ex.cool_method)
📟 Output:
I am instance method, my instance is: <__main__.Example object at 0x7ff57df07610>
<bound method Example.cool_method of <__main__.Example object at 0x7ff57df07610>>
Class methods
The method with class as the first argument. Useful to run some code without need of creating the instace.
To mark the method as class method it is required to use builtin decorator @classmethod
Let's add some class method to Bus class. Please note that we don't need any existing buses to call general_info method on Bus2:
🪄 Code:
classBus2(Bus):# creating isolated class attributes to not use the ones from base Bus class buses_count =0 buses = [] people_transferred =0 money_collected =0@classmethoddefgeneral_info(cls):print(f"* People_transferred = {cls.people_transferred}")print(f"* Money_collected = {cls.money_collected}")print(f"* Total buses = {cls.buses_count}")print(f"* Buses = {cls.buses}")@classmethoddefremove_bus(cls,bus):try: cls.buses.remove(bus) cls.buses_count -=1print(f"{bus} removed")exceptValueError:print("No such bus registered!")Bus2.general_info()
This method doesn't require to pass instance/class at all.
To mark the method as static method it is required to use builtin decorator @staticmethod
🪄 Code:
class Example:
@staticmethod
def cool_method():
Example.attr = 123123
return "I am a static method, I don't have access to anything :("
@staticmethod
def other_stat_method(str_):
return str_[::-1]
ex = Example()
print(ex.cool_method())
print(Example.cool_method())
print(ex.other_stat_method("Radar"))
print(Example.attr)
📟 Output:
I am a static method, I don't have access to anything :(
I am a static method, I don't have access to anything :(
radaR
123123
We can use static method for creating some helper function for `Bus` class, let's add simple one for calculating money collected:
class Bus:
...
@staticmethod
def calc_money(ppl, rate):
return ppl * rate
def transfer(self, num=1):
self.people_transferred += num
self.__class__.people_transferred += num
self.money_collected += self.calc_money(num, self.rate)
self.__class__.money_collected += self.calc_money(num, self.rate)
...
Old and New classes
This chapter is only viable for Python 2 - as in Python 3 there are no such distinguishing as "old/new" classes.
Before Python 2.5 the format for creating a class was:
class Old:
pass
This was resulted in various problems with MRO and types. So some code redesigned, for this new format introduced:
class New(object):
pass
Differences:
classobj type as builtin types
Correct horizontal-then-vertical MRO (earlier it was vertical)
No __new__() method in old class
Additional functionality:
__slots__ (Python 3.x)
__getattribute__
__getattr__ and __getattribute__ don't look for magic methods in instance