14

我正在编写一个用于模拟分布式传感器群的 python 平台。这个想法是最终用户可以编写一个由 SensorNode 行为(通信、日志记录等)组成的自定义节点,并实现许多不同的传感器。

下面的例子简要地演示了这个概念。

#prewritten
class Sensor(object):
  def __init__(self):
    print "Hello from Sensor"
  #...

#prewritten
class PositionSensor(Sensor):
  def __init__(self):
    print "Hello from Position"
    Sensor.__init__(self)
  #...

#prewritten
class BearingSensor(Sensor):
  def __init__(self):
    print "Hello from Bearing"
    Sensor.__init__(self)
  #...

#prewritten
class SensorNode(object):
  def __init__(self):
    print "Hello from SensorNode"
  #...

#USER WRITTEN
class MySensorNode(SensorNode,BearingSensor,PositionSensor):
  def CustomMethod(self):
    LogData={'Position':position(), 'Bearing':bearing()} #position() from PositionSensor, bearing() from BearingSensor
    Log(LogData) #Log() from SensorNode

新编辑:

首先概述我想要实现的目标:我正在编写一个模拟器来模拟群体智能算法,特别关注移动传感器网络。这些网络由许多小型机器人组成,它们相互交流各个传感器数据,以构建复杂的环境感官地图。

该项目的基本目标是开发一个模拟平台,为传感器提供抽象接口,以便可以将相同的用户实现功能直接移植到运行嵌入式 linux 的机器人群中。由于机器人实现是目标,因此我需要设计成软件节点的行为相同,并且只能访问物理节点所拥有的信息。

作为模拟引擎的一部分,我将提供一组模拟不同类型传感器和不同类型传感器节点的类。我希望从用户那里抽象出所有这些复杂性,以便用户所要做的就是定义节点上存在哪些传感器,以及正在实施哪种类型的传感器节点(移动、固定位置)。

我最初的想法是每个传感器都会提供一个 read() 方法,该方法将返回相关值,但是在阅读了对问题的回答后,我发现可能更具描述性的方法名称会是有益的(.distance(), .position( )、.bearing() 等)。

我最初想为传感器使用单独的类(具有共同的祖先),以便技术更高的用户可以轻松地扩展现有类之一以创建新的传感器,如果他们愿意的话。例如:

Sensor
  |
DistanceSensor(designed for 360 degree scan range)
    |           |           |
IR Sensor   Ultrasonic    SickLaser
(narrow)    (wider)       (very wide)

我最初考虑多重继承的原因(尽管它半破坏了继承的 IS-A 关系)是由于模拟系统背后的基本原理。让我解释:

用户实现的 MySensorNode 不应直接访问其在环境中的位置(类似于机器人,通过传感器接口间接访问),同样,传感器不应知道它们在哪里。然而,这种缺乏直接知识带来了一个问题,因为传感器的返回值都取决于它们在环境中的位置和方向(需要对其进行模拟才能返回正确的值)。

SensorNode,作为模拟库中实现的一个类,负责在 pygame 环境中绘制 MySensorNode - 因此,它是唯一应该直接访问环境中传感器节点的位置和方向的类。

SensorNode 还负责环境中的平移和旋转,但是这种平移和旋转是电机驱动的副作用。

我的意思是机器人不能直接改变它们在世界中的位置,它们所能做的就是为电机提供动力,而在世界中的运动是电机与环境相互作用的副作用。我需要在模拟中准确地建模。

因此,要移动,用户实现的功能可以使用:

motors(50,50)

作为副作用,此调用将改变节点在世界中的位置。

如果 SensorNode 是使用组合实现的,SensorNode.motors(...) 将无法直接更改实例变量(例如位置),MySensorNode.draw() 也不会解析为 SensorNode.draw(),因此 SensorNode imo 应该使用继承来实现。

在传感器方面,像这样的问题组合的好处是显而易见的,MySensorNode 是由许多传感器组成的——说得够多了。

然而,我看到的问题是传感器需要访问它们在世界中的位置和方向,如果你使用组合,你最终会得到如下调用:

>>> PosSensor.position((123,456))
(123,456)

再说一遍 - 想一想,你可以在初始化时将 self 传递给传感器,例如:

PosSensor = PositionSensor(self)

然后稍后

PosSensor.position()

但是这个 PosSensor.position() 需要访问实例的本地信息(在init () 期间作为 self 传递),那么当您可以在本地访问信息时为什么还要调用 PosSensor 呢?同样将您的实例传递给您所组成的对象似乎不太正确,跨越了封装和信息隐藏的界限(即使python在支持信息隐藏的想法方面做得不多)。

