38

I love how beautiful python looks/feels and I'm hoping this can be cleaner (readability is awesome).

What's a clean way to accept an optional keyword argument when overriding a subclassed init where the optional kwarg has to be used after the super() call?

I have a django form where I'd like to accept an optional user argument, but if I define it as one of the arguments user=None, then the usual form call form({}) assumes the positional argument refers to the keyword argument user.

Code speaks better than (my) words:

def __init__(self, *args, **kwargs):
    user = None
    if 'user' in kwargs:
        user = kwargs.pop('user')
    super(BaseCheckoutForm, self).__init__(*args, **kwargs)
    if user:
        self.prefill_from_user(user)

I can make this the cleanest by looking into the actual Form class to see what arguments it's looking for, but one of the greatest things about subclassing anything in python is collecting all args and kwargs and passing it into whatever you subclassed. Also this doesn't hold up to any changes in the source.

def __init__(self, querydict=None, user=None):
    super(BaseCheckoutForm, self).__init__(querydict)
    if user:
        self.prefill_from_user(user)

But unfortunately I have:

 def __init__(self, *args, **kwargs):
    # cannot define user=None as an argument because normal usage
    # of class expects a certain order of positional args
    user = None
    if 'user' in kwargs:
        # must pop now since super()__init__ isn't expecting a user kwarg
        user = kwargs.pop('user') 
    super(BaseCheckoutForm, self).__init__(*args, **kwargs)
    if user:
        self.prefill_from_user(user)

Thanks for any input!

Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • 2
    One tip: Check for `None` via `x is not None`. `not x` is `True` for several possibly valid values (like empty collections (including strings), zero(-equivalent numbers), obviously `False`, etc.). It's also more explicit/readable, some would say ;) –  Feb 17 '11 at 16:39
  • I think it's more readable with just `if user:` and the other values would not be valid anyways (False, '', []). Would it be OK in my case then? – Yuji 'Tomita' Tomita Feb 17 '11 at 16:45
  • 1
    As long as you're okay with any value passed in as `user` that doesn't equate to a logical `True` being ignored, then yes, it's fine. However, in many cases you can be burned by this. Just be aware of the differences! – Joe Kington Feb 17 '11 at 16:55

1 Answers1

60

I usually just do essentially what you're doing here. However, you can shorten/clean up your code by supplying a default argument to dict.pop:

 def __init__(self, *args, **kwargs):
    user = kwargs.pop('user', None)
    super(BaseCheckoutForm, self).__init__(*args, **kwargs)
    if user is not None:
        self.prefill_from_user(user)
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 3
    Ohh, very nice! I didn't realize pop had a second argument. Can you tell I don't `pop` that often? That helps! – Yuji 'Tomita' Tomita Feb 17 '11 at 16:38
  • 6
    @Yuji - It's certainly a nice feature! Don't forget that you can use a default value for `dict.get`, as well to avoid `KeyError` s. I.e. use `x.get('a', defaultvalue)` instead of `x['a']` if you want to avoid a KeyError when `'a'` isn't in `x`. – Joe Kington Feb 17 '11 at 16:40
  • 3
    @Joe it should be noted though that [`dict.get()` has `None` as a default for 2nd argument](https://docs.python.org/2/library/stdtypes.html#dict.get), so it can never cause `KeyError` - it returns `None` for nonexistant keys. – havelock Apr 22 '15 at 15:42
  • @havelock Yeah that's what he was saying. Use x.get instead of x['a']. – dave4jr Mar 30 '17 at 08:58
  • To make the above more generalized, use ```super(self.__class__, self)``` instead of ```super(BaseCheckoutForm, self)``` – mwag Jan 07 '18 at 20:54