我希望在我的类中实现一个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 :(得分:2)
如果我误解了你的问题,或者你不想要高级别的建议而你的问题得到解决,请纠正我。
如果更改了(可能相关的)变量,那么__init__
当前所做的是重新计算几何形状的属性。第1步是将其从__init__
中取出,并转换为由init调用的单独def
。这里最重要的是你不要将变量传递给这个函数,而是使用在__init__
中设置的类变量或者超类更新方法中的类变量。
第2步是更改您的更新功能。 Python有一种名为properties的getter和setter形式,允许您挂钩任务以更新变量。另一方面,更通用的方式更类似于您自己的更新,并列为
下面的选项2class 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
值(以避免无用的计算),但仍需确保在length
或perimeter
更改时更新,您需要将它们设置为所有属性:
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__
方法,以微调对复制过程的控制。