Object-Oriented Programming in Python
Riley Dunnaway
Mathematician at U.S. Bank | Quantitative Finance | Algorithmic Trading | Research | Investing
Python is a versatile and powerful programming language known for its simplicity and readability. One of the key features that make it suitable for a wide range of applications is its support for object-oriented programming. Whether you're a Python novice or have a little more experience, object-oriented programming can help you mature your coding skills.
Object-oriented programming?is a programming method that organizes code around objects rather than functions. The quintessential illustration of object-oriented programming uses a car as a metaphor. A car can be thought of as an object. The car has various attributes associated with it such as color, model, engine, and year. Every single car is going to inherit these same types of attributes due to the nature of all cars having color, model, engine, and year. This is the essence of a programming object.
To provide a real example with code, take a look at the code below. This code defines a class called "Actor". Within the class definition, we see various attributes such as "get_attack_power", and "is_alive", and we see name, level, and hp. Seemingly, these are the attributes of some video game character!
from random import randint
class Actor:
def __init__(self, name, level):
self.name = name
self.level = level
self.hp = 100 + 3 * level
self.full_hp = self.hp
def get_attack_power(self):
return randint(1, 50) + 5*self.level
def get_defense(self):
return randint(1, 25) + self.level*self.level
def __repr__(self):
return 'Actor: {}, Level:{}'.format(self.name, self.level)
def is_alive(self):
return self.hp > 0
def attacks(self, other):
raise NotImplementedError()
def stats(self):
print(' {} has {} health.'.format(self.name, self.hp))
This code illustrates perfectly how a class is given certain attributes and functions that act on these attributes. The benefits of object-oriented programming, however, are not limited to just the organization of ideas. Classes allow for something called?inheritance.
Examine the code below.
class Player(Actor):
def heal(self):
self.hp += round((self.full_hp - self.hp) / 3)
def attacks(self, enemy):
power = self.get_attack_power()
enemy_defense = enemy.get_defense()
damage = power - enemy_defense
if power > enemy_defense:
print('{} hit the {} for {} damage!'.format(self.name, enemy.kind, damage))
enemy.hp -= damage
if enemy.hp <= 0:
print('{} slayed the {}!'.format(self.name, enemy.kind))
return True
else:
print('The {} appears unaffected by your attack...'.format(enemy.kind))
return False
We have defined another object, this one called "Player". We see similar organization to the "Actor" class before, but careful observation reveals the "Player" class is using variables not explicitly defined within the object! How is it possible that the "Player" class can use attributes like hp when we haven't defined hp for the Player? This is because "Player" is defined as a child class of "Actor". As such, the "Player" inherits all of the attributes and functions defined in the "Actor" class before! Hp, name, and get_attack_power functions are all attributes that all "Player" objects inherit.
To return to our car metaphor, the minivan is a type of car. All minivans share certain characteristics that are not necessarily shared by all cars. For example, minivans are driven by soccer moms while not all cars are driven by soccer moms. However, the minivan inherits all the characteristics of a car. The minivan inherits color, and model, and then defines further attributes unique to just the minivan child class.
Inheritance is a powerful tool that allows for more reusability of code, less code duplication, faster development, consistency, and more efficient code. Together with inheritance, three other principles of object-oriented programming make it such a useful programming style. These other attributes are abstraction, encapsulation, and polymorphism.
领英推荐
Abstraction?provides an interface with the class that allows a programmer to access it without needing to access the inner works of attributes within the class. In the "Actor" code above, functions like "get_attack_power" provide access to the Actor's attack power, but the user does not need to know how the attack power is calculated. The essence of abstraction is like how you do not know how your car engine works but can still turn it on.
Encapsulation?is the idea of wrapping data and the methods that work on data within one object. As you can see in the code above, a class is an example of encapsulation as it encapsulates functions, variables, etc. The goal of information hiding is to ensure that an object’s state is always valid by controlling access to attributes that are hidden from the outside world.
The "Player" class cannot affect the attributes of a separate "Enemy" class. They are hidden from other classes and prevent unwanted changes and wacky behavior in your code.
Lastly,?polymorphism?allows the same function name to have numerous different applications. Take for example the "Enemy" class defined below.
class Enemy(Actor):
def __init__(self, name, level, kind):
super().__init__(name, level)
self.kind = kind
def attacks(self, player):
print('The {} attacks!'.format(self.kind))
e_power = self.get_attack_power()
player_defense = player.get_defense()
damage = e_power - player_defense
if damage < 0:
damage = 0
print('The {}\'s attack had no effect.'.format(self.kind, damage))
else:
print('The {} attacks the player for {} damage!'.format(self.kind, damage))
player.hp -= damage
In this "Enemy" class, we see similar attributes as were defined in the "Player" class. For example, both classes contain an "attacks" function. However, notice that the code for the "attacks" functions are very different. This is because each is defined locally within its class and the name can be reused in a different manner for adjacent classes. Using polymorphism, classes can edit attributes inherited by parent classes without editing the parent class itself.
Object-oriented programming constructs useful building blocks that allow for greater usability and transparency in code. It is a staple of programming and is used in areas from software development to modeling and statistics. Below, I have included the complete code for a game called "Monster Slash" inspired by ITPro.TV's Python tutorials. Check out their videos for more in-depth discussion on how to implement object-oriented programming in Python.
This code is a dungeon crawler game complete with level progression, unique path generation, monster difficulty scaling, and more. One could adjust the classes to add an inventory system and items, add more types of enemies, etc. There is a game.py file and an actors.py file each with separate information for the game. Run this code in Python to enjoy a short game experience that illustrates the usefulness of object-oriented programming.
###actors.py###
from random import randint
class Actor:
def __init__(self, name, level):
self.name = name
self.level = level
self.hp = 100 + 3 * level
self.full_hp = self.hp
def get_attack_power(self):
return randint(1, 50) + 5*self.level
def get_defense(self):
return randint(1, 25) + self.level*self.level
def __repr__(self):
return 'Actor: {}, Level:{}'.format(self.name, self.level)
def is_alive(self):
return self.hp > 0
def attacks(self, other):
raise NotImplementedError()
def stats(self):
print(' {} has {} health.'.format(self.name, self.hp))
class Player(Actor):
def heal(self):
self.hp += round((self.full_hp - self.hp) / 3)
def attacks(self, enemy):
power = self.get_attack_power()
enemy_defense = enemy.get_defense()
damage = power - enemy_defense
if power > enemy_defense:
print('{} hit the {} for {} damage!'.format(self.name, enemy.kind, damage))
enemy.hp -= damage
if enemy.hp <= 0:
print('{} slayed the {}!'.format(self.name, enemy.kind))
return True
else:
print('The {} appears unaffected by your attack...'.format(enemy.kind))
return False
class Enemy(Actor):
def __init__(self, name, level, kind):
super().__init__(name, level)
self.kind = kind
def attacks(self, player):
print('The {} attacks!'.format(self.kind))
e_power = self.get_attack_power()
player_defense = player.get_defense()
damage = e_power - player_defense
if damage < 0:
damage = 0
print('The {}\'s attack had no effect.'.format(self.kind, damage))
else:
print('The {} attacks the player for {} damage!'.format(self.kind, damage))
player.hp -= damage
class Ogre(Enemy):
def __init__(self, name, level, size):
super().__init__(name, level, 'Ogre')
self.size = size
self.hp = 125 + 5 * level * size
full_hp = self.hp
def get_attack_power(self):
return randint(1, 50)*(self.size + self.level)
def get_defense(self):
return randint(1, 15) + self.level*self.level*self.size
class Imp(Enemy):
def __init__(self, name, level):
super().__init__(name, level, 'imp')
def get_attack_power(self):
return randint(1, 20)
def get_defense(self):
return randint(1, 10)
###game.py###
from actors import Player, Enemy, Ogre
import random
from random import randint
class Game:
def __init__(self, player, enemies):
self.player = player
self.enemies = enemies
def main(self):
self.print_intro()
self.wander()
#self.encounter()
def print_intro(self):
print('''
Monster Slash!!!
Ready Player One?
[Press Enter to Continue]
''')
input()
def wander(self):
print('{} enters the dark and mysterious dungeon...'.format(self.player.name))
print('{} is faced with two choices. [l]eft or [r]ight?'.format(self.player.name))
input()
ending_counter = 100
difficulty_counter = 1
numb_paths = 0
while True:
if numb_paths == 0:
numb_paths = randint(1, 3)
if numb_paths == 1:
print('{} reached a dead end and retraced his steps.'.format(self.player.name))
numb_paths = 0
elif numb_paths == 2:
if randint(1, ending_counter) == 1 and ending_counter <= 85:
print('''There is a faint light ahead.
{} escaped the dungeon!'''.format(self.player.name))
print()
print('*'*40)
print()
print(' CONGRATULATIONS! YOU WIN!')
print()
print('*'*40)
break
cmd = input('Continue [s]traight or [g]o back?')
if cmd == 's':
print('{} continued onward into the darkness...'.format(self.player.name))
numb_paths = 0
elif cmd == 'g':
print('{} retraced his steps.')
numb_paths = 0
lost = randint(1, 2)
if lost == 2:
print('Riley has become even more lost...')
else:
print('{} must make a decision.'.format(self.player.name))
elif numb_paths == 3:
if randint(1, ending_counter) == 1 and ending_counter <= 85:
print('''There is a faint light ahead.
{} escaped the dungeon!'''.format(self.player.name))
print()
print('*'*40)
print()
print(' CONGRATULATIONS! YOU WIN!')
print()
print('*'*40)
print()
break
cmd = input('Continue [l]eft, [r]ight, or [g]o back?')
if cmd == 'l':
print('{} continued left...'.format(self.player.name))
numb_paths = 0
elif cmd == 'r':
print('{} continued right...'.format(self.player.name))
numb_paths = 0
elif cmd == 'g':
print('{} retraced his steps.'.format(self.player.name))
numb_paths = 0
lost = randint(1, 2)
if lost == 2:
print('Riley has become even more lost...')
else:
print('{} must make a decision.'.format(self.player.name))
if randint(1, ending_counter) <= (ending_counter + difficulty_counter/ending_counter) / 3 :
defeat_or_survive = self.encounter()
if defeat_or_survive == 0:
break
ending_counter -= 1
difficulty_counter += 1
print()
print('*'*40)
print()
def encounter(self):
while True:
next_enemy = random.choice(self.enemies)
continuer = 0
print('{} encountered a {}!'.format(self.player.name, next_enemy.kind))
while continuer == 0:
# print('You see a {}'.format(next_enemy.kind))
self.player.stats()
next_enemy.stats()
print()
cmd = input('[a]ttack, [i]nventory, [r]un? \n')
if cmd == 'r':
print(self.player.name + ' ran away. {} healed slightly'.format(self.player.name))
self.player.heal()
next_enemy.hp = next_enemy.full_hp
continuer = 0
break
elif cmd == 'a':
print(self.player.name + ' attacks the ' + next_enemy.kind + '!')
self.player.attacks(next_enemy)
if next_enemy.hp <= 0:
next_enemy.hp = next_enemy.full_hp
#self.enemies.remove(next_enemy)
break
next_enemy.attacks(player)
if self.player.hp <= 0:
break
elif cmd == 'i':
print(self.player.name + ' awaits the {}\'s next move...'.format(next_enemy.kind))
continuer = 1
else:
print('Choose a valid option')
print()
print('*'*40)
print()
if self.player.hp <= 0:
print()
print('You have been slain by the {}.'.format(next_enemy.kind))
print()
print('*'*40)
print(''' GAME OVER ''')
print('*'*40)
#break
return 0
print()
print('*'*40)
print()
if cmd == 'r':
#break
next_enemy.hp = next_enemy.full_hp
return 1
if __name__ == '__main__':
# enemies = [
# Enemy('Ogre', 1),
# Enemy('Imp', 1)
# ]
character_name = input('What is your name, adventurer?\n')
player = Player(character_name, 1)
cnt = 'y'
while cnt == 'y':
enemies = [
Ogre('Ogre', 1, randint(1, 4)),
Enemy('Imp', 1, 'Imp')
]
Game(player, enemies).main()
print()
print('*'*40)
print()
cnt = input('Would you like to play again? (\'y\' for yes)\n')