重新创建更改一个构造函数参数的类对象

时间:2017-01-10 06:56:46

标签: python python-3.x class object

我希望在我的类中实现一个update方法来重新创建类,但只更改一个构造函数参数。

我的尝试:

class Updateable:
    def update(self, var, var_str, **kwargs):
        kwargs.update(self._vars)
        kwargs[var_str] = var
        self.__init__(**kwargs)

class Rectangle(Updateable):
    def __init__(self, length, perimeter):
        self._vars = locals()
        self.length = length
        self.width = 0.5*(perimeter - 2.*length)      

r = Rectangle(10, 20)
r.update('perimeter', 16)

问题是,我认为整个locals()事情非常狡猾,这意味着任何Updateable的班级都需要分配self._vars

实现此功能的正确方法是什么?装饰师,元类?更简单的东西?

3 个答案:

答案 0 :(得分:2)

如果我误解了你的问题,或者你不想要高级别的建议而你的问题得到解决,请纠正我。

如果更改了(可能相关的)变量,那么__init__当前所做的是重新计算几何形状的属性。第1步是将其从__init__中取出,并转换为由init调用的单独def。这里最重要的是你不要将变量传递给这个函数,而是使用在__init__中设置的类变量或者超类更新方法中的类变量。

第2步是更改您的更新功能。 Python有一种名为properties的getter和setter形式,允许您挂钩任务以更新变量。另一方面,更通用的方式更类似于您自己的更新,并列为

下面的选项2

替代示例

class Updateable:
    # Option 1
    @property
    def perimeter(self):
        return self.__perimeter

    @perimeter.setter
    def perimeter(self, perimeter):
        self.__perimeter = perimeter
        self.recalculate_everything() # or self.calculate_width() or something

    # Option 2
    def update(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        self.recalculate_everything

class Rectable(Updateable):
    def __init__(self, length, perimeter):
        self.__length = length
        self.__perimeter = perimeter
        recalculate_everything()

    def recalculate_everything():
        self.calculate_width()
        ...     

    def calculate_width():
        self.__width = 0.5*(self.__perimeter - 2.*self.__length)     

答案 1 :(得分:2)

Laurens Koppenol建议使用properties(Python对计算属性的通用支持),这是一个好主意,但他的示例代码在很多方面都被打破,并且比以前更加复杂,所以这里更简单,更有效和pythonic示例(没有Updatable类,也不需要任何其他无关的东西):

class Rectangle(object):
    def __init__(self, length, perimeter):
        self.length = length
        self.perimeter = perimeter

    @property
    def width(self):
        return 0.5*(self.perimeter - 2.*self.length)     

如果您要缓存width值(以避免无用的计算),但仍需确保在lengthperimeter更改时更新,您需要将它们设置为所有属性:

class Rectangle(object):
    def __init__(self, length, perimeter):
        self.length = length
        self.perimeter = perimeter

    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, value):
        self._length = value
        self._width = None


    @property
    def perimeter(self):
        return self._perimeter

    @length.setter
    def perimiter(self, value):
        self._perimeter = value
        self._width = None

    @property
    def width(self):
        if self._width is None:
            self._width = 0.5*(self.perimeter - 2.*self.length)     
        return self._width

或(如果你有很多这样的东西)使用一些“cached_property with invalidation”实现这个:Storing calculated values in an object

编辑:wrt /你的问题,对locals的调用确实很难看(并且很容易破坏 - 你可能有局部变量,不应该是_vars的一部分),以及需要在子类中明确设置self._vars。此外,update() API本身也很难看。现在你不需要任何花哨的东西来使整个事情更加pythonic - 这是一个解决方案,其唯一的样板是需要使用命名参数调用Updateable.__init__(不适用于位置参数):

class Updateable(object):
    def __init__(self, **kwargs):
        self._vars = kwargs

    def update(self, **kwargs):
        vars = self._vars.copy()
        vars.update(**kwargs)
        self.__init__(**vars)


class Rectangle(Updateable):
    def __init__(self, length, perimeter):
        super(Rectangle, self).__init__(length=length, perimeter=perimeter)
        self.length = length
        self.width = 0.5*(perimeter - 2.*length)

r = Rectangle(10, 20)
r.update(perimeter=40)

作为旁注,我个人发现非常令人不安的是,您的Rectangle课程采用了perimeter参数,但却存储了width ...也许您应该考虑perimeter财产? (即使是只读以避免重新计算等)

答案 2 :(得分:1)

正如其他人所观察到的那样,width之类的计算应该转移到属性或方法;他们不属于初始化者。

如果你真的想要返回一个新实例,这适用于最简单的情况,其中实例的属性是不可变对象,如字符串或整数:

import copy

class Copyable:

    """Mixin to create copies with a changed attribute."""

    def copy_and_modify(self, var, var_str, **kwargs):
        new = copy.copy(self)
        setattr(new, var, var_str)
        return new

class Rectangle(Copyable):

    def __init__(self, length, perimeter):
        self.perimeter = perimeter
        self.length = length

    @property
    def width(self):
        return 0.5 * (self.perimeter - 2.0 * self.length)

但是,如果您的对象包含嵌套的可变结构(如字典或列表),则需要将copy_and_modify更改为使用copy.deepcopy(但请注意深度复制速度很慢)

class Copyable:

    def copy_and_modify(self, var, var_str, **kwargs):
        new = copy.deepcopy(self)
        setattr(new, var, var_str)
        return new

您可以在子类as described in the docs中定义__copy____deepcopy__方法,以微调对复制过程的控制。