2

In C++ I often break up large classes with 'accessor' structs like the following

struct Big {
    struct Coordinates {
        int x;
        int y;
    } coords;
}

Which avoids namespace pollution and makes call-sites very clear eg: big.coords.x; In particular this is useful for avoiding having many functions called 'set_x' which becomes 'set.x' instead

I'd like to do the same thing in Python such as in the below, but am wondering is this considered good, pythonic style?

class Big:
    class Coordinates:
        x = 0
        y = 0
    coords = Coordinates()

big.coords.x
Madden
  • 1,024
  • 1
  • 9
  • 27
  • 4
    Storing an instance of another class inside a class instance is a normal and useful thing to do. Actually *nesting* the definition of that other class inside the using class, not so much... Is there really no possibility that you'd ever want to use a `Coordinates` instance independently of `Big`? – jasonharper May 15 '20 at 13:15
  • In this example `Coordinates` is definitely more generic, but consider if the contained class was something specific to the `Big` class. In that scenario the fully qualified name clearly states the relationship between them – Madden May 15 '20 at 13:19
  • It simply isn't common in Python to do this. An instance of `Big` might have an instance of `Coordinates` as an attribute, but only in exceptional circumstances would `Coordinates` itself be an attribute of `Big`. Just because `Coordinates` is only *used* by `Big` doesn't make it *part* of `Big`. Classes are more than just syntax. – chepner May 15 '20 at 19:29

1 Answers1

0

If you never instantiate your classes it is perfectly OK to do this for namespacing alone:

class Colors:
    class RGB:
        Red = (255, 0, 0)
        Green = (0, 255, 0)
        Blue = (0, 0, 255)
    class CMYK:
        pass

color1 = Colors.RGB.Red

However this is different:

class Big:
    class Coordinates:
        x = 0
        y = 0
    coords = Coordinates()

You instantiate a Coordinates class object in Big class, specifically in Big.coords.

x and y are 0 for every Coordinates object instance, at start. But they are also class attributes, not instance attributes. This means they are shared between every instance, until you assign one of them in the instance. Then that particular instance starts to have its own x or y. This is totally diferent from what you seem to expect. You should make it like:

class Coordinates:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

class Big:
    def __init__(self, coords=None):
        self.coords = coords or Coordinates(0,0)

    def __str__(self):
        return "Big coords at x:{} y:{}".format(self.coords.x, self.coords.y)

big = Big()
print(big)
print(big.coords.x, big.coords.y)

point1 = Coordinates(1, 2)
big1 = Big(point1)
print(big1)

Output:

Big coords at x:0 y:0
0 0
Big coords at x:1 y:2

This way you make one instance contain an instance of another type. This is also an example of using containment instead of inheritance.

Another common way of arranging namespaces is to just organize your names inside modules to import.

See here for other ideas.

progmatico
  • 4,714
  • 1
  • 16
  • 27