如果解决方案是使用多重继承来实现的,这些问题就会消失:

class MySensorNode(SensorNode,PositionSensor,BearingSensor):
  def Think():
    while bearing()>0:
      # bearing() is provided by BearingSensor and in the simulator
      # will simply access local variables provided by SensorNode
      # to return the bearing. In robotic implementation, the
      # bearing() method will instead access C routines to read
      # the actual bearing from a compass sensor
      motors(100,-100)
      # spin on the spot, will as a side-effect alter the return
      # value of bearing()

    (Ox,Oy)=position() #provided by PositionSensor
    while True:
      (Cx,Cy)=position()
      if Cx>=Ox+100:
        break
      else:
        motors(100,100)
        #full speed ahead!will alter the return value of position()

希望这次编辑澄清了一些事情,如果您有任何问题,我很乐意尝试澄清它们

旧事:

构造 MySensorNode 类型的对象时,需要调用超类中的所有构造函数。我不想让用户不得不为 MySensorNode 编写一个自定义构造函数,该构造函数从每个超类调用构造函数。理想情况下,我希望发生的是:

mSN = MySensorNode()
# at this point, the __init__() method is searched for
# and SensorNode.__init__() is called given the order
# of inheritance in MySensorNode.__mro__

# Somehow, I would also like to call all the other constructors
# that were not executed (ie BearingSensor and PositionSensor)

任何见解或一般性评论将不胜感激,干杯:)

旧编辑:做类似的事情:

#prewritten
class SensorNode(object):
  def __init__(self):
    print "Hello from SensorNode"
    for clss in type(self).__mro__:
      if clss!=SensorNode and clss!=type(self):
        clss.__init__(self)

这很有效,因为 self 是 MySensorNode 的一个实例。然而,这个解决方案很混乱。


4

3 回答 3

12

如果您想坚持原来的数据图设计,可以通过使用组合来解决传感器架构。您似乎对 Python 很陌生,所以我会尽量减少使用习语。

class IRSensor:
    def read(self): return {'ir_amplitude': 12}

class UltrasonicSensor:
    def read(self): return {'ultrasonic_amplitude': 63}

class SickLaserSensor:
    def read(self): return {'laser_amplitude': 55}

class CompositeSensor:
    """Wrap multiple component sensors, coalesce the results, and return
    the composite readout.
    """
    component_sensors = []

    def __init__(self, component_sensors=None):
        component_sensors = component_sensors or self.component_sensors
        self.sensors = [cls() for cls in component_sensors]

    def read(self):
        measurements = {}
        for sensor in self.sensors:
            measurements.update(sensor.read())
        return measurements

class MyCompositeSensor(CompositeSensor):
    component_sensors = [UltrasonicSensor, IRSensor]


composite_sensor = MyCompositeSensor()
measurement_map = composite_sensor.read()
assert measurement_map['ultrasonic_amplitude'] == 63
assert measurement_map['ir_amplitude'] == 12

您使用执行器描述的架构问题是通过使用混合和代理(通过__getattr__)而不是继承来解决的。(代理可以是继承的一个很好的替代方案,因为要代理的对象可以在运行时绑定/取消绑定。此外,您不必担心使用此技术在单个构造函数中处理所有初始化。)

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

    def move(self, x, y):
        print 'Moving to', x, y
        self.x, self.y = (x, y)

    def get_position(self):
        return (self.x, self.y)

class CommunicationActuator:
    def communicate(self):
        return 'Hey you out there!'

class CompositeActuator:
    component_actuators = []

    def __init__(self, component_actuators=None):
        component_actuators = component_actuators \
            or self.component_actuators
        self.actuators = [cls() for cls in component_actuators]

    def __getattr__(self, attr_name):
        """Look for value in component sensors."""
        for actuator in self.actuators:
            if hasattr(actuator, attr_name):
                return getattr(actuator, attr_name)
        raise AttributeError(attr_name)


class MyCompositeActuator(CompositeActuator):
    component_actuators = [MovementActuator, CommunicationActuator]

composite_actuator = MyCompositeActuator()
assert composite_actuator.get_position() == (0, 0)
assert composite_actuator.communicate() == 'Hey you out there!'

最后,您可以将它们与一个简单的节点声明一起使用:

from sensors import *
from actuators import *

class AbstractNode:
    sensors = [] # Set of classes.
    actuators = [] # Set of classes.
    def __init__(self):
        self.composite_sensor = CompositeSensor(self.sensors)
        self.composite_actuator = CompositeActuator(self.actuators)

