2017-01-10 72 views
3

我想在我的类中实现一个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

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

+0

对象已存在后调用__init__似乎有点奇怪。为什么不只是一个'recalculate_width'方法呢? – BrenBarn

+0

因为我希望它适用于我有的其他类,它们都有不同的参数名称。有时它是'宽度',有时它是'角度',有时它是'波长',可能是任何东西。 –

回答

2

如果我误解了你的问题,或者你不想要高层次的建议和你刚才的问题解决,请纠正我。

__init__当前所做的是重新计算几何形状的属性,如果(可能相关的)变量发生变化。步骤1是从__init__中取出这个数据,并将其存入由init调用的另一个def中。这里最主要的是你不会将变量传递给这个函数,而是使用已经在__init__中设置的类变量或者一个超类更新方法。

第2步是更改您的更新功能。 Python有一种形式,叫做properties,它允许你挂钩任务来更新你的变量。在另一方面更普遍的方式是更类似于自己的更新,并且被列为选项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)  
+0

1 /不要对实现属性使用双下划线 - 这会触发一个会破坏继承的名称修改函数。只使用一个领先的下划线。 2 /如果使用继承,则调用超类初始化程序。 3 /在Python 2.x中,属性不能正确处理旧式类。 4 /您的“Updatable”类应该使用默认实现定义“recalculate_everything()”,或者是抽象基类(参见stlib的ABC模块)。 5 /在'Rectangle .__ init__'(...) –

+0

中有一个NameError并且6.在初始化程序中手动设置实现属性,而不是使用propery setters类型来击败拥有属性的整个点。 –

+0

哦,是的,也是:'Updatable'的意义是通用的 - 它应该对特定的实现属性一无所知。 –

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 

您可以定义__copy____deepcopy__方法在您的子类as described in the docs中精细调整复制过程。

+0

如果它创建一个新的实例,该方法不应被命名为'update()'。 –

+0

@brunodesthuilliers我坚持OP原来的惯例,但你是对的。 OP正在重新初始化现有实例,而不是创建一个新实例。我会在今天晚些时候修复这个名字。也许'create_modified_copy'?另外,我会将mixin重新命名为与其目的更接近的东西。 – snakecharmerb

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与失效”的实施,因为这一个:Storing calculated values in an object

编辑:WRT /你的问题,调用locals的确是丑(和可能很容易中断 - 你可能有本地变量,不应该是_vars的一部分),以及需要在子类中明确设置self._vars。另外update() API本身相当丑陋恕我直言。现在你不需要任何幻想,使整个事情更Python - 这里有一个解决方案的唯一样板是需要调用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) 

作为侧面说明,我personnaly发现很令人不安,你的Rectangle类需要perimeter论点,但存储width而不是...也许你应该考虑perimeter属性? (即使只读,以避免重新计算等)

+0

在你的第二个例子中,你的意思是在初始化方法中赋给'self._length/_perimeter'吗? – snakecharmerb

+0

所以,事情是,我想将更新应用于许多类,所有这些类都有不同的参数,并且我想避免将每个参数都编写为“propery”。这就是为什么我试图做一个可以做到这一点的泛型类。不知道这是否可能。 –

+0

@snakecharmerb不,我的意思是我写的:分配给'self.length'和'self.perimeter'。为什么我会绕过我的属性设置程序并中断“宽度”getter代码? –

相关问题