3

デコレーターに関する多くの記事を読んだにもかかわらず、デコレーターをよく理解するのに苦労しています ([this][1] SO で非常に人気のある記事を含む)。私は自分がバカに違いないのではないかと疑っていますが、バカであることに伴うすべての頑固さを考えて、これを理解しようと決心しました。

それ、そして私は良いユースケースを持っていると思います...

以下は、PDF ファイルからテキストを抽出する私のプロジェクトのコードです。処理には次の 3 つの手順が含まれます。

  1. PDF ファイルの処理に必要な PDFMiner オブジェクトをセットアップします (ボイラープレートの初期化)。
  2. PDF ファイルに処理関数を適用します。
  3. 何が起こっても、ファイルを閉じてください。

私は最近、コンテキスト マネージャーとwithステートメントについて学びましたが、これは彼らにとって良いユース ケースのように思えました。PDFMinerWrapperそのため、クラスを定義することから始めました。

class PDFMinerWrapper(object):
    '''
    Usage:
    with PDFWrapper('/path/to/file.pdf') as doc:
        doc.dosomething()
    '''
    def __init__(self, pdf_doc, pdf_pwd=''):
        self.pdf_doc = pdf_doc
        self.pdf_pwd = pdf_pwd

    def __enter__(self):
        self.pdf = open(self.pdf_doc, 'rb')
        parser = PDFParser(self.pdf)  # create a parser object associated with the file object
        doc = PDFDocument()  # create a PDFDocument object that stores the document structure
        parser.set_document(doc)  # connect the parser and document objects
        doc.set_parser(parser)
        doc.initialize(self.pdf_pwd)  # pass '' if no password required
        return doc

    def __exit__(self, type, value, traceback):
        self.pdf.close()
        # if we have an error, catch it, log it, and return the info
        if isinstance(value, Exception):
            self.logError()
            print traceback
            return value

これで、PDF ファイルを簡単に操作できるようになり、エラーが適切に処理されることを確認できるようになりました。理論的には、私がする必要があるのは次のようなことだけです:

with PDFMinerWrapper('/path/to/pdf') as doc:
    foo(doc)

によって返されたオブジェクトに関数を適用する前に、PDF ドキュメントが抽出可能であることを確認する必要があることを除けば、これは素晴らしいことPDFMinerWrapperです。私の現在の解決策には、中間ステップが含まれます。

私はPamplemousse、PDFを操作するためのインターフェースとして機能するクラスを呼び出しています。次に、PDFMinerWrapperオブジェクトがリンクされているファイルに対して操作を実行する必要があるたびに使用します。

以下は、その使用法を示す (要約された) コードです。

class Pamplemousse(object):
    def __init__(self, inputfile, passwd='', enc='utf-8'):
        self.pdf_doc = inputfile
        self.passwd = passwd
        self.enc = enc

    def with_pdf(self, fn, *args):
        result = None
        with PDFMinerWrapper(self.pdf_doc, self.passwd) as doc:
            if doc.is_extractable:  # This is the test I need to perform
                # apply function and return result
                result = fn(doc, *args)

        return result

    def _parse_toc(self, doc):
        toc = []
        try:
            toc = [(level, title) for level, title, dest, a, se in doc.get_outlines()]
        except PDFNoOutlines:
            pass
        return toc

    def get_toc(self):
        return self.with_pdf(self._parse_toc)

PDF ファイルに対して操作を実行したいときはいつでも、関連する関数をwith_pdfその引数とともにメソッドに渡します。次にwith_pdf、メソッドはwithステートメントを使用してコンテキストマネージャーを活用しPDFMinerWrapper(したがって、例外の適切な処理を保証します)、渡された関数を実際に適用する前にチェックを実行します。

私の質問は次のとおりです。

を明示的に呼び出す必要がないように、このコードを単純化したいと思いますPamplemousse.with_pdf。私の理解では、デコレータがここで役立つ可能性があるため、次のようになります。

  1. withステートメントを呼び出して抽出可能性チェックを実行するデコレータをどのように実装すればよいでしょうか?
  2. デコレータをクラス メソッドにすることは可能ですか?それとも、デコレータを自由形式の関数またはクラスにする必要がありますか?