class MyNode(AbstractNode):
    sensors = [UltrasonicSensor, SickLaserSensor]
    actuators = [MovementActuator, CommunicationActuator]

    def think(self):
        measurement_map = self.composite_sensor.read()
        while self.composite_actuator.get_position()[1] >= 0:
            self.composite_actuator.move(100, -100)

my_node = MyNode()
my_node.think()

这应该让您了解刚性类型系统的替代方案。请注意,您根本不必依赖类型层次结构——只需实现一个(可能是隐式的)公共接口。

少旧:

仔细阅读问题后,我发现您所拥有的是钻石继承的经典例子,这是使人们逃向单一继承的邪恶。

您可能不希望这样开始,因为类层次结构在 Python 中意味着蹲下。您要做的是创建一个SensorInterface(传感器的最低要求)并拥有一堆“mixin”类,这些类具有完全独立的功能,可以通过各种名称的方法调用。在您的传感器框架中,您不isinstance(sensor, PositionSensor)应该说诸如“此传感器可以进行地理定位吗?”之类的话。采用以下形式:

def get_position(sensor):
    try:
        return sensor.geolocate()
    except AttributeError:
        return None

这是鸭子打字哲学和EAFP(比许可更容易请求宽恕)的核心,这两者都是 Python 语言所包含的。

您可能应该描述这些传感器将实际实现的方法,以便我们可以描述如何将 mixin 类用于您的插件架构。

老的:

如果他们在一个模块中编写代码,该模块被放入插件包或你有什么,你可以在导入他们的插件模块时神奇地为他们检测类。类似于此片段的内容(未经测试):

 import inspect
 import types

 from sensors import Sensor

 def is_class(obj):
     return type(obj) in (types.ClassType, types.TypeType)

 def instrumented_init(self, *args, **kwargs):
     Sensor.__init__(self, *args, **kwargs)

 for module in plugin_modules: # Get this from somewhere...
     classes = inspect.getmembers(module, predicate=is_class)
     for name, cls in classes:
         if hasattr(cls, '__init__'):
             # User specified own init, may be deriving from something else.
             continue 
         if cls.__bases__ != tuple([Sensor]):
             continue # Class doesn't singly inherit from sensor.
         cls.__init__ = instrumented_init

您可以在具有其他功能的包中找到模块。

于 2009-03-14T12:11:31.143 回答
1

super调用 mro 列表中的下一个类。即使您省略了__init__某些类的表单,这也有效。

class A(object):
  def __init__(self):
    super(A,self).__init__()
    print "Hello from A!"

class B(A):
  def __init__(self):
    super(B,self).__init__()
    print "Hello from B!"

class C(A):
  def __init__(self):
    super(C,self).__init__()
    print "Hello from C!"

class D(B,C):
  def __init__(self):
    super(D,self).__init__()
    print "Hello from D!"

class E(B,C):
  pass

例子:

>>> x = D()
Hello from A!
Hello from C!
Hello from B!
Hello from D!
>>> y = E()
Hello from A!
Hello from C!
Hello from B!
>>> 

编辑:重写答案。(再次)

于 2009-03-14T07:44:16.437 回答
1

这是部分解决方案:

class NodeMeta(type):
    def __init__(cls, name, bases, d):
        setattr(cls, '__inherits__', bases)

class Node(object):
    __metaclass__ = NodeMeta

    def __init__(self):
        for cls in self.__inherits__:
            cls.cls_init(self)

class Sensor(Node):
    def cls_init(self):
        print "Sensor initialized"

class PositionSensor(Sensor):
    def cls_init(self):
        print "PositionSensor initialized"
        self._bearing = 0

    def bearing(self):
        # calculate bearing:
        return self._bearing

class BearingSensor(Sensor):
    def cls_init(self):
        print "BearingSensor initialized"
        self._position = (0, 0)

    def position(self):
        # calculate position:
        return self._position

# -------- custom sensors --------

class CustomSensor(PositionSensor, BearingSensor):
    def think(self):
        print "Current position:", self.position()
        print "Current bearing:", self.bearing()

class CustomSensor2(PositionSensor, BearingSensor, Sensor):
    pass

>>> s = CustomSensor()
PositionSensor initialized
BearingSensor initialized
>>> s.think()
Current position: (0, 9)
Current bearing: 0

您必须将__init__代码从 Node 子类移动到其他方法(我使用过cls_init)。

编辑:我在看到你的更新之前发布了这个;我会重新阅读您的问题,如有必要,请更新此解决方案。

于 2009-03-14T09:14:50.200 回答