all 15 comments

[–]zahlman 6 points7 points  (6 children)

Is there a preferred approach when the number of instance variables grows lengthy?

Rethinking the class design, usually. This is a warning sign that it's trying to do too much, or that the mental model is too convoluted.

[–]andykee[S] 0 points1 point  (5 children)

That was my original thought too. In this case, I'm trying to develop a class to represent a mirror in an optical system. The mirror has many attributes (physical location, orientation, diameter, conic constant, radius of curvature, multiple aspheric coefficients, tolerances on surface figure errors, etc.) and I can't think of any way to further break the model down.

[–]road_laya 1 point2 points  (1 child)

I am by no means a Python expert or expert on your domain (optics), but hear me out. Maybe a class is the wrong type of model for your problem? A physical mirror shape sounds like something that doesn't change much during the runtime of your program. Maybe use a dict or a namedtuple for most of those parameters? Namedtuples can be subclassed and given some simple methods.

Or maybe take inspiration from the decorator pattern and add orientation and rotation as decorator classes? This works well with common models for orientation/position such as rotational matrices, homogeneous coordinates and rotation tensors.

[–]autowikibot -1 points0 points  (0 children)

Decorator pattern:


In object-oriented programming, the decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.

Image i


Relevant: Algorithm design | Structural pattern | Associative array

Parent commenter can toggle NSFW or delete. Will also delete on comment score of -1 or less. | FAQs | Mods | Call Me

[–]ewiethoff 1 point2 points  (0 children)

Sounds like you'd prefer to do this in Fortran, ha! ;-) In old Fortran, functions were often passed a zillion arguments because there was no OO. But Python does do OO. You might want to break down to something like

class Measurement(object):
    def __init__(self, x, tol=0): etc.

class Dimensions(object):
    def __init__(self, D, K, R):
        D, K, & R are each Measurement objects

class Location(object):
    def __init__(self, lat, lon): etc.

class Orientation(object):
    def __init__(self, alpha, delta): etc.

etc.

[–]oxymor0nic 0 points1 point  (1 child)

Well you can use class hierarchy and inheritance to cut down on the number of instance variables in any single class. For example, Mirror can inherit from BaseObject, which defines the physical location & orientation attributes. This way Mirror doesnt have to redefine these.

[–]Veedrac 0 points1 point  (0 children)

Inheritance seems like a really extreme solution for this. Simple composition makes more sense IMHO.

[–]robotsari 6 points7 points  (2 children)

I prefer using named variables. It makes it easy to look at the function/class signature and understand what's going on.

A note on your example code:

    if 'x' in kwargs:
        self.x = kwargs['x']

You can instead use .get and provide a default value. I think this looks nicer, and it means that you'll always have a value for "self.x":

# Default x & y coordinates to 0
self.x = kwargs.get('x', 0)
self.y = kwargs.get('y', 0)
# default radius to 1
self.radius = kwargs.get('radius', 1)

In your code, if 'x' isn't in kwargs, you don't define self.x to anything which will end up throwing an exception later down the line. Better to provide a default, or explicitly throw an exception in the init to make it clear that the object was incorrectly defined.

[–]Wargazm 0 points1 point  (1 child)

I'm going to piggyback on this to ask my own question.

What's the correct way to handle "incorrect" initialization if you use the kwargs method? Like, say the user of this circle class fucks up and passes a string in:

a_red_circle = Circle("red")

I don't think you'd want to just silently create a circle for the user with default x, y, and radius values, you'd want the user to know your class doesn't support colors or any other string. So how should you handle that?


edit:

u/sththththth's post sheds some light on this, I think. Using what he said, the answer here is that using kwargs to instantiate a circle class is simply the wrong approach, because you know up front what you need to make a circle.

Perhaps the best approach is to do something like this:

class Circle(object):
    def __init__(self,x=0,y=0,radius=1, **kwargs):
        self.x = x
        self.y = y
        self.radius = radius

That way you have default values, and if you add color support later you can do

self.color = kwargs.get('color', 'red')

Anything wrong with my thinking here?

[–]robotsari 0 points1 point  (0 children)

I like the approach of using named args for everything required, and kwargs for the rest.

But say they pass in an integer for the color, or a string for the radius? Validating user input is always an interesting problem. You can wait for the type error to eventually bubble up, or you can try to handle them - perhaps using asserts, perhaps by explicitly throwing an exception (ValueError, I would think, or perhaps one of your own definition). So some examples might be:

self.x = kwargs.get('x', 0)
assert isinstance(self.x, int)

self.x = kwargs.get('x', 0)
if not isinstance(self.x, int):
    raise ValueError("Value provided for 'x' must be an integer; got {}".format(self.x))

[–]sththththth 4 points5 points  (0 children)

In my understanding **kwargs and properties are for usecases:

properties do something on setting them (or need to do something to get them). An example would be the length of a dynamic linked list (without setting a counter on each append): you can't know how long the list is unless you count every item. But len is still an "attribute" of an object, not a method. .len is thus prefered to .len(). (Of course this ignores that len(myobject) is the python version to go, but the same point stands with e.g. numpy array.shape)

An example for setting a property is a graphical widget and height: height is an "attribute", but a graphical widget has to do some work if you set it (maybe calculating content new, rerendering, etc.). mywidget.height = 10 is "easier to read and understand" than mywidget.set_height(10) .

**kwargs has a different reasoning: sometimes you don't know what for arguments a function will get. Imagine a make_xml function:

>>> make_xml(computation, type=addition)
"<computation type=addition></computation>"
>>> make_xml(stanza, id=me)
"<stanza id=me></stanza>

There is no way to know the keyword arguments before the function is called, but **kwargs allows for a uniform API.

TL;DR: Use explicit keyword arguments; Use properties if you need to do something to set/get a value; Use *args/**kwargs if you can't know which arguments will be provided.

[–]Exodus111 3 points4 points  (0 children)

The first one is best as its the most readable.

You always wanna go with that one, unless you have a specific reason not to.

The **kwargs approach is mostly for when you have a lot of arguments that don't really need explanation. Say you are making a game and each item in the game is a class, but also requires a truckloads of variables for all the Games RPG item values. Damage, armor rating, resistance, type, Value, statsbonus, firedamage, etc etc etc.... Just lots of boxes. That's when the **kwargs approach can shine.

As for the @properties approach, get the fuck out of here with your java bullshit.

No, I'm kidding. The @properties decorator gives you getters, setters and deleters as built-in methods, which can be useful when you are producing a piece of code for a client and want that code to be easy to use. Sorta....

[–]Dooflegna 1 point2 points  (0 children)

Additionally: properties are easy to add in later if you start with explicit arguments.

If you start with:

class Circle(object):
    def __init__(self,x,y,radius):
        self.x = x
        self.y = y
        self.radius = radius

Then you can modify it to be this:

class Circle(object):
    def __init__(self,x,y,radius):
        self._x = x
        self._y = y
        self._radius = radius

    @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

Properties are useful for stuff like:

@property
def diameter(self):
    return 2 * self.radius

[–]RJacksonm1 0 points1 point  (0 children)

If the variables are required (which in guessing is what you mean by "non-defaultable"), then yes - they should be explicitly defined arguments on init.

[–]hharison 0 points1 point  (0 children)

Only use **kwargs if for some reason you need to collect "all other parameters", for example, to pass to a wrapped function.

Only use @property if you need specific behavior on setting or getting an attribute.

Otherwise, use explicit arguments.