I think I'm misusing the concept of subclass. I'm working on a hobby project with Grids and Cells.
What I have, is the implementation of a Cell class, and its subclass HexCell which basically redefines many of the attributes/methods like so:
class Cell:
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
self.links = set()
self.neighbors = 4*[None]
def __repr__(self):
return f'Cell #({self.row},{self.col})'
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
Then I have a subclass that is the HexGrid which follows a similar structure with new parameters.
class HexCell(Cell):
def __init__(self, r_out, th_around):
# I'm indexing Hex cells around a center cell
# instead of by rows and columns; Prefixed hex
# as they follow the hexagon, and not regular polar coordinates.
self.hex_r = r_out
self.hex_th = th_around
self.neighbors = 6*[None]
self.links = set()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
to_dir = to_dir % 6
if (self.neighbors[to_dir] is None):
self.neighbors[to_dir] = other
other.neighbors[to_dir - 3] = self
# Hexagonal grids share neighbors.
other_1 = other.neighbors[to_dir - 2]
if (self.neighbors[to_dir - 1] is None) & (other_1 is not None):
self.bind(other_1, to_dir - 1)
other_5 = other.neighbors[to_dir - 4]
if (self.neighbors[to_dir - 5] is None) & (other_5 is not None):
self.bind(other_5, to_dir - 5)
In this case, the method self.link(other) is shared, but other attributes change from rectangular grid to hexagonal like the locaion from (row, col) to (hex_r, hex_th), or neighbors as a 4-list or 6-list. Thus I'd like these attributes to be dependent on a another cell-type attribute and transferred down to the subclass.
Correct use of subclassing needs to obey the following substitution principle:
If there are some objects x_1 of type T_1 and x_2 of type T_2 such that issubclass(T_2, T_1) == True, then any property that applies to x_1 must also apply for x_2.
In other words, you expect subclassing to implement new behaviours, not to change existing behaviours.
In you example, the change of coordinate system itself is a change of behaviour and thus HexCell should not inherit from Cell.
What you can do is create a base class BaseCell that encapsulates the common behaviour between Cell and HexCell and inherit from it.
class BaseCell:
def __init__(self):
self.links = set()
self.neighbors = []
def add_neighbor(self, other):
self.neighbors.append(other)
def link(self, other, bidirectional=True):
self.links.add(other)
if bidirectional:
other.link(self, bidirectional=False)
class Cell(BaseCell):
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
super().__init__()
def __repr__(self):
return f'Cell #({self.row},{self.col})'
class HexCell(Cell):
def __init__(self, r_out, th_around):
self.hex_r = r_out
self.hex_th = th_around
super().__init__()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
...
Your Cell class is in fact not an abstract "Cell", but a square cell in two-dimensional space (has exactly 4 neighbours, has "row" and "col" position). Such cell may not be subclassed by a hex cell, because hex cell is just a different type of cell : )
As you noticed, the only common things are link() method and links attribute. If you insist on subclassing, you could create something like:
class LinkedObject():
def __init__(self):
self.links = set()
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
class SquareCell(LinkedObject):
# "Cell" class here
class HexCell(LinkedObject):
# HexCell here
Related
I have two classes of objects, Top and Bottom. I create the objects in pairs and want to assign them to each other so that I can reference the other's corresponding object in my project. Is there a more elegant way to do this than my sample code?
class Top:
def __init__(self,bottom):
self.bottom = bottom
class Bottom:
def __init__(self):
self.top = None
def set_top(self,top):
self.top = top
Top_Bottom_List = []
for i in range(5):
Current_Bottom = Bottom()
Current_Top = Top(Current_Bottom)
Current_Bottom.set_top(Current_Top)
Top_Bottom_list.append((Current_Top,Current_Bottom))
My problem is that I'd like to create an instance of two classes at the same time, but obviously one of them will have to be defined first or the other one won't have anything to set a value to.
I'd prefer a solution that doesn't give one class a "preferred" status over the other.
class Top:
def __init__(self, bottom=None):
self.bottom = bottom
class Bottom:
def __init__(self, top=None):
self.top = top
def link(t, b):
t.bottom = b
b.top = t
return t, b
top_bottom_list = [link(Top(), Bottom()) for _ in range(5)]
If you can't immediately pass the linked object to each other on instantiation, do nothing initially, then let an additional function handle pairing the two together after the fact. An additional function can take care of the instantiation as well
def make_pair():
t = Top()
b = Bottom()
return link(t, b)
top_bottom_list = [make_pair() for _ in range(5)]
Nothing that can’t be solved by another layer of indirection:
def create_pair():
b = Bottom()
t = Top(b)
b.top = t
return b, t
…
top_bottom_list = [create_pair() for _ in range(5)]
If the coupling is 1:1, you could also change the constructors:
class Top:
def __init__(self, bottom = None):
if bottom is not None:
bottom.top = self
self.bottom = bottom
class Bottom:
def __init__(self, top = None):
if top is not None:
top.bottom = self
self.top = top
And use it as:
def create_pair():
b = Bottom(Top())
return b, b.top
… though I’m not sure I like this code.
I want to access a list of instantiated objects with a method inside the objects' class in Python 3.
I assume I can't give the the whole list to the object, as it would contain itself.
Concretely: how do I access cells[] from within the class cell? Or is this the wrong way to think about it? the end goal is to easily program cell behavior like cell.moveUp() -- all cells are connected to 8 neighbors.
I am missing something, probably since I don't have much experience in python/programming.
#!/usr/bin/env python3
import random
class cell:
""" cell for celluar automata """
def __init__(self, n=0, nghbrs=[], a=0.00, b=0.00, c=0.00):
self.n = n #id
self.nghbrs = nghbrs #list of connected neighbors
self.a = a #a value of the cell
self.b = b
self.c = c
def growUp(self):
if self.a > .7: # if cell is "bright"
cells[self.nghbrs[7]].a = self.a # update cell above (nghbrs[7] = cell above )
def main():
iterations = 4
links = initLinks() # 150 random links [link0, link2, ... , link7]*150
val1 = initval() # 150 random values
cells = [cell(nghbrs[0], nghbrs[1], val1[nghbrs[0]])for nghbrs in enumerate(
links)] # create cell objects, store them in cells and init. neigbours , a
for i in range(iterations): # celluar automata loop
for c in cells:
c.growUp()
def initLinks(): #for stackoverflow; in real use the cells are arranged in a grid
nghbrs = []
for i in range(150):
links = []
for j in range(8):
links.append(random.randrange(0, 150))
nghbrs.append(links)
return nghbrs
def initval():
vals = []
for i in range(150):
vals.append(random.random())
return vals
if __name__ == "__main__":
main()
run as is cells cannot be accessed in the method growUp():
NameError: name 'cells' is not defined
You could make a CellsList class (subclass of list) that has a method which you call to get a new cell.
class CellsList(list):
def add_cell(self, *args, **kwargs):
"""
make a cell, append it to the list, and also return it
"""
cell = Cell(cells_list=self, *args, **kwargs)
self.append(cell)
return cell
then in the cell itself (I've renamed the class Cell and above I am using cell as in instance variable in accordance with usual capitalisation convention) you have an attribute cells_list where you store a back-reference to the cells list. (I'm also fixing the initialisation of nghbrs to avoid a mutable object in the defaults.)
class Cell:
""" cell for celluar automata """
def __init__(self, n=0, nghbrs=None, a=0.00, b=0.00, c=0.00, cells_list=None):
self.n = n #id
self.nghbrs = (nghbrs if nghbrs is not None else []) #list of connected neighbors
self.a = a #a value of the cell
self.b = b
self.c = c
self.cells_list = cells_list
def growUp(self):
if self.a > .7: # if cell is "bright"
self.cells_list[self.nghbrs[7]].a = self.a # update cell above (nghbrs[7] = cell above )
And then inside main, you can change your current code that instantiates Cell (or what you call cell) directly (your line with cells = ...) to instead use cells.add_cell
cells = CellsList()
for nghbrs in enumerate(links):
cells.add_cell(nghbrs[0], nghbrs[1], val1[nghbrs[0]])
Here we're not actually using the value returned by add_cell, but we return it anyway.
Note: this approach allows you to maintain multiple independent lists of cells if you wish, because it does not rely on any class variables to hold the list -- everything is held in instance variables. So for example, your main program could model multiple regions, each containing a different cells list, by instantiating CellsList more than once, and calling the add_cell method of the relevant CellsList instance to create a new cell.
You can track instances of cell() by making the cells list a static variable of your class, which can be easily accessed from within all instances of the class.
import random
class cell:
""" cell for celluar automata """
cells = []
def __init__(self, n=0, nghbrs=[], a=0.00, b=0.00, c=0.00):
self.n = n #id
self.nghbrs = nghbrs #list of connected neighbors
self.a = a #a value of the cell
self.b = b
self.c = c
def growUp(self):
if self.a > .7: # if cell is "bright"
self.cells[self.nghbrs[7]].a = self.a # update cell above (nghbrs[7] = cell above )
def main():
iterations = 4
links = initLinks() # 150 random links [link0, link2, ... , link7]*150
val1 = initval() # 150 random values
cell.cells = [cell(nghbrs[0], nghbrs[1], val1[nghbrs[0]])for nghbrs in enumerate(
links)] # create cell objects, store them in cells and init. neigbours , a
for i in range(iterations): # celluar automata loop
for c in cell.cells:
c.growUp()
def initLinks(): #for stackoverflow; in real use the cells are arranged in a grid
nghbrs = []
for i in range(150):
links = []
for j in range(8):
links.append(random.randrange(0, 150))
nghbrs.append(links)
return nghbrs
def initval():
vals = []
for i in range(150):
vals.append(random.random())
return vals
if __name__ == "__main__":
main()
Earlier this week I asked a generic question in a related SO community regarding constructing mathematical trees using OOP. The main takeaway was that the Composite and Interpreter patterns were the go-to patterns for this kind of application.
I then spent several days looking around online for resources on how these are constructed. I'm still convinced that I do not need to construct an entire interpreter and that a composite might be sufficient for my purposes.
From the other question I was trying to construct this tree:
Without using OOP, I'd probably do something like this:
import numpy as np
def root(B, A):
return B+A
def A(x,y,z):
return x*np.log(y)+y**z
def B(alpha, y):
return alpha*y
def alpha(x,y,w):
return x*y+w
if __name__=='__main__':
x,y,z,w = 1,2,3,4
result = root(B(alpha(x,y,w),y), A(x,y,z))
This would give a correct result of 20.693147180559947. I tried to use the composite pattern to do something similar:
class ChildElement:
'''Class representing objects at the bottom of the hierarchy tree.'''
def __init__(self, value):
self.value = value
def __repr__(self):
return "class ChildElement with value"+str(self.value)
def component_method(self):
return self.value
class CompositeElement:
'''Class representing objects at any level of the hierarchy tree except for the bottom level.
Maintains the child objects by adding and removing them from the tree structure.'''
def __init__(self, func):
self.func = func
self.children = []
def __repr__(self):
return "class Composite element"
def append_child(self, child):
'''Adds the supplied child element to the list of children elements "children".'''
self.children.append(child)
def remove_child(self, child):
'''Removes the supplied child element from the list of children elements "children".'''
self.children.remove(child)
def component_method(self):
'''WHAT TO INCLUDE HERE?'''
if __name__=='__main__':
import numpy as np
def e_func(A, B):
return A+B
def A_func(x,y,z):
return x*np.log(y)+y**z
def B_func(alpha,y):
return alpha*y
def alpha_func(x,y,w):
return x*y+w
x = ChildElement(1)
y = ChildElement(2)
z = ChildElement(3)
w = ChildElement(4)
e = CompositeElement(e_func)
A = CompositeElement(A_func)
B = CompositeElement(B_func)
alpha = CompositeElement(alpha_func)
e.children = [A, B]
A.children = [x, y, z]
B.children = [alpha, y]
alpha.children = [x, y, w]
e.component_method()
I got stuck in the last line, however. It seems that if I call the component_method at the level of composite class instance e, it will not work, since the architecture is not built to handle adding two Child or Composite objects.
How can I get this to work? What should the component_method for my CompositeElement class contain?
def component_method(self):
values = [child.component_method() for child in self.children]
return self.func(*values)
This will evaluate the child nodes and pass the values to the function of the node itself, returning the value.
I am working on creating a module with a class that acts as a container for a list of another created class. Is there a way for the container class to be able to tell if any of the objects it contains has changed?
Here is an example:
class Part:
def __init__(self, size):
self.part_size = size
class Assembly:
def __init__(self, *parts):
self.parts = list(parts) # `parts` are all Part() objects
self.update()
def update(self):
self.assy_size = 0
for each in self.parts:
self.assy_size += each.part_size
def __getitem__(self, key):
return self.parts[key]
This is what I get if I try to change any of the Part properties in the Assembly:
>>>x = Part(1)
>>>y = Part(1)
>>>z = Part(1)
>>>u = Assembly(x, y, z)
>>>u.assy_size
3
>>>u[0].part_size = 4
>>>u.assy_size
3
I know that I can create additional methods that will call the update method if I replace, delete, or add Part objects to the Assembly, but is there any way to have the Assembly notified if any of the contained Part properties have changed?
The answer is in your question. Use a property.
class Part:
_size = 0
assembly = None
#property
def part_size(self):
return self._size
#part_size.setter
def part_size(self, value):
self._size = value
if self.assembly: # only notify if an Assembly is set
self.assembly.update()
def set_assembly(self, assembly):
self.assembly = assembly
def __init__(self, size):
self.part_size = size
class Assembly:
def __init__(self, *parts):
self.parts = list(parts) # `parts` are all Part() objects
for part in self.parts:
part.set_assembly(self) # reference to self needed to notify changes
self.update()
def update(self):
self.assy_size = 0
for each in self.parts:
self.assy_size += each.part_size
In this version of Assembly the constructor sets a reference on the Part to itself. This way it can update the assembly when the part_size changes. Use it as the example in your question.
>>>x = Part(1)
>>>y = Part(1)
>>>z = Part(1)
>>>u = Assembly(x, y, z)
>>>u.assy_size
3
>>>u[0].part_size = 4
>>>u.assy_size
6
If update isn't an expensive operation (in your example it isn't, but maybe in reality you have thousands of parts), you could calculate the size ad-hoc using a property:
class Assembly:
def __init__(self, *parts):
self.parts = list(parts)
#property
def assy_size(self):
result = 0
for each in self.parts:
result += each.part_size
return result
which can be accessed the same way: assembly.assy_size.
The calculation can also be simplified:
#property
def assy_size(self):
return sum(part.part_size for part in self.parts)
In Python what is the idiomatic way for initializing Python's instance variable:
class Test:
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
Or
class Test2:
def __init__(self, data):
self.a = data[0]
self.b = data[1]
self.c = data[2]
self.d = data[3]
UPDATE: I have around 20 instance variables for a class named Link:
self.street
self.anode
self.bnode
self.length
self.setbackA
self.setbackB
self.bearingA
self.bearingB
self.ltype
self.lanesAB
self.leftAB
self.rightAB
self.speedAB
self.fspdAB
self.capacityAB
self.lanesBA
self.leftBA
self.rightBA
self.speedBA
self.fspdBA
self.capacityBA
self.use
Each variable is related to the class Link. Is there a recommended way of refactoring this?
The former, since it's more explicit about what the parameters are and what the object requires.
If you do need to pass in your data as a tuple, there's a shortcut you can use. Instead of doing the latter, or something like:
test = Test(data[0], data[1], data[2], data[3])
You can instead unpack the list/tuple and do:
test = Test(*data)
If you need to pass in a bunch of data (more then 4-5), you should look into either using optional/keyword arguments, creating a custom object to hold some of the data, or using a dictionary:
config = Config(a, b, c, d)
test = Test(e, f, config, foo=13, bar=True)
I would probably refactor your Link class to look like this:
class Node(object):
def __init__(self, node, setback, bearing):
self.node = node
self.setback = setback
self.bearing = bearing
class Connection(object):
def __init__(self, lanes, left, right, speed, fspd, capacity):
self.lanes = lanes
self.left = left
self.right = right
self.speed = speed
self.fspd = fspd
self.capacity = capacity
class Link(object):
def __init__(self, street, length, ltype, use, a, b, ab, ba):
self.street = street
self.length = length
self.ltype = ltype
self.use = use
self.a = a
self.b = b
self.ab = ab
self.ba = ba
I saw that you had some duplicate data, so pulled those off into a separate object. While this doesn't reduce on the number of fields you have, overall, it does make the parameters you need to pass in smaller.
Having a large number of fields isn't bad, but having a large number of parameters generally is. If you can write your methods in a way that they don't need a huge amount of parameters by bundling together data, then it doesn't really matter how many fields you have.
Unpacking an array into a bunch of named variables suggests you should have started with named variables in the first place - stick with the first one.
There is only one reason you might want the second one here - you have something that is inconsiderately producing lists rather than objects. If that does happen:
data = get_data_in_list_form()
actual_data = Test(*data)
Can you group some of your data:
self.street
self.ltype
self.use
self.length
# .a and .b can be instances of NodeConnection
self.a.setback
self.a.bearing
self.b.setback
self.b.bearing
self.b.node
# .ab and .ba can be a separate class, "UniDirectionalLink
self.ab.lanes
self.ab.left
self.ab.right
self.ab.speed
self.ab.fspd
self.ab.capacity
self.ba.lanes
self.ba.left
self.ba.right
self.ba.speed
self.ba.fspd
self.ba.capacity
There's no need to do everything in a constructor here:
link = (
Link(street=..., ltype=..., use=..., length=...)
.starting_at(node_a, bearing=..., setback=...)
.finishing_at(node_b, bearing=..., setback=...)
.forward_route(lanes, left, right, speed, fspd, capacity)
.reverse_route(lanes, left, right, speed, fspd, capacity)
)