質問

I'm building a simulator, which will model various types of entities. So I've got a base class, ModelObject, and will use subclasses for all the different entities. Each entity will have a set of properties that I want to keep track of, so I've also got a class called RecordedDetail, that keeps tracks of changes (basically builds a list of (time_step, value) pairs) and each ModelObject has a dict to store these in. So I've got, effectively,

class ModelObject(object):
    def __init__(self):
        self.details = {}
        self.time_step = 0

    def get_detail(self, d_name):
        """ get the current value of the specified RecordedDetail"""
        return self.details[d_name].current_value()

    def set_detail(self, d_name, value):
        """ set the current value of the specified RecordedDetail"""
        self.details[d_name].set_value(value, self.time_step)


class Widget(ModelObject):
    def __init__(self):
        super().__init__(self)
        self.details["level"] = RecordedDetail()
        self.details["angle"] = RecordedDetail()

    @property
    def level(self):
        return self.get_detail("level")

    @level.setter
    def level(self, value):
        self.set_detail("level", value)

    @property
    def angle(self):
        return self.get_detail("angle")

    @angle.setter
    def angle(self):
        self.set_detail("angle", value)

This gets terribly repetitious, and I can't help thinking there must be a way of automating it using a descriptor, but I can't work out how. I end up with

class RecordedProperty(object):
    def __init__(self, p_name):
        self.p_name = p_name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.get_detail(self.p_name)

    def __set__(self, instance, value):
        instance.set_detail(self.p_name, value)


class Widget(ModelObject):
    level = RecordedProperty("level")
    angle = RecordedProperty("angle")

    def __init__(self):
        super().__init__(self)
        self.details["level"] = RecordedDetail()
        self.details["angle"] = RecordedDetail()

which is a bit of an improvement, but still a lot of typing.

So, a few questions.

  1. Can I just add the descriptor stuff (__get__, __set__ etc) into the RecordedDetail class? Would there be any advantage to doing that?

  2. Is there any way of typing the new property name (such as "level") fewer than three times, in two different places?

    or

  3. Am I barking up the wrong tree entirely?

役に立ちましたか?

解決

The last bit of code is on the right track. You can make the process less nasty by using a metaclass to create a named RecordedProperty and a matching RecordedDetail for every item in a list. Here's a simple example:

class WidgetMeta(type):

    def __new__(cls, name, parents, kwargs):
        '''
        Automate the creation of the class
        '''

        for item in kwargs['_ATTRIBS']:
            kwargs[item] = RecordedProperty(item)
        return super(WidgetMeta, cls).__new__(cls, name, parents, kwargs)

class Widget(ModelObject):

    _ATTRIBS = ['level', 'angle']
    __metaclass__ = WidgetMeta

    def __init__(self, *args, **kwargs):
       super().__init__(self)
       self.Details = {}
       for detail in self._ATTRIBS:
           self.Details[detail] = RecordedDetail()

Subclasses would then just need to have different data in _ATTRIBS.

As an alternative (I think it's more complex) you could use the metaclass to customize the init in the same way you customize the new, creating the RecordedDetails out of the _ATTRIBS list.

A third option would be to create a RecordedDetail in every instance on first access. That would work fine as long as you don't have code that expects a RecordedDetail for every property even if the RecordedDetail has not been touched.

Caveat I'm not super familiar with Python3; I've used the above pattern often in 2.7x

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top