0
class Content(Component):
    context = {
        'loading_circle': LoadingCircle(),
        'error_message': ErrorMessage(),
        'components': [],
    }

    def __init__(self, context = context):
        super().__init__()

In this class, if i want to create a new instance with some diferent components, i would have to call:

context = {
    'loading_circle': LoadingCircle(),
    'error_message': ErrorMessage(),
    'components': ['example component'],
}

Content(context = context)

But this means i'll have to always include loading_circle and error_message keys on my context..

I want to be able to do:

context = {
    'components': ['example component'],
}
Content(context = context)

.. And still have the default value for loading_circle and error_message.

I know i could change the __init__ method:

    def __init__(self, context = context):
        for key, value in context.items():
            self.context[key] = value
        
        super().__init__()

But for some reason it doesn't seems pythonic.

What would an experienced programmer change here? (Or is it this indeed the best solution?)

Ricardo Vilaça
  • 846
  • 1
  • 7
  • 18
  • 2
    `self.context.update(context)` – Barmar Jul 08 '21 at 15:54
  • 2
    I would suggest you name the shared context something like `shared_context` or something, because this will get really confusing really quickly! – Alexander Jul 08 '21 at 15:55
  • @Barmar That will mutate the context of existing instances. – user2390182 Jul 08 '21 at 16:01
  • @schwobaseggl So does the original code. – Barmar Jul 08 '21 at 16:01
  • 1
    True :-) I guess it shouldn't, or instance-specific "components" will be pointless. – user2390182 Jul 08 '21 at 16:04
  • @Alexander is the problem only about the confusion it might make to other devs? (Honestly asking. This is a solo project, im still kind of a noob in OOP). – Ricardo Vilaça Jul 08 '21 at 16:07
  • @Barmar the goal was to override the class existing dictionary, without removing any non-defined keys. I realize now it's my fault my example was bad.. Thanks anyway :) – Ricardo Vilaça Jul 08 '21 at 16:10
  • 1
    @RicardoVilaça "other devs" includes you, 6 months into the future. Whatever is obvious and clear to you because it's in your head today, won't be obvious and clear after you forgot it. – Alexander Jul 08 '21 at 16:11
  • @Alexander True, i've experienced that with 1-week period.. 6 months? That's more than enough ahah :) I was just curious because my way seems more intuitive than yours... but maybe i'm wrong i guess – Ricardo Vilaça Jul 08 '21 at 16:13
  • 1
    @RicardoVilaça It wasn't clear what the goal was. It seemed like you just wanted a "more pythonic" way to get the same result. – Barmar Jul 08 '21 at 16:17
  • @Barmar thats's exactly what i wanted but turns out i didn't even know my own code wasn't in sync with my idea – Ricardo Vilaça Jul 08 '21 at 16:23

1 Answers1

3

I would not have conflicting names for class and instance variables and also avoid mutable default arguments:

class Content(Component):
    defaults = {
        'loading_circle': LoadingCircle(),
        'error_message': ErrorMessage(),
        'components': [],
    }

    def __init__(self, context=None):  # no mutable default args!
        super().__init__()
        self.context = {**self.defaults, **(context or {})}

This way, every instance has its own context with the class providing said context's defaults.

Note that in the {**d1, **d2} expression, any key present in d2 will "override" the same key from d1.

user2390182
  • 72,016
  • 6
  • 67
  • 89
  • Why is it a bad idea to have equal names on the class and instance variables? (Genuinely asking). It's been working great so far but yet i'm a newbie.. Wouldn't be clearer set `context={}` on the `__init__` parameters? – Ricardo Vilaça Jul 08 '21 at 16:06
  • 1
    Since you can set attributes dynamically in Python, and instances fall back to their class's attributes when looking up their attributes, it can get confusing qickly, as `instance.attr` might at some point of the instance's life cycle be a reference to `instance.__class__.attr` and later a reference to the actual `instance.attr` (`instance.__dict__["attr"]`). – user2390182 Jul 08 '21 at 16:10
  • You can even do `del instance.attr` when `instance.attr` will be back to the class attribute ;-) And I have yet to encounter the need to have both of the same name. – user2390182 Jul 08 '21 at 16:14
  • I see.. So this hypotetical example would happen if, for example, an instance would lose the `instance.attr` after already being initiated and used somewhere? Shouldn't happen in my case but good to know standard pratices! I will adapt my script, Thank you! Edit- Nvm i just saw you recent comment now. thats it. ty :) – Ricardo Vilaça Jul 08 '21 at 16:18
  • The `context or {}` is a preference thing, right? I could just set it in the parameters – Ricardo Vilaça Jul 08 '21 at 16:21
  • 1
    That is possible. Another (maybe more likely) possibility is that, as your project grows in complexity, you add code inside your constructor, possibly before(!) you set `self.context`, maybe even call a helper method where you access `self.context` without realizing that it still references the class attr. – user2390182 Jul 08 '21 at 16:22
  • 1
    `context or {}` is definitely a bit of syntactic sugar, but you should avoid `context={}` in the constructor signature at all costs. See e.g. https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument for some explanation on this very common trap. This may not be a problem since we do not modify or keep any reference to the passed dict, but it is still a good practice to avoid it. – user2390182 Jul 08 '21 at 16:24
  • Very interesting! I was not aware of this behavior of default parameters... ! However, it doesn't matter in this particular case because, if i'm getting this right, we overwrite `self.context` everytime `__init__` runs, therefore we have no problems with the possibly modified parameter value. Would this problem apply between two instances? For example, if i modified the default parameter of `__init__` in a first instance, would a second instance of that class start with a modified parameter as well? I thought the two `__init__` functions would be treated as different objects (?) – Ricardo Vilaça Jul 09 '21 at 13:34
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234692/discussion-between-schwobaseggl-and-ricardo-vilaca). – user2390182 Jul 09 '21 at 13:35