0

Is there a better way of doing this?

def __init__(self,**kwargs):
        self.ServiceNo = kwargs["ServiceNo"]
        self.Operator = kwargs["Operator"]
        self.NextBus = kwargs["NextBus"]
        self.NextBus2 = kwargs["NextBus2"]
        self.NextBus3 = kwargs["NextBus3"]

The attributes (ServiceNo,Operator,...) always exist

Fishball Nooodles
  • 474
  • 1
  • 4
  • 11
  • Related: [Is there a shortcut for `self.somevariable = somevariable` in a Python class constructor?](https://stackoverflow.com/q/12191075/674039) and [Is self.__dict__.update(**kwargs) good or poor style?](https://stackoverflow.com/q/9728243/674039) – wim Sep 30 '21 at 01:17

3 Answers3

2

That depends on what you mean by "simpler".

For example, is what you wrote simpler than what I would write, namely

def __init__(self,ServiceNo, Operator, NextBus, NextBus2, NextBus3):
    self.ServiceNo = ServiceNo
    self.Operator = Operator
    self.NextBus = NextBus
    self.NextBus2 = NextBus2
    self.NextBus3 = NextBus3

True, I've repeated each attribute name an additional time, but I've made it much clearer which arguments are legal for __init__. The caller is not free to add any additional keyword argument they like, only to see it silently ignored.

Of course, there's a lot of boilerplate here; that's something a dataclass can address:

from dataclasses import dataclass


@dataclass
class Foo:
    ServiceNo: int
    Operator: str
    NextBus: Bus
    NextBus2: Bus
    NextBus3: Bus

(Adjust the types as necessary.)

Now each attribute is mentioned once, and you get the __init__ method shown above for free.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Minor note: If you wish to force the arguments to be received by keyword to avoid a forward compatibility requirement to maintain order (a possible reason the OP received only as `**kwargs`), that's still possible with individually named arguments, you just change the `def` for `__init__` to: `def __init__(self, *, ServiceNo, Operator, NextBus, NextBus2, NextBus3):` (the `*` with no name prevents the arguments to its right from being received positionally). – ShadowRanger Sep 30 '21 at 01:05
  • I think I skipped mentioning that because I knew I'd be introducing dataclasses right away, and then you're back to positional arguments being accepted. I don't think there's anyway to make the dataclass generate an `__init__` that requires keyword-only arguments, but that would be nice. – chepner Sep 30 '21 at 11:23
0

Better how? You don’t really describe what problem you’re trying to solve.

If it’s error handling, you can use the dictionary .get() method in the event that key doesn’t exist.

If you just want a more succinct way of initializing variables, you could remove the ** and have the dictionary as a variable itself, then use it elsewhere in your code, but that depends on what your other methods are doing.

seve
  • 159
  • 12
-1

A hacky solution available since the attributes and the argument names match exactly is to directly copy from the kwargs dict to the instance's dict, then check that you got all the keys you expected, e.g.:

def __init__(self,**kwargs):
    vars(self).update(kwargs)
    if vars(self).keys() != {"ServiceNo", "Operator", "NextBus", "NextBus2", "NextBus3"}:
        raise TypeError(f"{type(self).__name__} missing required arguments")

I don't recommend this; chepner's options are all superior to this sort of hackery, and they're more reliable (for example, this solution fails if you use __slots__ to prevent autovivication of attributes, as the instance won't having a backing dict you can pull with vars).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271