Class variables in OOP - python

I am trying to develop a car-dealership-customer model to really understand OOP using Python.
The problem I am running into is that I'd like to define Car as a separate class object from the Dealer class. This causes confusion; how can I use Car attributes and upload them into the dealership inventory, for example, and then make transaction updates depending on customer acquisition?
I broke it down to just these two classes for now (Car & Dealer). I'd like to create an inventory dictionary with model being the key and the output from my retail_cost function being the cost of vehicle. How do i insert instances of car into the dealership inventory in the class dealer?
For example:
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory and sales components"""
car_inventory = {}
def __init__(self, dealer_name):
self.dealer_name = dealer_name

The basic idea is that you add each instance of a car to the dealership, but note that I changed car_inventory to be an instance member of Dealer. This is because you want each new dealership you make to have its own car_inventory. If you left it as a class member of Dealer (the way you have it) then every dealership you make will have the same inventory.
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory sales components"""
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = []
car1 = Car("ford", "fiesta", '18,000')
car2 = Car("ford", "fiesta", '12,000')
dealer = Dealer("tom's dealership")
dealer.car_inventory.append(car1)
dealer.car_inventory.append(car2)
print(dealer.car_inventory)

First, car_inventory should probably be an instance variable, not a class variable. This allows each dealer to have their own inventory.
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = {}
Now you can create Dealer objects:
dealer_alice = Dealer('Alice')
dealer_bob = Dealer('Bob')
Create Car objects:
car_1 = Car('Corolla', 'V4', 12000)
car_2 = Car('Focus', 'V4', 13000)
And add them to the dealers' inventories:
dealer_alice.car_inventory['Corolla'] = car_1
dealer_bob.car_inventory['Focus'] = car_2
Or instantiate new cars and add them directly to the inventories:
dealer_alice.car_inventory['Jetta'] = Car('Jetta', 'V6', 18000)
dealer_bob.car_inventory['Mustang'] = Car('Mustang', 'V8', 17000)
Each dealer now has an inventory dictionary with keys that are strings representing the model and values that are Car objects. You can print them like this:
for model,car in dealer_alice.car_inventory.items():
print(model, car)
Note that this will print a nice model name like 'Jetta', but the Car objects will only print basic information - namely, the name of the class and the object's location in memory. If you want nicer output, you'll have to define a __str__() method for the Car class. It would also be good to do that for the Dealer class so that you can just print(dealer_bob) and get tidy output.
I'd also recommend picking some other kind of key (like the VIN) - as it is, if the dealer gets another car of the same model it'll just overwrite the existing one.

Related

What is the best way to structure relationships stored across classes?

I am attempting to build a game environment in python that I can then utilize for a reinforcement learning application. To accomplish this, I see that applying an OOP approach will integrate best with common Reinforcement Learning methodologies. I am new to OOP, however, and have some questions regarding the class relationships. Here are my class definitions:
class Game:
def __init__(self, n_players, avg_hands_blinds, starting_chips, table_cnt, verbose):
self.n_players = n_players * table_cnt
self.starting_chips = starting_chips
self.table_cnt = table_cnt
self.tables = []
for table in range(self.table_cnt):
self.tables.append(Table(int(n_players/table_cnt)))
self.players = []
for i, player in enumerate(range(self.n_players)):
table_no = int(i/table_cnt)
self.players.append(Player(i, starting_chips, table_no))
def __repr__(self):
self.out_list = []
self.out_list.append(f'Number of players: {self.n_players}')
return self.out_list
class Tables:
def __init__(self, n_players):
self.n_players = n_players
self.table_hands = []
class Player:
def __init__(self, player_no, starting_chips, table_no):
self.player_no = player_no
self.table_no = table_no
self.chips = Chips(starting_chips)
def __repr__(self):
return f'player_no: {self.player_no} table_no: {self.table_no} chips: {self.chips}'
My question relates to how I should store information about the table assignments. For example, I have defined Player instances at the game level to facilitate awareness of their overall status in a game. During the game, players will need to be reassigned to different tables to re-balance tables as players are knocked out.
At the same time, each Table instance will need to have an "awareness" of which players are sitting at the table (to execute Hand instances, for example).
My question is this: how do I enable both transfer of players between tables and enable my Table instances to conduct operations on the relevant players?

