问题
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 inRoom
for all instance variables ofWindow
orautomatically adjusts the list of keyword arguments in
set_windowprops
orset_props
.
回答1:
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.
回答2:
To answer you first question. If you want to set window
attribute of r
and kw
attribute of that `windows as in your example, you cat do the following:
setattr(getattr(r, "window"), kw, kwargs[kw])
You get attribute named window
of r
. And for that windows
attributes set its own whose name is in variable kw
to a new value from kwargs[kw]
. As your line has attempted.
But to be perfectly honest, why a function with many possible arguments would be preferred over just accessing/setting the attributes themselves? Or in other words, on the face of it, it looks more complicated then it needs to be.
As for you second question. You should be also able to just use dir()
to get list of instance attributes, but yes, inherited attributes will be getting in your way and I am not immediately sure if there is an elegant way around it (i.e. not walk the inheritance tree and do the pruning yourself). But if you genuinely wanted to dynamically walk attributes of an instance that are subject to for instance setting through a function, I'd say add a (class) attribute defining what those are to be for each type rely on that as a defined interface.
side note: There is only one instance of None
(and True
and False
) and it can be tested for identity (not just equality) and since it reads a little better you would normally see if somevar is not None
instead of if not somevar == None
.
来源:https://stackoverflow.com/questions/54620899/function-to-set-properties-of-an-object-of-a-class-composition