首页 > 解决方案 > 如何在python中向描述符或属性添加方法

问题描述

我正在尝试编写一个可以轻松扩展的模拟类。为此,我想使用类似于属性的东西,但这也提供了update一种可以针对不同用例以不同方式实现的方法:

class Quantity(object):
    
    def __init__(self, initval=None):
        self.value = initval

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value
    
    def update(self, parent):
        """here the quantity should be updated using also values from
        MySimulation, e.g. adding `MySimulation.increment`, but I don't
        know how to link to the parent simulation."""

        
class MySimulation(object):
    "this default simulation has only density"
    density = Quantity()
    increment = 1
    
    def __init__(self, value):
        self.density = value
    
    def update(self):
        """this one does not work because self.density returns value
        which is a numpy array in the example and thus we cannot access
        the update method"""
        self.density.update(self)

默认模拟可以这样使用:

sim = MySimulation(np.arange(5))

# we can get the values like this
print(sim.density)
> [0, 1, 2, 3, 4]

# we can call update and all quantities should update
sim.update()  # <- this one is not possible

我想以这样的方式编写它,以便可以以任何用户定义的方式扩展模拟,例如添加另一个以不同方式更新的数量:

class Temperature(Quantity):
    def update(self, parent):
        "here we define how to update a temperature"


class MySimulation2(MySimulation):
    "an improved simulation that also evolves temperature"
    temperature = Temperature()
    
    def __init__(self, density_value, temperature_value):
        super().__init__(density_value)
        self.temperature = temperature_value
    
    def update(self):
        self.density.update(self)
        self.temperature.update(self)

这有可能以某种方式还是有其他方法可以实现类似的行为?我已经看到了这个问题,这可能会有所帮助,但答案似乎很不优雅——我的案例有没有好的面向对象的方法?

标签: pythonooppython-descriptors

解决方案


这有可能以某种方式还是有其他方法可以实现类似的行为?

有一种方法可以实现类似的行为。

instance第 1 步:在/上设置标志MySimulation

第 2 步:检查标志selfQuantity.__get__如果设置了标志,则返回。

幼稚的实现

4行变化。

class Quantity(object):

    def __init__(self, initval=None):
        self.value = initval

    def __get__(self, instance, owner):
        if hasattr(instance, '_update_context'):  # 1
            return self                           # 2
        return self.value

    def __set__(self, instance, value):
        self.value = value

    def update(self, parent):
        self.value += parent.increment  # Example update using value from parent


class MySimulation(object):
    "this default simulation has only density"
    density = Quantity()
    increment = 1

    def __init__(self, value):
        self.density = value

    def update(self):
        setattr(self, '_update_context', None)  # 3
        self.density.update(self)
        delattr(self, '_update_context')        # 4

请注意,这MySimulation对它的子类来说是非常具有侵入性的。
缓解这种情况的一种方法是为子类定义一个_update方法来覆盖:

def update(self):
    setattr(self, '_update_context', None)  # 3
    self._update()
    delattr(self, '_update_context')        # 4

def _update(self):
    self.density.update(self)

更强大的实施

使用元类,我们可以对原始代码进行 3 行更改。

class UpdateHostMeta(type):
    UPDATE_CONTEXT_KEY = '_update_context'

    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        __class__.patch_update(cls)

    @staticmethod
    def patch_update(update_host_class):
        _update = update_host_class.update

        def update(self, *args, **kwargs):
            try:
                setattr(self, __class__.UPDATE_CONTEXT_KEY, None)
                _update(self, *args, **kwargs)
            finally:
                delattr(self, __class__.UPDATE_CONTEXT_KEY)

        update_host_class.update = update

    @staticmethod
    def is_in_update_context(update_host):
        return hasattr(update_host, __class__.UPDATE_CONTEXT_KEY)
class Quantity(object):

    def __init__(self, initval=None):
        self.value = initval

    def __get__(self, instance, owner):
        if UpdateHostMeta.is_in_update_context(instance):  # 1
            return self                                    # 2
        return self.value

    def __set__(self, instance, value):
        self.value = value

    def update(self, parent):
        self.value += parent.increment  # Example update using value from parent


class MySimulation(object, metaclass=UpdateHostMeta):  # 3
    "this default simulation has only density"
    density = Quantity()
    increment = 1

    def __init__(self, value):
        self.density = value

    def update(self):
        self.density.update(self)

推荐阅读