Is there a generally accepted best practice for creating a class whose instances will have many (non-defaultable) variables?
For example, by explicit arguments:
class Circle(object):
def __init__(self,x,y,radius):
self.x = x
self.y = y
self.radius = radius
using **kwargs:
class Circle(object):
def __init__(self, **kwargs):
if 'x' in kwargs:
self.x = kwargs['x']
if 'y' in kwargs:
self.y = kwargs['y']
if 'radius' in kwargs:
self.radius = kwargs['radius']
or using properties:
class Circle(object):
def __init__(self):
pass
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
self._radius = value
For classes which implement a small number of instance variables (like the example above), it seems like the natural solution is to use explicit arguments, but this approach quickly becomes unruly as the number of variables grows. Is there a preferred approach when the number of instance variables grows lengthy?
I'm sure there are many different schools of thought on this, by here's how I've usually thought about it:
This should usually be your method of first attack. If you find however that your list of things you are passing in is getting too long, it is likely pointing to more of a structural problem with the code. Do some of these things you are passing in share any common ground? Could you encapsulate that in a separate object? Sometimes I've used config objects for this and then you go from passing in a gazillion args to passing in 1 or 2
e.g.
def do_it(a, b, thing=None, zip=2, zap=100, zimmer='okay', zammer=True):
# do some stuff with a and b
# ...
get_er_done(abcombo, thing=thing, zip=zip, zap=zap, zimmer=zimmer, zammer=zammer)
Instead becomes:
def do_it(a, b, **kwargs):
# do some stuff with a and b
# ...
get_er_done(abcombo, **kwargs)
Much cleaner in cases like this, and can see get_er_done
for the full signature, although good docstrings can also just list all the arguments as if they were real arguments accepted by do_it
The *args and **kwargs syntax is super useful, but also can be super dangerous and hard to maintain as you lose the explicit nature of what arguments you can pass in. I usually like to use these in situations when I have a method that basically is just a wrapper around another method or system and you want to just pass things through without defining everything again, or in interesting cases where the arguments need to be pre-filtered or made more dynamic, etc. If you are just using it to hide the fact that you have tons and tons of arguments and keyword arguments, **kwargs will probably just exacerbate the problem by making your code even more unwieldy and arcane.
__init__
'sI actually really like taking advantage of the getter and setter properties, especially when I am doing tricky stuff with private versions of those attributes that I don't want to expose. It can also be good for config objects and other things and is nice and explicit, which I like. However, if I am initializing an object where I don't want to allow half-formed ones to be walking around and they are serving no purpose, it's still better to just go with explicit argument and keyword arguments.
**kwargs and properties have nice specific use cases, but just stick to explicit keyword arguments whenever practical/possible. If there are too many instance variables, consider breaking up your class into hierarchical container objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With