2017-01-10 71 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代碼? –

相關問題