lilbanili
lilbanili

Reputation: 137

Groups of optional arguments for a function - python

Okay so I have to make two classes (in two different scripts), both called Block, which store information about the position and size of a rectangular block. Version 1 should have attributes to store the coordinates of the center of the block (either as separate x- and y-coordinates or as a pair* of numbers) and for the width and height of the block. Version 2 should have attributes to store the coordinates of the bottom-left corner (the "SW" corner) and the coordinates of the upper-right corner (the "NE" corner).

So I know how I would set up the constructor for each one separately, but for this assignment both versions should have a constructor that will take either a pair of coordinates for the center along with the width and height (as floating point numbers), or two pairs of coordinates representing any two opposite corners of the block. This is what I tried so far:

class Block:
    """Stores information about the position and size of a rectangular block.

    Attributes: x-coordinate (int), y-coordinate (int), width (int), height (int) OR northeast corner (int) and southwest corner (int)"""

    def __init__(self, center = '', width = '', height = '', SW = '', NE = ''):
        """A constructor that assigns attributes to the proper variables

        Block, tuple, tuple -> None"""
        self.center = center
        self.width = width
        self.height = height
        self.SW = SW
        self.NE = NE

but I'm pretty sure that doesn't actually work the way I want it to. Basically I need to be able to input either a set of variables as the center, width and height, OR I need to input the two corners. Is there a way to do this?

Upvotes: 0

Views: 104

Answers (2)

SoloPilot
SoloPilot

Reputation: 1534

You are almost there. Try something like this...

class Block:
    def __init__(self, center = '', width = '', height = '', SW = '', NE = ''):
        if SW != ''  or  NE != '':
            if SW == ''  and  NE ==  '':   # usage error
                return None                # throw an exception here
            self.center = getCenterFromCorners(SW, NE)  # ((sw[0]+ne[0])/2, ...)
            self.width = getWidthFromCorners(SW, NE)    # abs(sw[0]-ne[0])
            self.height = getHeightFromCorners(SW, NE)  # abs(sw[1]-ne[1])
        else:
            if center == ''  or  width == ''  or '' height == '':
                return None                # throw exception
            self.center = center
            self.width = width
            self.height = height
        return self

# usage: block1 and block2 should be similar
block1 = Block(center=(10,20), height=2, width=4)
block2 = Block(SW=(9,18), NE=(11,22))

I am sure you can replace the code for getCenterFromCorners(), ...

Upvotes: 0

chepner
chepner

Reputation: 532418

You have to examine which arguments are passed to the function and act accordingly. Typically, what you want to do is pick one canonical representation to use to actually store the data, and have __init__ convert whatever arguments are passed to it to the canonical form. For example:

# Use None to represent missing data. Think about it: "hello" is not a 
# valid width; neither is "".
def __init__(self, center=None, width=None, height=None, SW=None, NE=None):
    """A constructor that assigns attributes to the proper variables

    Block, tuple, tuple -> None"""

    if center is not None and width is not None and height is not None:
        # If either SW or NE is given, ignore them 
        self.center = center
        self.width = width
        self.height = height
    elif SW is not None and NE is not None:
        # _convert_corners is a helper function you define
        self.center, self.width, self.height = _convert_corners(SW, NE)
    else:
        # Not entirely true. Give width, height, and one corner, you
        # could reconstruct the center, but this is just an example.
        raise ValueError("Insufficient information to construct Block")

You can use properties to compute other attributes on the fly, rather than store them redundantly:

@property
def SW(self):
    # return the south-west corner as computed from
    # self.center, self.height, and self.width

@property
def NE(self):
    # return the north-east corners computed from
    # self.center, self.height, self.width

Another approach is to use class methods to supply alternate constructors.

def __init__(self, center, width, height):
    "Define a block by its center, width, and height"
    self.center = center
    self.width = width
    self.height = height

@classmethod
def from_corners(self, sw, ne):
    "Define a block by two corners"
    c, w, h = _convert_corners(sw, ne)
    return Block(c, w, h)

In use:

# For demonstration purposes, I'm assuming points like the center
# and the corners are simple tuples of integer coordinates
b1 = Block((10, 50), 5, 7)
b2 = Block.from_corners((20, 30), (40, 70))

Upvotes: 2

Related Questions