Share datastructures between two classes by importing

I have 2 classes: Vehicle & Car.
Vehicle Class has a dictionary of Car objects & a heap.
ClassV.py:
from ClassC import Car
import heapq
class Vehicle:
MapOfCars_ID = {}
heap = [] # Stores the load factor of each car
counter = 0
def createCar(number, idnum):
C = Car(number, idnum) # Create a car object
self.MapOfCars_ID[counter] = C # Dict of Car_ID : Car Object
self.heapq.heappush(heap, (0.0, counter)) # Heap stores load factor, Car_ID
counter += 1
def AssignCar():
t = heapq.heappop(heap)
MapOfCars_ID[t[1]].addPassenger()
ClassC.py is the logic for creating a Car:
from ClassV import Vehicle
class Car:
size = 0;
occupiedSeats = 0
carId = -1
def __init__(size, id_number):
self.size = size
self.carId = id_number
print "Created Car with size " + self.size + " and ID number "+ self.carId
def addPassenger():
if self.occupiedSeats < self.size:
self.occupiedSeats += 1
# Code below adjusts the load factor of the car in the heap when a passenger is added to the car
# Load factor = seat-occupied/total-seats-in-the-car
for index, value in Vehicle.heap:
if value[1] == self.carId:
Vehicle.heap[index] = heap[-1]
heap.pop()
t = (float(self.occupiedSeats/self.size), self.carId)
heap.append(t)
heapq.heapify(Vehicle.heap)
break
else:
print "Car is full!"
The program is run from another file, main.py:
from ClassV import Vehicle
from random import randint
def main():
for i in range(1, 10): # Create 10 cars
r = randint(1,6) # Maximum number of seats could be 6 in a car
Vehicle.createCar(r, i) # <Car size, ID>
Vehicle.AssignCar()
if __name__ == "__main__":
main()
The intention of this program is to create 10 cars and then assign passengers to the car having minimal occupancy.
As shall be evident from the program, The heap which is a class attribute of the class Vehicle is being updated in Car Class. And, Class Vehicle is creating an array of Car objects.
This gives me an error:
File "/home/Testing/ClassC.py", line 1, in <module>
from ClassV import Vehicle
ImportError: cannot import name Vehicle
I have searched around but could really find a resolution to this problem. What is the right way to resolve this problem?
Update:
I got a few comments which explain that this is possibly a problem of circular imports and has 2 solution:
Refactor the program to avoid circular imports
Move the imports to the end of the module
I am looking for feedback as to how do I do either of these.
Update: I got a few comments which explain that this is possibly a problem of circular imports and has 2 solution:
Refactor the program to avoid circular imports
Move the imports to the end of the module
Several things wrong here:
Your chosen nomenclature is confusing/wrong. Why is the Vehicle class a container for Car instances? I would call it something like VehicleRegistry, or similar, to make its intent explicit.
You have a fundamental design flaw, in that you are violating the responsibilities of your two classes. An instance of Car should be able to stand in isolation, and when I add a passenger to it, it should only affect the internal state of that instance, it should not affect the state of Vehicle, this is a recipe for fragile code, that breaks easily.
Do not use class level attributes, unless you know exactly what you're doing. Altering the state of a class level attributes can alter the state of said attribute for all instances of that class, leading to some very interesting and unexpected behaviour.
This is what I mean by a class level attributes:
class Person(object):
first_name = "Bob"
last_name = "Smith"
These are tied to the class, not an instance.
Possible solution:
Herewith some code to illustrate what I mean:
Your addPassenger method should only add a passenger to the car and return whether it was successful or not, nothing else.
def add_passenger(self) -> bool:
if self.capacity > self.number_of_passengers:
self.capacity = self.capacity + 1
return True
return False
You place the updating of your load factor logic in the assign_car method, for example:
def assign_car(self):
car_id = heapq.heappop(self.heap)
car = self.vehicle_registry[car_id]
result = car.add_passenger()
if result:
# Put your load factor update logic here ...
print("A passenger was successfully added to: {0}".format(car.id_number))
else:
print("A passenger could not be added to the car.")
Edit[2018/09/24]:
Alternatively, if load factor is an attribute of the Car, then it makes sense to place it on the instance of car itself, and allow the VehicleRegistry to consume the load factor state.