4

4 に答える 4

1

私があなたの目標を解釈した方法は、Pamplemousseクラスで複数のメソッドを定義できるようにすることであり、その呼び出しでそれらを常にラップする必要はありません。これは、それが何であるかを非常に単純化したバージョンです。

def if_extractable(fn):
    # this expects to be wrapping a Pamplemousse object
    def wrapped(self, *args):
        print "wrapper(): Calling %s with" % fn, args
        result = None
        with PDFMinerWrapper(self.pdf_doc) as doc:
            if doc.is_extractable:
                result = fn(self, doc, *args)
        return result
    return wrapped


class Pamplemousse(object):

    def __init__(self, inputfile):
        self.pdf_doc = inputfile

    # get_toc will only get called if the wrapper check
    # passes the extractable test
    @if_extractable
    def get_toc(self, doc, *args):
        print "get_toc():", self, doc, args

デコレーターif_extractableは単なる関数として定義されていますが、クラスのインスタンス メソッドで使用されることを想定しています。

get_tocプライベート メソッドに委譲するために使用されたdecorateddocは、チェックに合格した場合、オブジェクトと引数を受け取ることを期待します。それ以外の場合は呼び出されず、ラッパーは None を返します。

これにより、操作関数を定義し続けて、doc

期待されるクラスをラップすることを確認するために、型チェックを追加することもできます。

def if_extractable(fn):
    def wrapped(self, *args):
    if not hasattr(self, 'pdf_doc'):
        raise TypeError('if_extractable() is wrapping '\
                        'a non-Pamplemousse object')
    ...
于 2012-07-26T00:30:54.233 に答える
0

デコレータは、関数を受け取り、別の関数を返す単なる関数です。あなたは好きなことをすることができます:

def my_func():
    return 'banana'

def my_decorator(f): # see it takes a function as an argument
    def wrapped():
        res = None
        with PDFMineWrapper(pdf_doc, passwd) as doc:
            res = f()
        return res
     return wrapper # see, I return a function that also calls f

デコレータを適用すると:

@my_decorator
def my_func():
    return 'banana'

関数はwrappedを置き換えるmy_funcため、余分なコードが呼び出されます。

于 2012-07-25T23:31:29.727 に答える
0

次の行に沿って試してみてください。

def with_pdf(self, fn, *args):
    def wrappedfunc(*args):
        result = None
        with PDFMinerWrapper(self.pdf_doc, self.passwd) as doc:
            if doc.is_extractable:  # This is the test I need to perform
                # apply function and return result
                result = fn(doc, *args)
        return result
    return wrappedfunc

関数をラップする必要がある場合は、次のようにします。

@pamplemousseinstance.with_pdf
def foo(doc, *args):
    print 'I am doing stuff with', doc
    print 'I also got some good args. Take a look!', args
于 2012-07-25T23:35:19.663 に答える
0

ここにいくつかのデモコードがあります:

#! /usr/bin/python

class Doc(object):
    """Dummy PDFParser Object"""

    is_extractable = True
    text = ''

class PDFMinerWrapper(object):
    '''
    Usage:
    with PDFWrapper('/path/to/file.pdf') as doc:
        doc.dosomething()
    '''
    def __init__(self, pdf_doc, pdf_pwd=''):
        self.pdf_doc = pdf_doc
        self.pdf_pwd = pdf_pwd

    def __enter__(self):
        return self.pdf_doc

    def __exit__(self, type, value, traceback):
        pass

