omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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?

    Pythonista
    4
    16
    9531
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cook
      cook last edited by

      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)
      
      1 Reply Last reply Reply Quote 0
      • ccc
        ccc last edited by ccc

            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
        
        Phuket2 1 Reply Last reply Reply Quote 1
        • Phuket2
          Phuket2 @ccc last edited by

          @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
          
          1 Reply Last reply Reply Quote 1
          • ccc
            ccc last edited by ccc

            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
            
            Phuket2 1 Reply Last reply Reply Quote 0
            • Phuket2
              Phuket2 @ccc last edited by

              @ccc, super nice 💋💋💋

              1 Reply Last reply Reply Quote 0
              • dgelessus
                dgelessus last edited by dgelessus

                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...)

                Phuket2 1 Reply Last reply Reply Quote 0
                • Phuket2
                  Phuket2 @dgelessus last edited by

                  @dgelessus , lol. I wish I had thought of that 😬

                  1 Reply Last reply Reply Quote 0
                  • dgelessus
                    dgelessus last edited by

                    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
                            })
                    1 Reply Last reply Reply Quote 1
                    • cook
                      cook last edited by

                      @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" the kwargs 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" the kwargs 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.

                      1 Reply Last reply Reply Quote 0
                      • cook
                        cook last edited by

                        @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)

                        Phuket2 1 Reply Last reply Reply Quote 0
                        • ccc
                          ccc last edited by ccc

                          @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
                          
                          1 Reply Last reply Reply Quote 1
                          • Phuket2
                            Phuket2 @cook last edited by

                            @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 way

                            class 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)
                            
                            1 Reply Last reply Reply Quote 1
                            • cook
                              cook last edited by

                              @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.

                              Phuket2 1 Reply Last reply Reply Quote 0
                              • cook
                                cook last edited by

                                Really... Thanks for the input guys... !
                                @phuket2 @ccc @dgelessus

                                1 Reply Last reply Reply Quote 1
                                • ccc
                                  ccc last edited by

                                  @cook I think you now have all the components required to put together your dream solution.

                                  1 Reply Last reply Reply Quote 2
                                  • Phuket2
                                    Phuket2 @cook last edited by

                                    @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.

                                    1 Reply Last reply Reply Quote 0
                                    • First post
                                      Last post
                                    Powered by NodeBB Forums | Contributors