I would like construct a class composition that includes a function set_props
for setting the instance variables of components.
The application for this is in defining new objects for drawing in matplotlib. One example is that I would like to have a function drawMyArrow
that draws an arrow with possibly different colors (and other specifications) for its head, tail, and arc. I would like to be able to pass various specifications for the head, tail, and arc via keyword arguments in drawMyArrow
. I haven't worked with classes before, but reading up on this online, I believe that the best way to solve my problem is to define a class MyArrow
that is a composition of some classes ArrowHead
and ArrowArc
.
To illustrate my problem, I am using a toy example (that I also used for a previous question here). Let's define a class Room
that is a composition of the classes wall
, window
, and door
.
class Door:
def __init__(self, color='white', height=2.3, width=1.0, locked=True):
self.color = color
self.height = height
self.width = width
self.locked=locked
class Window:
def __init__(self, color='white', height=1.0, width=0.8):
self.color = color
self.height = height
self.width = width
class Wall:
def __init__(self, color='white', height=2.5, width=4.0):
self.color = color
self.height = height
self.width = width
class Room:
def __init__(self):
self.door = Door()
self.window = Window()
self.wall = Wall()
If I want to have a function for Room
that can set properties of its components, I can do something like this:
def set_windowprops(r, color=None, width=None, height=None):
if not color==None: r.window.color=color
if not width==None: r.window.widht=width
if not height==None: r.window.height=height
return r
But if I decide to add more instance variables to Window
, I would have to go back to this function and add the new instance variables. Can I write set_windowprops
so that it automatically accepts all instance variables of Window
as keywords?
Ideally, I would like to write a function like this:
def set_props(r, windowcolor=None, windowwidth=None, windowheight=None,
doorcolor=None, doorwidth=None, doorheight=None, doorlocked=None,
wallcolor=None, wallwidth=None, wallheight=None):
if not windowcolor==None: r.window.color=windowcolor
if not windowwidth==None: r.window.widht=windowwidth
if not windowheight==None: r.window.height=windowheight
if not doorcolor==None: r.door.color=doorcolor
if not doorwidth==None: r.door.widht=doorwidth
if not doorheight==None: r.door.height=dooorheight
if not doorlocked==None: r.door.locked=doorlocked
if not wallcolor==None: r.wall.color=wallcolor
if not wallwidth==None: r.wall.widht=wallwidth
if not wallheight==None: r.wall.height=wallheight
return r
but without the need of hardcoding all instance variables of components into the function.
I was looking into using keyword dictionaries like so:
window_vars = getNamesOfInstanceVariables(Window) #TODO
window_kwargs = {}
for v in window_vars:
window_kwargs[v] = None
def set_windowprops(r, **kwargs):
for kw in kwargs:
if not kwargs[kw]==None:
r["window"][kw] = kwargs[kw] #TODO
return r
Two issues keep me from getting this to work:
(1) In the last line, I am pretending that r
is a dictionary (of dictionaries) and using dictionary syntax to assign a value to r.window.kw
. But that doesn't work because r
is an instance of Room
and not a dictionary. What would be the syntax for setting instance variables if the name of the component class and the name of the instance variable are given as strings?
(2) I have tried using inspect
to write getNamesOfInstanceVariables
, but I am unable to get it to work robustly. Many classes in matplotlib
inherit from base classes. I would like getNamesOfInstanceVariables
to return all instance variables that a user can set for an object of this class. For example, the class FancyArrow
in matplotlib
has Patch
as base class and instance variables head_length
and head_width
. So I would getNamesOfInstanceVariables(FancyArrow)
to return ['head_length','head_width', *listOfInstanceVarsForPatch]
.
EDIT
Let me add a bit of background on why I am asking for a dynamical way to write these functions. Let's say I have finished my script and it includes the classes Window
, Door
, Wall
and many class compositions of these three. One of these many class compositions is Room
. This class Room
has ten hardcoded set_
functions that look like this:
def set_windowcolor(r, color):
r.window.color = color
return r
I now decide that I want to add another instance variable to the class Window
. For example,
class Window:
def __init__(self, color='white', height=1.0, width=0.8, open=False):
self.color = color
self.height = height
self.width = width
self.open = open # new attribute of Window
Similar to all the other instance variables of window, this new attribute of Window
should be customizable in all classe compositions that contain a Window
. So I would go through my code, find the class Room
and add a function
def set_windowopen(r, open):
r.window.open = open
return r
I would also have to look for all other class compositions that contain a Window
and update them manually as well. I don't want to do this because it is a lot of work and I am likely going to overlook some class dependencies in the process and introduce bugs into my code. I am looking for a solution that either
generates set
functions for single properties (e.g. set_windowcolor
) automatically in Room
for all instance variables of Window
or
automatically adjusts the list of keyword arguments in set_windowprops
or set_props
.
Here is what I would do
class Room:
def __init__(self, kw_door=None, kw_window=None, kw_wall=None):
if kw_door:
self.door = Door(**kw_door)
else:
self.door = Door()
if kw_window:
self.window = Window(**kw_window)
else:
self.window = Window()
if kw_wall:
self.wall = Wall(**kw_wall)
else:
self.wall = Wall()
effectively you are accepting a dictionary that will be unpacked into the instance creation, and when the class definition gets new attributes, they too will be unpacked if they are found in the passed dictionary.