0

I want to create a set of objects through inheritance, and I need some advice to organize my implementation.

All objects must inherit from a BaseObject, as so:

class BaseObject:
    class Actuator:
        pass
    class Sensor:
        pass

I want to ensure that all objects inheriting from BaseObject are forced to have a class Actuator and a class Sensor.

Then I have another abstraction level, with some generics objects, for example:

class Camera(BaseObject):
    class Actuator:
        pass
    class Sensor:
        def get_frame():
            pass
        def set_parameter():
            pass

And I want every Camera object I create to have at least get_frame and set_parameters.

I want to force implementation of these classes/function to keep the same synthax for every new object, so that if I want to work with another camera, I can keep my previous scripts, I just need to create a new Camera object. You should also know that these classes won't be instanciated directly, but are a patern to build on.

I looked at metaclasses, wich looks nice to force implementation of methods in a class but I'm not sure if:

  • you can create a metaclass (Camera) from a metaclass BaseObject
  • you can force implementation of a class in a metaclass (like Actuator in BaseObject) and how (via @abc.abstractmethod ?)
  • you can force implementation of method inside a class inside a metaclass? (like get_frame())
  • Is it really the only way or should I organize my classes totaly differently?
  • Should I better go back to making paperplanes ? (Please don't answer this one)
CoMartel
  • 3,521
  • 4
  • 25
  • 48
  • 3
    Why *nested* classes? Could you elaborate on the thinking behind the current structure? – jonrsharpe May 21 '15 at 14:24
  • 1
    You need to rethink your design. This is basically un-testable. – Lawrence Aiello May 21 '15 at 14:26
  • 2
    "All objects must inherit from a BaseObject, as so". Nesting is not inheritance. Which do you mean? – chepner May 21 '15 at 14:27
  • The standard inheritance pattern of `class Foo(object)` and `class Bar(Foo)` seems like it would work fine here. – kylieCatt May 21 '15 at 14:27
  • @jonrsharpe : All the objects I will use can have `Sensor` or `Actuator` methods. I want the use of these object as generic as possible, so when I change the camera object, I still can use my scripts because calling Camera.Sensor.get_frame() will be existing. But maybe I can do that without nesting ? – CoMartel May 21 '15 at 14:28
  • Harry, I don't quite understand why do you need to do such complicated things. You can just have some kind of objects which have both actuators and sensors. They might have (or not) the same class. Let's say you have a Camera and a Air Conditioning Machine. An instance of them may have a sensor, and a respective actuator. – finiteautomata May 21 '15 at 14:28
  • 1
    But why are you calling all of the methods on the *classes*, rather than *instances*? Are `Sensor` and `Actuator` *classes* or *methods*? Shouldn't that be something like `Camera(whatever, it, needs).sensor.get_frame()`? How would these classes be used? – jonrsharpe May 21 '15 at 14:29
  • A nested class question was [just asked](http://stackoverflow.com/questions/30376127/is-it-a-good-practice-to-make-nested-class-in-python), see it for reasons on why not to do it! – Matthew May 21 '15 at 14:30
  • Every object can be use as a sensor or as an actuator, so I want to regroup methods into classes sensor or actuator of each object. If I need my camera to be a sensor, I should call camSensor=Camera.Sensor.get_frame(), if it should be an actuator, I should call camActuator=Camera.Actuator.randomFunction() – CoMartel May 21 '15 at 14:36
  • The goal here is to simplify the usage of these objects, as it won't be used by developpers. I just need a way to organize it properly and ensure that any new object implemented will follow the same pattern. – CoMartel May 21 '15 at 14:38
  • @HarryPotfleur: I might be wrong but it looks like you don't quite grep some basic OO concepts - like the difference between class and instance. But anyway: read Ionut Hulub's answer, it's probably as close as possible to what you want. – bruno desthuilliers May 21 '15 at 15:04

2 Answers2

3

I would implement them like so:

from abc import ABCMeta

class BaseObject:
    __metaclass__ = ABCMeta
    def __init__(self):
        self.actuator = self._get_actuator()
        self.sensor = self._get_sensor()
    @abstractmethod
    def _get_actuator(self):
        return 'actuator'
    @abstractmethod
    def _get_sensor(self):
        return 'sensor'

Every subclass will have an actuator and a sensor param. Make sure you call super.__init__ if you override __init__ in child classes.

Class Sensor:
    __metaclass__ = ABCMeta
    pass

class CameraSensor(Sensor):
    __metaclass__ = ABCMeta
    @abstractmethod
    def get_frame():
        pass
    @abstractmethod
    def set_parameter():
        pass

class Camera(BaseObject):
    def _get_sensor(self):
        # get the sensor
        assert issubclass(sensor, CameraSensor)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
Ionut Hulub
  • 6,180
  • 5
  • 26
  • 55
  • as far as I understand the OP's requirements, `get_frame()` should be part of the `Sensor` ABC - I mean, all Sensor subclasses should have this method. Also, `Camera._get_sensor() should obviously return a `CameraSensor` instance (I assume that's what you meant but it actually fails), and I would raise a `NotImplementedError` in `BaseObject._get_sensor()` and `BaseObject._get_camera()` instead of returning an incompatible type. – bruno desthuilliers May 21 '15 at 15:03
  • @brunodesthuilliers: in fact, all objects should have a `Sensor` and an `Actuator` class (as children of `BaseObject`), but only `Camera` (wich is also a child of `BaseObject`) should have a `get_frame()` method in the `Sensor` class – CoMartel May 21 '15 at 15:08
  • @HarryPotfleur: First point : I still don't understand why you insist on nested classes - what you want is that all your `BaseObject` subclasses have a `sensor` and an `actuator` attributes, but there are no reason for these attributes to be classes, and quite a few reason for them _not_ to be classes. Second point: if only `Camera.sensor` is supposed to have a `get_frame()` method - I mean, if the expected interfaces for `sensor` and `actuator` depend on the `ObjectBase` subclass, then it just doesn't make any sense _at all_ to enforce they all have a `sensor` and `actuator` attributes. – bruno desthuilliers May 21 '15 at 15:16
  • @brunodesthuilliers: I don't need nested classes, I just want to organize my methods properly for an easy use, and that's why I look for a better solution that nesting (but I don't know how). The same apply to forcing `sensor` and `actuator`, I want to ensure users that create new object will have to follow the same synthax, and all my objects will have methods that can be classified either as `sensor` or `actuator`. – CoMartel May 21 '15 at 15:41
  • In which way is adding a (technically useless so far) level of indirection supposed to make for an "easy use" ??? https://www.python.org/dev/peps/pep-0020/ – bruno desthuilliers May 21 '15 at 15:47
2

I want to force implementation of these classes/function to keep the same syntax for every new object, so that if I want to work with another camera, I can keep my previous scripts, I just need to create a new Camera object.

OT : do you really mean "object" (=> instance), or subclass ? But anyway:

Since you mentionned that "Sensor" and "Actuator" are not necessarily supposed to have the same interface for different BaseObject subclasses, let's ignore the whole BaseObject part for the moment and concentrate on the Camera part.

If I understand correctly, what you want is generic Camera type. This type must have a sensor and an actuator attributes, which both must respect a given interface, but with possibly different implementations.

At this point we (well, I at least) don't have enough context to decide if we need an abstract BaseCamera type or if we just need abstract BaseCameraSensor and BaseCameraActuator interfaces. What is sure is that we do need BaseCameraSensor and BaseCameraActuator so let's start with this. I assume the sensor and actuator need to be aware of the camera they belong too, which FWIW really screams "strategy" pattern, so we start with a base class - let's call it "BaseStrategy" - that don't do much except get a reference to it's host object:

class Strategy(object):
    def __init__(self, parent):
        self._parent = parent

Now let's define our "CameraSensor" and "CameraActuator" interfaces:

class BaseCameraSensor(Strategy):
    __metaclass__ = ABCMeta

    @abstractmethod
    def get_frame(self):
        raise NotImplementedError

    @abstractmethod
    def set_parameter(self, value):
        raise NotImplementedError

class BaseCameraActuator(Strategy):
    __metaclass__ = ABCMeta

    @abstractmethod
    def random_method(self):
        raise NotImplementedError

Now we can take care of the "Camera" part. If the only variant parts of the implementation are encapsulated in the Sensor and Actuator (which is the point of the strategy pattern), then we don't need an abstract base class - we can just pass the appropriate Sensor and Actuator subclasses as params to the initialiser:

class Camera(object):
    def __init__(self, brand, model, sensor_class, actuator_class):
        self.brand = brand
        self.model = model

        assert issubclass(sensor_class, BaseCameraSensor)
        self.sensor = sensor_class(self)

        assert issubclass(actuator_class, BaseCameraActuator)
        self.actuator = actuator_class(self)

And the problem is solved. FWIW, note that you don't really need the Strategy class nor the ABC part, just documenting the expected interface for sensor_class and actuator_class and let the program crash if they are not respected is enough - that's how we've been programming in Python for years (15+ years as far as I'm concerned) and it JustWork(tm) - but ABC at least makes the contract clearer and the program will crash sooner. But don't be fooled: it wont make your code foolsafe (hint: nothing will, period).

Now if we have some other variant parts that implementation cannot be known ahead of time, the simplest solution would be to follow the same pattern - delegate to a Strategy object that is passed at instanciation time. So the point is: now we have this implemented, we find out we don't have a need for some BaseCamera ABC.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thanks for your very precise and detailed answer, that explains a lot. Even if I am not sure to use it as it seems I am creating an awfully complicated thing, I certainly learned a lot and it will definitly help me improve my work. Thanks again for patiently explaining all this! – CoMartel May 22 '15 at 09:03
  • Some problems are inherently complex and require complex solutions, but "complex" is indeed not the same thing as "complicated". You may want to read this by the way : http://blog.codinghorror.com/kiss-and-yagni/ – bruno desthuilliers May 22 '15 at 09:30