Function to set properties of an object of a class composition

别等时光非礼了梦想. 提交于 2019-12-24 11:30:03

问题


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.


回答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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!