nested object in a class Python - python

I am obviously missing something fundamental here. Hopefully someone can put me right! TIA
I have an array of objects whose class contains instances of another object. But when I set a property for one of these then they all change.
class direction():
dest = -1
lock = ''
class room():
roomname = ''
desc = ''
n = direction()
s = direction()
w = direction()
e = direction()
item = ''
rooms = []
rooms.append( room() )
rooms.append( room() )
rooms.append( room() )
rooms.append( room() )
rooms.append( room() )
rooms[0].roomname = 'outside'
rooms[0].desc = ''
rooms[0].n.dest = 4
rooms[0].item = ''
rooms[1].roomname = 'hall'
rooms[1].desc = 'The hallway has doors to the east and south'
rooms[1].n.dest = 2
rooms[1].item = ''
if I iterate through the n.dest properties in the rooms list then all are returned as 2
It is as if the direction objects in each object in the rooms list are all a single instance and setting one value in one of them sets it for all of them.

Your attributes are all declared at class level, not instance level, meaning that every instance of the class will share the same values. I think you want:
class Room():
def __init__(self):
self.roomname = ''
self.desc = ''
self.n = direction()
self.s = direction()
self.w = direction()
self.e = direction()
self.item = ''

You're missing constructors, and therefore missing instance variables
You're defining class variables, so each variable is the same between all instances

In each class, you have declared a bunch of class attributes. An assignment like rooms[0].roomname = 'outside' creates an instance attribute that shadows room.roomname. However, you never actually make any such assignment to rooms[0].n, so each assignment to something like rooms[0].n.dest is adding an instance attribute dest to the same instance of direction shared by each instance of room.
In your attempt to "simplify" your code, you've made it more complicated. Define __init__ to set your instance attributes; class attributes are not used as often.
class Direction:
def __init__(self, dest, lock=''):
self.dest = dest
self.lock = lock
class Room:
def __init__(self, roomname, desc, item=''):
self.roomname = roomname
self.desc = desc
self.n = direction()
self.s = direction()
self.w = direction()
self.e = direction()
self.item = item
rooms = []
r = Room('outside', '')
r.n.dest = 4
rooms.append(r)
r = Room('hall', 'The hallway has doors to the east and south')
r.n.dest = 2
rooms.append(r)

Related

Is there a cleaner more pythonic way to create class instances within class instances?