def safe_with_pdf(fn):
    """
    This is the decorator, it gets passed the fn we want
    to decorate.

    However as it is also a class method it also get passed
    the class. This appears as the first argument and the
    function as the second argument.
    """
    print "---- Decorator ----"
    print "safe_with_pdf: First arg (fn):", fn
    def wrapper(self, *args, **kargs):
        """
        This will get passed the functions arguments and kargs,
        which means that we can intercept them here.
        """
        print "--- We are now in the wrapper ---"
        print "wrapper: First arg (self):", self
        print "wrapper: Other args (*args):", args
        print "wrapper: Other kargs (**kargs):", kargs

        # This function is accessible because this function is
        # a closure, thus still has access to the decorators
        # ivars.
        print "wrapper: The function we run (fn):", fn

        # This wrapper is now pretending to be the original function

        # Perform all the checks and stuff
        with PDFMinerWrapper(self.pdf, self.passwd) as doc:
            if doc.is_extractable:
                # Now call the orininal function with its
                # argument and pass it the doc
                result = fn(doc, *args, **kargs)
            else:
                result = None
        print "--- End of the Wrapper ---"
        return result

    # Decorators are expected to return a function, this
    # function is then run instead of the decorated function.
    # So instead of returning the original function we return the
    # wrapper. The wrapper will be run with the original functions
    # argument.

    # Now by using closures we can still access the original
    # functions by looking up fn (the argument that was passed
    # to this function) inside of the wrapper.
    print "--- Decorator ---"
    return wrapper


class SomeKlass(object):

    @safe_with_pdf
    def pdf_thing(doc, some_argument):
        print ''
        print "-- The Function --"

        # This function is now passed the doc from the wrapper.

        print 'The contents of the pdf:', doc.text
        print 'some_argument', some_argument
        print "-- End of the Function --"
        print ''

doc = Doc()
doc.text = 'PDF contents'
klass = SomeKlass()  
klass.pdf = doc
klass.passwd = ''
klass.pdf_thing('arg')

そのコードを実行して、その動作を確認することをお勧めします。注意すべき興味深い点のいくつか:

最初に、1 つの引数しか渡していないことに気付くでしょうがpdf_thing()、メソッドを見ると、2 つの引数を取ります。

@safe_with_pdf
def pdf_thing(doc, some_argument):
    print ''
    print "-- The Function --"

これは、すべての関数のラッパーを見ると、次のようになるためです。

with PDFMinerWrapper(self.pdf, self.passwd) as doc:
    if doc.is_extractable:
        # Now call the orininal function with its
        # argument and pass it the doc
        result = fn(doc, *args, **kargs)

doc 引数を生成し、元の引数 ( ) と共に渡します*args, **kargs。これは、このデコレーターでラップされたすべてのメソッドまたは関数がdoc、その宣言 ( ) にリストされた引数に加えて、追加の引数を受け取ることを意味しdef pdf_thing(doc, some_argument):ます。

注意すべきもう 1 つのことは、ラッパーであるということです。

def wrapper(self, *args, **kargs):
    """
    This will get passed the functions arguments and kargs,
    which means that we can intercept them here.
    """

また、引数をキャプチャし、self呼び出されているメソッドに渡しません。次の関数呼び出しを変更して、この動作を変更できます。

result = fn(doc, *args, **kargs)
    else:
        result = None

に:

result = fn(self, doc, *args, **kargs)
    else:
        result = None

次に、メソッド自体を次のように変更します。

def pdf_thing(self, doc, some_argument):

ご不明な点がございましたら、お気軽にお問い合わせください。

編集:

あなたの質問の2番目の部分に答えるために。

はい、クラスメソッドにすることができます。上記safe_with_pdfの中に配置して、それを呼び出すだけです。たとえば、クラスの最初のメソッドです。SomeKlass

また、クラスにデコレータを使用した、上記のコードの縮小版もここにあります。

class SomeKlass(object):
    def safe_with_pdf(fn):
        """The decorator which will wrap the method"""
        def wrapper(self, *args, **kargs):
            """The wrapper which will call the method is a doc"""
            with PDFMinerWrapper(self.pdf, self.passwd) as doc:
                if doc.is_extractable:
                    result = fn(doc, *args, **kargs)
                else:
                    result = None
            return result
        return wrapper

    @safe_with_pdf
    def pdf_thing(doc, some_argument):
        """The method to decorate"""
        print 'The contents of the pdf:', doc.text
        print 'some_argument', some_argument
        return '%s - Result' % doc.text

print klass.pdf_thing('arg')
于 2012-07-26T00:06:19.720 に答える