Pass enum type as constructor argument

I am new to python and I would like to pass an enum as an argument to a constructor, within a function.
EDIT: I am working on a program with a class that has to organize different types of data, but most of these data types can be treated the same way. This data won't be all be added at the same time or in a foreseeable order. I would therefore like to keep the same functions, and just change the way the constructor stores the data. Let's consider this simpler example:
Say I have an enum
from enum import Enum, auto
class HouseThing(Enum):
people = auto()
pets = auto()
furniture = auto()
And I have a class House that can contain some or all of those things
class House():
def __init__(self, address, people = None, pets = None,
furniture = None):
self.address = address,
if self.people is not None:
self.people = people
etc....
And now I want to have a function that makes new furbished houses, but I want to use a function that could be used for any house:
house_things = HouseThing.furniture
def make_house_with_some_house_things(neighborhood, house_things):
neighborhood.append(House(house_things.name = house_things.name))
Is there a way to do this without first testing what kind of HouseThing house_things is first? house_things.name passes a string, but I would like it to be able to use it as a keyword.
I'm not sure exactly what you are trying to achieve here, but for the sake of solving the puzzle:
First, change House to determine what it has been passed:
class House():
def __init__(self, address, *house_things):
self.address = address
for ht in house_things:
if ht is HouseThings.people:
self.people = ht
elif ht is HouseThings.pets:
self.pets = ht
elif ht is HouseThings.furniture:
self.furniture = ht
else:
raise ValueError('unknown house thing: %r' % (ht, ))
Then, change make_house_with_some_house_things to just pass the house things it was given:
def make_house_with_some_house_things(neighborhood, house_things):
neighborhood.append(House(house_things))

Mixing class and instance variables together