I am programming a game with discord py. Each game have 10 players divided into 5 teams.
I don't really need:
A reference for each player, so the self.BlackHero etc properties are unnecesary...
But I do need:
Initiate 10 Player instances.
Initiate 5 Team instances, containing the 10 Player instances.
A list containing the 12 Player instances.
I was wondering if there's a more elegant way to write the code below? Thanks!
class Game:
def __init__(self, title):
self.title = title
self.round = 1
self.status = 'Registration'
self.winners = ''
self.deadline = ''
self.registrations = {}
self.chests = []
self.monsters = []
self.BlackHero = Player('BlackHero.png', 'black-team', 'hero', 'J6')
self.BlackWitch = Player('BlackWitch.png', 'black-team', 'witch', 'J5')
self.BlueHero = Player('BlueHero.png', 'blue-team', 'hero', 'A7')
self.BlueWitch = Player('BlueWitch.png', 'blue-team', 'witch', 'A8')
self.GreenHero = Player('GreenHero.png', 'green-team', 'hero', 'F9')
self.GreenWitch = Player('GreenWitch.png', 'green-team', 'witch', 'G9')
self.RedHero = Player('RedHero.png', 'red-team', 'hero', 'B0')
self.RedWitch = Player('RedWitch.png', 'red-team', 'witch', 'A0')
self.YellowHero = Player('YellowHero.png', 'yellow-team', 'hero', 'I0')
self.YellowWitch = Player('YellowWitch.png', 'yellow-team', 'witch', 'H0')
self.players = [
self.BlackHero,
self.BlackWitch,
self.BlueHero,
self.BlueWitch,
self.GreenHero,
self.GreenWitch,
self.RedHero,
self.RedWitch,
self.YellowHero,
self.YellowWitch
]
self.teams = {
'black-team':Team(self.BlackHero, self.BlackWitch),
'blue-team':Team(self.BlueHero, self.BlueWitch),
'green-team':Team(self.GreenHero, self.GreenWitch),
'red-team':Team(self.RedHero, self.RedWitch),
'yellow-team':Team(self.YellowHero, self.YellowWitch)
}
class Player:
def __init__(self, image, team, role, pos):
self.username = None
self.user_id = None
self.image = image
self.team = team #red/blue/green/yellow/black
self.role = role #witch/hero
self.position = pos
self.new_position = pos
self.old_target = None
self.new_target = None
self.has_sword = False
self.is_frozen = False
self.has_moved = False
self.will_die = False
self.status = 'ALIVE' #alive/dead
class Team:
def __init__(self, hero, witch):
self.hero = hero #username
self.witch = witch #username
self.items = []
self.status = 'ALIVE'
It's functional though...
Assuming that:
Every Team has exactly 2 players, a hero and a witch, and
Every Game consists of a number of Teams indicated by different colors,
you can considerably simplify your code by adapting the different __init__ functions
for better integration. E.g., all you need to determine a pair of hero and witch seems to be
a shared color and
a position for each of them.
Assuming they are always part of a team, it makes more sense to initiate the players in the Team constructor than in Game.
Consider transforming some redundant attributes into something less manual, e.g.,
I assume you manually change has_moved somewhere; I'd use a method for that so
you can check if a player moved via player.has_moved() or, if you set #property
as I did, player.has_moved like a regular attribute. Cf. is_dead in the Team.
I assume you could also check has_sword in a similar manner by checking the
items of the Team or something.
In general, it isn't necessary to initialize each and every possible attribute. E.g., you only
need to set self.username = None if someone could try to access
the username before it's set, which would throw an AttributeError.
If that cannot happen, you can just set the attribute directly when the time
has come to do so (player.username = 'Carl') even
if you didn't initialize it with None beforehand.
If status is binary (dead or alive), you might want to change that to
something like .is_dead and set it to True or False.
It's always a good idea to set __str__ and __repr__ for easier testing, too.
class Player:
"""
Represents a Player. Is called by the Team constructor.
color := color string, e.g. 'blue'
role := 'hero' or 'witch'
pos := '[A-J][0-9]' e.g. 'B4'
"""
def __init__(self, color, role, pos):
self.username = None
self.user_id = None
self.color = color
self.position = pos
self.new_position = pos
# e.g. 'red' and 'hero' -> 'RedHero.png'
self.image = '{}{}.png'.format(color.title(), role.title())
self.team = '{}-team'.format(color) #red/blue/green/yellow/black
self.role = role
self.old_target = None
self.new_target = None
self.has_sword = False # maybe set this as #property method, too?
self.is_frozen = False
self.will_die = False
# maybe set alive/dead as bool, too?
self.is_dead = False
#property
def has_moved(self):
return self.position != self.new_position
# Optional: set string methods
def __str__(self):
return f'<{self.color} {self.role}>'.title()
def __repr__(self):
return repr(str(self))
class Team:
"""
Represents a team of a certain color,
consisting of a hero and a witch player.
color := color string, e.g. 'blue'
positions := Tuple of two positions, strings in the pattern '[A-J][0-9]'
"""
def __init__(self, color, positions):
self.color = color
self.positions = positions
self.items = []
roles = ['hero', 'witch']
# initialize and store hero and witch
for role, pos in zip(roles, positions):
setattr(self, role, Player(color, role, pos))
self.players = [self.hero, self.witch]
#property
def is_dead(self):
"""Returns True if all players are dead"""
return all(p.is_dead for p in self.players)
# Optional: set string methods
def __str__(self):
return f'<{self.color.title()} Team>'
def __repr__(self):
return repr(str(self))
class Game:
"""
Represents a game with teams and players.
"""
def __init__(self, title, colors):
self.title = title
self.colors = colors
# you could also use some smarter heuristic
# to determine the positions
self.positions = [['J6', 'J5'],
['A7', 'A8'],
['F9', 'G9'],
['B0', 'A0'],
['I0', 'H0']]
self.round = 1
self.status = 'Registration'
self.winners = ''
self.deadline = ''
self.registrations = {}
self.chests = []
self.monsters = []
# Builds teams for each color
self.teams = [Team(color, pos) for color, pos in zip(self.colors, self.positions)]
self.players = [p for team in self.teams for p in team.players]
# Optional: set string methods
def __str__(self):
return f"<Game '{self.title}' – {len(self.teams)} teams>"
def __repr__(self):
return repr(str(self))
A new Game is initiated via
Game('Epic match name', ['black', 'blue', 'green', 'red', 'yellow'])
For a lower number of colors, the positions are consumed top-down. Adopt to your needs
if that doesn't make sense.

