2017-01-24 2 views
1

Ich versuche, den Konstruktor für Pyspark-Pipeline zu wickeln. init Konstruktor und Affepatch im neu verpackten Konstruktor. Allerdings stoße ich auf einen Fehler, der mit der Art Pipeline zu tun zu haben scheint. init verwendet DekorateureWrapping pyspark Pipeline .__ init__ und Dekoratoren

Hier ist der Code, der tatsächlich funktioniert der Affe Patch:

def monkeyPatchPipeline(): 
     oldInit = Pipeline.__init__ 

     def newInit(self, **keywordArgs): 
     oldInit(self, stages=keywordArgs["stages"]) 

     Pipeline.__init__ = newInit 

Allerdings, wenn ich ein einfaches Programm laufen:

import PythonSparkCombinatorLibrary 
from pyspark.ml import Pipeline 
from pyspark.ml.classification import LogisticRegression 
from pyspark.ml.feature import HashingTF, Tokenizer 

PythonSparkCombinatorLibrary.TransformWrapper.monkeyPatchPipeline() 
tokenizer = Tokenizer(inputCol="text", outputCol="words") 
hashingTF = HashingTF(inputCol=tokenizer.getOutputCol(),outputCol="features") 
lr = LogisticRegression(maxIter=10, regParam=0.001) 

pipeline = Pipeline(stages=[tokenizer, hashingTF, lr]) 

ich diesen Fehler:

Traceback (most recent call last): 
    File "C:\<my path>\PythonApplication1\main.py", line 26, in <module> 
    pipeline = Pipeline(stages=[tokenizer, hashingTF, lr]) 
    File "C:<my path>PythonApplication1 \PythonSparkCombinatorLibrary.py", line 36, in newInit 
oldInit(self, stages=keywordArgs["stages"]) 
    File "C:\<pyspark_path>\pyspark\__init__.py", line 98, in wrapper 
    return func(*args, **kwargs) 
File "C:\<pyspark_path>\pyspark\ml\pipeline.py", line 63, in __init__ 
    kwargs = self.__init__._input_kwargs 
AttributeError: 'function' object has no attribute '_input_kwargs' 

Blick in den Pyspark Interfa Ich sehe diese Pipeline. init sieht wie folgt aus:

@keyword_only 
def __init__(self, stages=None): 
    """ 
    __init__(self, stages=None) 
    """ 
    if stages is None: 
     stages = [] 
    super(Pipeline, self).__init__() 
    kwargs = self.__init__._input_kwargs 
    self.setParams(**kwargs) 

Und in Anbetracht der @keyword_only Dekorateur, ich kontrolliert auch, dass Code:

def keyword_only(func): 
    """ 
    A decorator that forces keyword arguments in the wrapped method 
    and saves actual input keyword arguments in `_input_kwargs`. 
    """ 
    @wraps(func) 
    def wrapper(*args, **kwargs): 
     if len(args) > 1: 
      raise TypeError("Method %s forces keyword arguments." % func.__name__) 
     wrapper._input_kwargs = kwargs 
     return func(*args, **kwargs) 
    return wrapper 

ich total verwirrt bin sowohl darüber, wie dieser Code funktioniert in erster Linie und auch, warum es Probleme mit meinem eigenen Wrapper zu verursachen scheint. Ich sehe, dass Wrapper ein _input_kwargs Feld zu sich selbst hinzufügt, aber wie ist Pipeline .__ init__ über das Lesen dieses Feldes mit self .__ init __._ input_kwargs? Und warum passiert nicht das Gleiche, wenn ich Pipeline .__ init__ erneut einpacke?

Antwort

0

Dekorator 101. Dekorator ist eine Funktion höherer Ordnung, die eine Funktion als erstes Argument (und normalerweise nur) übernimmt und eine Funktion zurückgibt. @ Anmerkung ist nur syntaktischer Zucker für einen einfachen Funktionsaufruf, so folgt

@decorator 
def decorated(x): 
    ... 

kann beispielsweise neu geschrieben werden als:

def decorated_(x): 
    ... 

decorated = decorator(decorated_) 

So Pipeline.__init__ eigentlich ein functools.wrappedwrapper ist die __init__ definiert Captures (func Argument der keyword_only) als Teil seiner Schließung. Wenn es aufgerufen wird, verwendet es kwargs als function attribute von sich. Im Grunde, was hier kann passiert vereinfacht werden:

def f(**kwargs): 
    f._input_kwargs = kwargs # f is in the current scope 

hasattr(f, "_input_kwargs") 
False 
f(foo=1, bar="x") 

hasattr(f, "_input_kwargs") 
True 

Wenn Sie weiter wickeln (dekorieren) __init__ die externe Funktion nicht _input_kwargs angeschlossen haben, damit der Fehler auf. Wenn Sie es machen wollen, arbeiten Sie den gleichen Prozess anwenden müssen, wie sie in der ursprünglichen __init__, um Ihre eigene Version, zum Beispiel mit dem gleichen Dekorateur verwendet:

@keyword_only 
def newInit(self, **keywordArgs): 
    oldInit(self, stages=keywordArgs["stages"]) 

aber ich mochte ich in den Kommentaren erwähnt, Sie sollte eher Unterklassen betrachten.