I'm designing a simple role-playing game (similar to World of Warcraft, League of Legends, etc.) where you basically have a hero or a champion who fights with wild creatures and other heroes/champions.
This hero can then level up by gaining experience points from fights, and each level grants him a skill point, which allows him to level up one of his skills.
Each skill has its own purpose and does its own thing, an example of a skill would be:
class DamageAura(Skill):
"""
This skill increases damage dealt by your hero,
as well as deals damage back to the ones who attack your hero.
"""
name = 'Damage Aura'
description = '...'
# These methods are called when the actual event happens in game
def unit_attack(self, target, damage):
target._health -= (self.level / 100) * damage
def unit_defend(self, attacker, damage):
attacker.deal_damage((self.level / 100) * damage)
Now I would like the players of the game to be able to make their own heroes, and I need it to be simple.
The problem is, I don't want to do "bad" code simply to make creating heroes easier.
Here's what a hero class would ideally look like:
class Warrior(Hero):
name = 'Warrior'
description = 'Ancient Warrior is thirsty for blood.'
maximum_level = 30
agility = 20
intelligence = 10
strength = 30
class DamageAura(Skill):
name = 'Damage Aura'
description = '...'
def unit_attack(...):
...
class StrongerWeapons(Skill):
name = 'Stronger Weapons'
...
Now the problem is, I'd like the name variable to be an attribute of the actual hero (f.e.Warrior) instance, not a class variable.
Is there an easy, yet safe way to merge these two?
Basically the class variables would define the default values for a hero, but if I wanted to create a Warrior instance with a custom name, I could do so by simply doing warrior1 = Warrior(name='Warrior God', maximum_level=40)
This is what the class would look like if I didn't have to worry about subclasses or ease-of-creation:
class Hero(object):
def __init__(self, name, description='', maximum_level=50, skill_set=None, ...):
self.name = name
self.description = description
self.maximum_level = maximum_level
self.skill_set = skill_set or []
...
EDIT:
I forgot to mention; each player could be playing the exact same hero, Warrior in this case, so that's why Warrior is a class, not just an instance of Hero. They would all have the same skill set, but unique levels, etc.
EDIT 2:
I'm having no problem with Python classes or anything, normally I would NOT make the skills nested classes, or anything similar, but in this case I must invest a lot on ease of creating classes, so even the "noobs" can do it without knowing rarely any python at all.
You could allow the end users to create their Hero definitions as a dictionary:
my_hero = dict(name='Warrior'.
description='Ancient Warrior is thirsty for blood.',
maximum_level=30,
...)
Then you can easily create an instance like:
hero = Hero(**my_hero)
(If the syntax is unfamiliar, see What does ** (double star) and * (star) do for parameters?)
For both DamageAura and Warrior, it seems more appropriate for them to be instances than subclasses of Skill and Hero respectively.
Alternatively, consider letting the user create their characters outside of your code (e.g. in JSON, XML, YAML, ...), then parse the files and create appropriate instances from the input data.

Python - Can't create instances from a list or random instances

I am making a small text RPG to help me learn the Python language, and I am trying to create multiple instances of a class from a list of names.
I have a class of enemies (Named: Enemy) and would like to create between 1 and 3 "goblin" enemies at at time.
class Enemy:
def __init__(self, health):
self.health = health
How I have approached the problem so far is to use a for loop to run through letters 'a', 'b', and 'c' and append a list of enemies.
for i in ['a', 'b', 'c']:
enemylist.append('goblin' + (i))
This gives me a list of three goblins:
['goblina', 'goblinb', 'goblinc']
Now I would like to take each newly appended "goblin" in the list and create an instance of the enemy class using that goblin's name (Example: "goblina = enemy(10)"... "goblinb = enemy(10)...)
But when I try to create an instance using any number of ways including the following (which is probably the absolutely wrong way to):
for i in range (1, 3):
enemylist[i] = enemy(10)
All that I get is a single instance named enemylist[i].
Can someone please help me. Like I said, I am new to the language so please be gentle with the explanation but I am a fast learner and willing to read and research.
I spent the better part of 2 days (on and off) trying to get to the bottom of this and could not find a solution that worked.
Perhaps it would make more sense to keep the enemy's name as a member of the Enemy class as well:
import random
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def create_enemies():
enemies = []
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies.append(Enemy(name, health))
It's hard to make a suggestion without knowing how you're going to use the enemies. If you want to be able to look an enemy up by name, a dictionary would be a better data structure in which to store them:
def create_enemies():
enemies = {} # Initialize empty dict
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies[name] = Enemy(name, health)
return enemies
def main()
enemies = create_enemies()
ga = enemies['goblina']
I think adding a name to the class is good, but creating a dict with a key that's equal to the same name, but also storing the name and health as a dict value seems a bit redundant (as shown in answer above).
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def __repr__(self):
return self.name
This will give you a name to access. You can still store each instance of the enemy class in a list, using list comprehension if you like (or any other list creation):
for i in enemylist:
i = Enemy(i, 10)
enemies.append(i)
Now you have a list enemies, consisting of three instantiated objects of the class Enemy, that have a name that can be accessed. For instance
goblina.__name__
will return goblina and
isintance(goblina, Enemy)
will return True.

Categories

Resources