Error appending to objects' list attributes in python

I'm trying to make a model where nodes connect to one and other using OOP. I'd like each node to store the names of its inputs and connections within lists and have these lists as attributes. The code seems to function perfectly outside of the "connect" function.
It creates desired Connection object, but it creates erroneous attributes for the destination node and origin nodes. The desired attributes are:
A.inputs = [],
A.outputs = [B]
B.inputs = [A]
B.outputs = [0]
But instead of that I get:
A.inputs = [A]
A.outputs = [B}
B.inputs = [A]
B.outputs = [B}
CONNECTIONS = []
NODES = []
class Zone:
def __init__(self, height, width):
self.height = height
self.width = width
class Node:
def __init__(self, name, initial_activation, activation_function = "linear", inputs = [], outputs = [], location = (0,0)):
global NODES
self.name = name
self.activation = initial_activation
self.activation_function = activation_function
self.inputs = inputs
self.outputs = outputs
self.location = location
NODES.append(self)
def __repr__(self):
return(f"Node {self.name}")
class Connection:
def __init__(self, origin, destination):
self.origin = origin.name
self.destination = destination.name
def __repr__(self):
return(f"Connection from {self.origin} to {self.destination}")
def connect(origin, destination):
new_connection = Connection(origin, destination)
origin.outputs.append(destination.name)
destination.inputs.append(origin.name)
global CONNECTIONS
CONNECTIONS.append(new_connection)
A = Node("A",0)
B = Node("B", 0, location = (100,100))
A.__dict__ # verify it is correct before hand
B.__dict__
test_connection = Connection(A,B)
test_connection.__dict__
connect(A,B) # create connection
A.__dict__ # show erroneous result
B.__dict__
CONNECTIONS
CONNECTIONS[0].__dict__ # verify connection object is correct
I have tried to debug using print statements, but to no avail. I believe the problem is found within lines 33 and 34 but I cannot see an error in those lines.
I see that Node class has mutable default arguments values for variables inputs and outputs.
In code, you don't pass values for this argument so A.input refers to same list as B.input.
print(id(A.inputs) == id(B.inputs))
will print True.
You need to get rid off mutable default argument, f.e doing this:
class Node:
def __init__(self, name, initial_activation, activation_function="linear",
inputs=None, outputs=None, location=(0, 0)):
# Some code
self.inputs = inputs or []
self.outputs = outputs or []

Python - Parent method don't acess the value of variable children

Hi I'm having a problem in this classes I created the parent class extracao_nia with the method aplica_extracao for having the similar part of the execution that I use in others class and the diferent part is in the transform method definined in the children class
but I'm having an issue that the variables that I defined as list() are Null variable when I execute the code:
AttributeError: 'NoneType' object has no attribute 'append'
class extracao_nia:
def __init__(self, d=1, h=1, m=15):
self._data_base = "database"
self.UM_DIA = datetime.timedelta(days=d)
self.UMA_HORA = datetime.timedelta(hours=h)
self.INTERVALO = datetime.timedelta(minutes=m)
#property
def data_base(self):
return self._data_base
def aplica_extracao(self, SQL):
fim_intervalo = self.inicio + self.INTERVALO#
pbar = self.cria_prog_bar(SQL)#
while (fim_intervalo <= self.FIM):#
self.connector.execute(SQL,(self.inicio.strftime('%Y-%m-%d %H:%M'),fim_intervalo.strftime('%Y-%m-%d %H:%M')))#
for log in self.connector:#
self.transforma(log)
self.inicio = fim_intervalo
fim_intervalo = self.inicio + self.INTERVALO
class usuarios_unicos(extracao_nia):
def __init__(self, d=1, h=1, m=15, file='nodes.json'):
self._data_base = "database"
self.UM_DIA = datetime.timedelta(days=d)
self.UMA_HORA = datetime.timedelta(hours=h)
self.INTERVALO = datetime.timedelta(minutes=m)
self.file = file
self.ids = list()
self.nodes = list()
self.list_cpf = list()
def transforma(self, log):
context = json.loads(log[0])['context']
output = json.loads(log[0])['output']
try:
nr_cpf = context['dadosDinamicos']['nrCpf']
conversation_id = context['conversation_id']
nodes_visited = output['output_watson']['nodes_visited']
i = self.ids.index(conversation_id)
atual = len(self.nodes[i])
novo = len(nodes_visited)
if novo > atual:
nodes[i] = nodes_visited
except KeyError:
pass
except ValueError:
self.ids.append(conversation_id)
self.nodes = self.nodes.append(nodes_visited)
self.list_cpf = self.list_cpf.append(nr_cpf)
list.append returns None since it is an in-place operation, so
self.nodes = self.nodes.append(nodes_visited)
will result in self.nodes being assigned None. Instead you can just use
self.nodes += nodes_visited

