簡體   English   中英

包裝調用Python類的方法

[英]Wrap calls to methods of a Python class

我想用相同的包裝器在Python中包裝許多類方法。

從概念上講,它在最簡單的場景中看起來像這樣:

x = 0 # some arbitrary context

class Base(object):
    def a(self):
       print "a x: %s" % x

    def b(self):
       print "b x: %s" % x

 class MixinWithX(Base):
     """Wrap"""
     def a(self):
         global x
         x = 1
         super(MixinWithX, self).a()
         x = 0

     def b(self):
         global x
         x = 1
         super(MixinWithX, self).a()
         x = 0

當然,當有比ab更多的方法時,這變得一團糟。 似乎應該有更簡單的東西。 顯然x可以在裝飾器中修改,但是仍然有一個很長的垃圾列表,而不是上面的代碼:

 from functools import wraps
 def withx(f):
     @wraps(f) # good practice
     def wrapped(*args, **kwargs):
         global x
         x = 1
         f(*args, **kwargs)
         x = 0
     return wrapped

 class MixinWithX(Base):
     """Wrap"""
     @withx
     def a(self):
         super(MixinWithX, self).a()

     @withx
     def b(self):
         super(MixinWithX, self).b()

我想在mixin中使用__getattr__ ,但是當然因為已經定義了諸如ab類的方法,所以從不調用它。

我還考慮過使用__getattribute__但它返回屬性,而不是包裝調用。 我想__getattribute__可以返回一個閉包(下面的例子),但我不確定設計是多么合理。 這是一個例子:

 class MixinWithX(Base):
    # a list of the methods of our parent class (Base) that are wrapped
    wrapped = ['a', 'b']

    # application of the wrapper around the methods specified
    def __getattribute__(self, name):
       original = object.__getattribute__(self, name)
       if name in wrapped:
          def wrapped(self, *args, **kwargs):
              global x
              x = 1 # in this example, a context manager would be handy.
              ret = original(*args, **kwargs)
              x = 0
              return ret
          return wrapped
       return original

在我看來,Python中可能有一些內容可能會減少手動重現要包裝的父類的每個方法的需要。 或者__getattribute__中的閉包可能是正確的方法。 我很感激你的想法。

這是我的嘗試,它允許更簡潔的語法......

x = 0 # some arbitrary context

# Define a simple function to return a wrapped class
def wrap_class(base, towrap):
    class ClassWrapper(base):
        def __getattribute__(self, name):
            original = base.__getattribute__(self, name)
            if name in towrap:
                def func_wrapper(*args, **kwargs):
                    global x
                    x = 1
                    try:
                        return original(*args, **kwargs)
                    finally:
                        x = 0
                return func_wrapper
            return original
    return ClassWrapper


# Our existing base class
class Base(object):
    def a(self):
       print "a x: %s" % x

    def b(self):
       print "b x: %s" % x


# Create a wrapped class in one line, without needing to define a new class
# for each class you want to wrap.
Wrapped = wrap_class(Base, ('a',))

# Now use it
m = Wrapped()
m.a()
m.b()

# ...or do it in one line...
m = wrap_class(Base, ('a',))()

...輸出......

a x: 1
b x: 0

您可以使用裝飾器執行此操作並檢查

from functools import wraps
import inspect

def withx(f):
    @wraps(f)
    def wrapped(*args, **kwargs):
        print "decorator"
        x = 1
        f(*args, **kwargs)
        x = 0
    return wrapped

class MyDecoratingBaseClass(object):
    def __init__(self, *args, **kwargs):
        for member in inspect.getmembers(self, predicate=inspect.ismethod):
            if member[0] in self.wrapped_methods:
                setattr(self, member[0], withx(member[1]))

class MyDecoratedSubClass(MyDecoratingBaseClass):
    wrapped_methods = ['a', 'b']
    def a(self):
        print 'a'

    def b(self):
        print 'b'

    def c(self):
        print 'c'   

if __name__ == '__main__':
    my_instance = MyDecoratedSubClass()
    my_instance.a()
    my_instance.b()
    my_instance.c()

輸出:

decorator
a
decorator
b
c

我能想到的兩個一般方向對你的情況有用。

一個是使用類裝飾器 編寫一個接受類的函數,並返回一個具有相同方法集的類,進行修飾(通過調用type(...)創建一個新類,或者通過更改輸入類)。

編輯:(我想到的實際包裝/檢查代碼與@girasquid在他的答案中類似,但連接是使用裝飾而不是mixin / inheritance完成的,我認為它更靈活,更健壯。)

這讓我想到了第二種選擇,即使用元類 ,它可能更干凈(如果你不習慣使用元類 ,那就更難了)。 如果您無權訪問原始類的定義,或者不想更改原始定義,則可以對原始類進行子類化,在派生類上設置元類。

有一個解決方案,它被稱為裝飾器。 Google“python decorators”提供了大量信息。

基本概念是裝飾器是一個函數,它將函數作為參數,並返回一個函數:

def decorate_with_x(f)
    def inner(self):
         self.x = 1 #you must always use self to refer to member variables, even if you're not decorating
         f(self)
         self.x = 0
    return inner

class Foo(object):

     @decorate_with_x # @-syntax passes the function defined on next line
                      # to the function named s.t. it is equivalent to 
                      # foo_func = decorate_with_x(foo_func)
     def foo_func(self):
         pass

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM