2016-05-31 21 views
3

Ich dachte, ich hätte Multiple Inheritance mit Super() im Griff und versuchte es in einer Facade-Klasse zu benutzen, aber ich stolperte in einen seltsamen Fehler. Ich benutze eine gut gemachte Python Workflow-Software namens Fireworks, aber die Strukturen der Klassen und Workflow-Aufgaben sind ziemlich starr, deshalb habe ich eine Facade-Klasse erstellt, um die Benutzerfreundlichkeit zu erhöhen.Konstruieren der richtigen Python Fassadenklasse mit Super?

Unten ist die Grundstruktur der Workflow-Aufgabe:

class BgwInputTask(FireTaskBase): 

    required_params = ['structure', 'pseudo_dir', 'input_set_params', 'out_file'] 
    optional_params = ['kpoints', 'qshift', 'mat_type'] 

    def __init__(self, params): 
     self.structure = Structure.from_dict(params.get('structure').as_dict()) 
     self.pseudo_dir = params.get('pseudo_dir') 
     self.kpoints = params.get('kpoints', None) 
     self.qshift = params.get('qshift', None) 
     self.isp = params.get('input_set_params') 
     self.run_type = params.get('run_type', None) 
     self.mat_type = params.get('mat_type', 'metal') 
     self.filename = params.get('out_file') 

     ''' 
     misc code for storing pseudo_potentials in: 
     self.pseudo_files 
     self.occupied_bands 
     etc... 
     ''' 

     params = {'structure': self.structure, 'pseudo_dir': self.pseudo_dir, 
      'kpoints': self.kpoints, 'qshift': self.qshift, 
      'input_set_params': self.isp, 'run_type': self.run_type, 
      'mat_type':self.mat_type, 'out_file': self.filename} 
     self.update(params) 

    def write_input_file(self, filename): 
     <code for setting up input file format and writing to filename> 

I strukturierte meine Fassade Klasse mit super unter:

class BgwInput(BgwInputTask): 

    def __init__(self, structure, pseudo_dir, isp={}, 
       kpoints=None, qshift=None, mat_type='semiconductor', 
       out_file=None): 

     self.__dict__['isp'] = isp 
     self.__dict__['run_type'] = out_file.split('.')[0] 
     self.__dict__['params'] = {'structure': structure, 'pseudo_dir': pseudo_dir, 
      'kpoints': kpoints, 'qshift': qshift, 
      'input_set_params': self.isp, 'mat_type': mat_type, 
      'out_file': out_file, 'run_type': self.run_type} 
     print("__init__: isp: {}".format(self.isp)) 
     print("__init__: runtype: {}".format(self.run_type)) 

     super(BgwInput, self).__init__(self.params) 

    def __setattr__(self, key, val): 
     self.proc_key_val(key.strip(), val.strip()) if isinstance(
      val, six.string_types) else self.proc_key_val(key.strip(), val) 

    def proc_key_val(self, key, val): 
     <misc code for error checking of parameters being set> 
     super(BgwInput, self).__dict__['params']['input_set_params'].update({key:val}) 

Das funktioniert gut, bis auf einen kleinen Vorbehalt, dass ich ist verwirrend. Beim Erstellen einer neuen Instanz von BgwInput wird keine leere Instanz erstellt. Input Set Parameter, die in früheren Instanzen gesetzt wurden, werden irgendwie auf die neue Instanz übertragen, aber nicht kpoints oder qshift. Zum Beispiel:

>>> epsilon_task = BgwInput(structure, pseudo_dir='/path/to/pseudos', kpoints=[5,5,5], qshift=[0, 0, 0.001], out_file='epsilon.inp') 
__init__: isp: {} 
__init__: runtype: epsilon 

>>> epsilon_task.epsilon_cutoff = 11.0 
>>> epsilon_task.number_bands = 29 


>>> sigma_task = BgwInput(structure, pseudo_dir='/path/to/pseudos', kpoints=[5,5,5], out_file='sigma.inp') 
__init__: isp: {'epsilon_cutoff': 11.0, 'number_bands': 29} 
__init__: runtype: sigma 

Allerdings, wenn ich self.__dict__['isp'] = isp in meiner Fassade Klasse self.__dict__['isp'] = isp if isp else {} alles ändern scheint wie erwartet zu funktionieren. Parameter, die in vorherigen Instanzen festgelegt wurden, werden nicht auf die neue Instanz übertragen. Warum ist die Facade-Klasse nicht auf isp = {} voreingestellt (vorausgesetzt, dies ist der Standardwert in __ init __), so wie es sein sollte, wenn bei der Erstellung keine Eingabeparameter übergeben wurden? Wo zieht es die vorherigen Parameter aus seit der Standard sollte ein leeres Wörterbuch sein?

Nur um klar zu sein, ich habe eine Lösung, um die Fassade Klasse funktionieren, wie ich es erwartet hatte (durch Änderung isp in der Fassade zu self.__dict__['isp'] = isp if isp else {}), aber ich versuche herauszufinden, warum dies erforderlich ist. Ich glaube, ich vermisse etwas Grundlegendes mit super oder die Reihenfolge der Methodenauflösung in Python. Ich bin neugierig, warum dies geschieht und versuche, meine Wissensbasis zu erweitern.

Unten ist die Methode Auflösung für die Fassadenklasse.

>>> BgwInput.__mro__ 

(pymatgen.io.bgw.inputs.BgwInput, 
pymatgen.io.bgw.inputs.BgwInputTask, 
fireworks.core.firework.FireTaskBase, 
collections.defaultdict, 
dict, 
fireworks.utilities.fw_serializers.FWSerializable, 
object) 
+1

Sie haben ein [veränderbares Standardargument] (http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) in Ihrem '__init__'. – user2357112

+0

Vielen Dank für diese Information. Ich wusste nichts über veränderbare Standardargumente. Dies erklärt definitiv das Verhalten, das ich gesehen habe. Also, es war nicht "super" oder "Methodenauflösung". Mir fehlte das Wissen, es war Teil der Kern-Python-Argumente, die ich vermisste. –

Antwort

1

Veränderbare Standardargumente in Python funktionieren nicht so, wie Sie es erwarten; Ihre Funktionsdefinition (irrelevant Argumente weggelassen):

def __init__(self, ..., isp={}, ...): 
    <CODE> 

entspricht dies:

DEFAULT_ISP = {} 
def __init__(self, ..., isp=DEFAULT_ISP, ...): 
    <CODE> 

(., Außer dass die DEFAULT_ISP Variable nicht verfügbar ist)

Der obige Code zeigt deutlich, dass Ihre beiden Aufgaben verwenden dasselbe isp dictoinary, das anscheinend von den Attributsetzern geändert wird.