Add all variables of instances within class with the same name

I have a class named Robot, which creates a number of instances of the classes Segment and Pointmass. All these instances have a variable mass. How can I obtain the total mass of all the objects within the object with the same variable name mass?
What I do right now:
class Robot:
def __init__(self, massJoint1, massJoint2, massJoint3, massSlide, reachSlide, massArm, lengthArm):
self.joint1 = Pointmass(massJoint1)
self.joint2 = Pointmass(massJoint2)
self.joint3 = Pointmass(massJoint3)
self.slide = Slide(massSlide, reachSlide)
self.arm = Arm(massArm, lengthArm)
self.totalmass = self.joint1.mass + self.joint2.mass + self.joint3.mass + self.slide.mass + self.arm.mass
def printVars(self):
attrs = vars(self)
print(', \n'.join("%s: \t%s" % item for item in attrs.items()))
print()
class Pointmass:
def __init__(self, mass):
self.mass = mass
self.location = None
class Segment:
def __init__(self, mass):
self.mass = mass
self.start = None
self.end = None
In which
self.totalmass = self.joint1.mass + self.joint2.mass + self.joint3.mass + self.slide.mass + self.arm.mass
is a very long line, and is really inconvenient when the Robot gains more arms and joints... Is there a better way to obtain the total mass?
You shouldn't be storing the joints in separate attributes. Rather, store a simple joints list; then you can simply use sum with a generator expression.
self.totalmass = sum(j.mass for j in self.joints)

Access name of python objects

I made a class to work with Heating wires:
class Heating_wire:
def __init__(self, ro, L,d,alpha):
self.ro = ro
self.L = L
self.d = d
self.alpha = alpha
self.RT = [1]
self.vector_T = [1]
def get_R20(self):
self.R_20 = self.ro*self.L/(np.pi*(self.d/2)**2)
def calcular_RT(self,vector_temp):
self.vector_T = vector_temp
self.RT = [self.R_20*(1 + temp*self.alpha) for temp in vector_temp ]
return self.RT
instantiate some objects:
kantal = Heating_wire(1.45,0.25,0.3,4e-5)
nicromo = Heating_wire(1.18,0.25,0.3,0.0004)
ferroniquel = Heating_wire(0.86,0.25,0.3,9.3e-4)
wires = [kantal,nicromo,ferroniquel]
And made a plot:
leg = []
vector_temp = np.linspace(20,1000,1000)
for wire in sorted(wires):
wire.get_R20()
wire.get_RT(vector_temp)
line, = plt.plot(wire.vector_T,wire.RT)
leg.append(line)
plt.legend(leg,sorted(wires))
The issue is that I'm not getting the right names in the legend but the reference to the objects:
If I add a name attribute
def __init__(self,name, ro, L,d,alpha):
self.name = name
I can append the names
leg = []
names= []
vector_temp = np.linspace(20,1000,1000)
for wire in sorted(wires):
wire.get_R20()
wire.get_RT(vector_temp)
line, = plt.plot(wire.vector_T,wire.RT)
leg.append(line)
names.append(wire.name)
plt.legend(leg,names,loc='best')
But I wonder if there is a simpler way t solve this using directly the names of the objects in the list of wires:
kantal = Heating_wire(1.45,0.25,0.3,4e-5)
nicromo = Heating_wire(1.18,0.25,0.3,0.0004)
ferroniquel = Heating_wire(0.86,0.25,0.3,9.3e-4)
wires = [kantal,nicromo,ferroniquel]
Just do it like this and there's no duplication:
wires = [
Heating_wire("kantal", 1.45,0.25,0.3,4e-5),
Heating_wire("nicromo", 1.18,0.25,0.3,0.0004),
Heating_wire("ferroniquel", 0.86,0.25,0.3,9.3e-4)
]
To answer your question, no, objects cannot access the names they were given.

Categories