Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Setting Attributes for a Custom View class > best approach?
-
Hey Everyone... This is just a question about best approach to set attributes for views in a class.
The problem: passing keyword arguments into a class. When you have multiple views that need to use keyword arguments, you suddenly have conflicts.
Anyway- just wanting some opinion. Most people know more than me about Python so let's see what you say.
Is the following a good way? Bad way? Awesome way? Silly way? Not clear way?
#problem: passing some kwargs to use in an assignment loop may not work because, for example, a label and a view both use 'frame'. Here is a way to use different keyword arguments but assign to the correct attribute #example: I could use 'label_frame' as an kwarg for the label. #on setting the attribute it will set 'frame' as defined in the tuple (in defaults) import ui class ViewObject(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.make_view(**kwargs) def make_view(self, **kwargs): label = self.make_label(**kwargs) self.add_subview(label) self.label = label def make_label(self, **kwargs): label = ui.Label() #defaults format: {<name for kwargs>: (<attribue name to set>, <value>)} defaults = {'label_bgcolor': ('background_color', 1), 'label_border_color': ('border_color', 0.6), 'label_border_width': ('border_width', 0.7), #'frame':('frame', (0,0,100,100)), #example: can't use 'frame' because the ui.View uses it... 'label_frame': ('frame', (0,0,100,100)) } for attr in defaults: setattr(label, defaults[attr][0], kwargs[attr]) if attr in kwargs else setattr(label, defaults[attr][0], defaults[attr][1]) return label a = ViewObject(background_color='#574854', frame=(0,0,201,205), label_bgcolor=0.3, label_frame=(0,0,40,40)) print('view background color', a.background_color) print('view frame', a.frame) print('background_color', a.label.background_color) print('border_color', a.label.border_color) print('border_width', a.label.border_width) print('label frame', a.label.frame)
-
def make_label(self, **kwargs): defaults = {'background_color': 1, # create a dict of default values 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100)} for attr, value in kwargs.items(): # create merged dict overwriting defaults[attr] = value # default values w/ kwarg values label = ui.Label() # make the label for attr, value in defaults.items(): # for all attrs in merged dict setattr(label, attr, value) # set them in the iu.Label return label # or even tighter... def make_label(self, **kwargs): defaults = {'background_color': 1, # create a dict of default values 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100)} for attr, value in kwargs.items(): # create merged dict overwriting defaults[attr] = value # default values w/ kwarg values return ui.Label(**defaults) # return a Label w/ merged values
-
@ccc , what about? No loop, just update the dict
def make_label(self, **kwargs): defaults = {'background_color': 1, # create a dict of default values 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100)} ''' for attr, value in kwargs.items(): # create merged dict overwriting defaults[attr] = value # default values w/ kwarg values ''' defaults.update(kwargs) label = ui.Label() # make the label for attr, value in defaults.items(): # for all attrs in merged dict setattr(label, attr, value) # set them in the iu.Label return label
-
Nice! @Phuket2 Combining all optimizations we get...
def make_label(self, **kwargs): defaults = {'background_color': 1, # create a dict of default values 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100)} defaults.update(kwargs) # merge in all kwargs values return ui.Label(**defaults) # return ui.Label w/ merged values
-
@ccc, super nice 💋💋💋
-
def make_label(self, **kwargs): return ui.Label( background_color=1, border_color=0.6, border_width=0.7, frame=(0, 0, 100, 100), **kwargs )
(nevermind, this actually complains about duplicate kwargs... I thought they changed that in Python 3.5...)
-
@dgelessus , lol. I wish I had thought of that 😬
-
Ah, but this works (3.5 only):
def make_label(self, **kwargs): return ui.Label(**{ 'background_color': 1, 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100), **kwargs })
-
@Phuket2 @ccc @dgelessus Sorry... I think maybe my point was missed a little. Originally I tried that and then quickly realized I would have a problem.
Let's say I have three different
ui
components to add to my view. But for the class I want to be able to "pass around" thekwargs
to set the appropriate attributes for each component. In the example I just had a label, let's say I also have a textview and a slider. Notice that we're also initializing super:super().__init__(**kwargs)
This leaves us with four objects that will take a
frame
keyword argument.
If I pass frame as an argument,ViewObject(frame=(0,0,100,100)
, and then "pass around" thekwargs
to other methods for making the other view objects, I will end up with everything having this same frame (or background color etc). That is likely not what I intend to happen have happen.So, I thought to make distinction by using a different keyword (such as 'label_bgcolor') but defining the intended attribute name in the tuple.
I hope that makes sense. So... is it a good way? I guess it boils down to: using distinguishable keyword arguments among multiple views in a custom view class.
-
@dgelessus that looks like a nice approach, but also not everything can be set in initializing most ui components. (Like I can't set a button action like
ui.Button(action=this)
-
@cook I don't like the approach of mixing the attrs in a single dict but here is the code:
def make_label(self, **kwargs): defaults = {'background_color': 1, # create a dict of default values 'border_color': 0.6, 'border_width': 0.7, 'frame': (0, 0, 100, 100)} for attr, value in kwargs.items(): if attr.startswith('label_'): defaults[attr[len('label_'):]] = value return ui.Label(**defaults) # return ui.Label w/ merged values
-
@cook , sorry yes I think that went a little off track.
I have an idea, maybe not good idea. The other guys can way in.
But you could use the named args to pass though dicts that you would use the same as kwargs for your objects, then the real kwargs would be passed through to super(). I didn't write the whole thing out.
But I think it's a way. But not sure it's a good wayclass MyClass(ui.View): def __init__(self, slider_args, label_args, *args, **kwargs): super().__init__(*args, **kwargs) if __name__ == '__main__': slider_kw = dict(bg_color = green) label_kw = dict(frame=(0, 0, 64, 32), bg_color = 'pink') v = MyClass(slider_args = slider_kw, label_ars = label_kw, bg_color = blue)
-
@ccc yes! Looks more 'pythonic'. I also didn't like having this strange dictionary with a tuple but couldn't think at the moment about a different way.
startswith
is nice. Real nice.Although, just returning
ui.Label(**defaults)
may be okay for some attributes, but some args can't be passed that way and it seems to vary among the UI components. Example:>>> import ui >>> def hello(sender): ... pass ... >>> tf = ui.TextField(action=hello) >>> tf.action # << wasn't assigned from the argument! >>> tf.action = hello >>> tf.action <function hello at 0x10a1a1a60>
So using
setattr
can provide someone with more flexibility for kwargs than even the original UI component.@phuket2 I think that's not a bad approach! Would definitely work and save typing in 'label_...' Etc. But it seems just to get one attribute (let's say I want to just set the label background color), I'd have to do a lot of work to get it.
-
Really... Thanks for the input guys... !
@phuket2 @ccc @dgelessus -
@cook I think you now have all the components required to put together your dream solution.
-
@cook , I don't want to complicate things, but to get a attr from any of your objects is just as easy as the reference you provide. The setting of and retrieving of the attrs are not connected.
But I think of one thing. The first thread was about share code. Meaning it's a lib for other programmers to use. That is different from creating a private function for use by yourself.
So I mean, the more configurable your class can be it has a better chance to serve more people's purposes without modifying the base code.
It's tricky. I struggle with it all the time. Of course you need a nice clean API for the programmers to call also. A balancing act.But the good thing is you have done it. If it's the best implementation or not, who cares. You have something that works. Then it's just a matter of refinement after that.
Although the above with the kwargs went off track a little to your exact question, still some very good info there.