Python: обертывание вызовов методов с методами pre и post - PullRequest
8 голосов
/ 03 ноября 2008

Я создаю экземпляр класса A (который я импортирую от кого-то иначе, поэтому я не могу изменить его) в моем классе X.

Есть ли способ, которым я могу перехватывать или переносить вызовы методов в A? Т.е. в приведенном ниже коде я могу позвонить

x.a.p1()

и получите вывод

X.pre
A.p1
X.post

Много ТИА!

class A:
    # in my real application, this is an imported class
    # that I cannot modify
    def p1(self): print 'A.p1'

class X:
    def __init__(self):
        self.a=A()
    def pre(self): print 'X.pre'
    def post(self): print 'X.post'

x=X()
x.a.p1()

Ответы [ 6 ]

6 голосов
/ 03 ноября 2008

Вот решение, которое я и мои коллеги придумали:

from types import MethodType

class PrePostCaller:
    def __init__(self, other):
        self.other = other

    def pre(self): print 'pre'
    def post(self): print 'post'

    def __getattr__(self, name):
        if hasattr(self.other, name):
            func = getattr(self.other, name)
            return lambda *args, **kwargs: self._wrap(func, args, kwargs)
        raise AttributeError(name)

    def _wrap(self, func, args, kwargs):
        self.pre()
        if type(func) == MethodType:
            result = func( *args, **kwargs)
        else:
            result = func(self.other, *args, **kwargs)
        self.post()
        return result

#Examples of use
class Foo:
    def stuff(self):
        print 'stuff'

a = PrePostCaller(Foo())
a.stuff()

a = PrePostCaller([1,2,3])
print a.count()

Дает:

pre
stuff
post
pre
post
0

Таким образом, при создании экземпляра вашего объекта оберните его объектом PrePostCaller. После этого вы продолжаете использовать объект, как если бы он был экземпляром обернутого объекта. С помощью этого решения вы можете выполнять упаковку для каждого экземпляра.

1 голос
/ 03 ноября 2008

Как уже упоминалось, решение обёртки / декоратора, вероятно, будет самым простым. Я не рекомендую модифицировать сам обернутый класс по тем же причинам, на которые вы указали.

Если у вас много внешних классов, вы можете написать генератор кода, который сгенерирует классы-обертки для вас. Поскольку вы делаете это в Python, вы, возможно, даже можете реализовать генератор как часть программы, генерируя обертки при запуске или что-то в этом роде.

1 голос
/ 03 ноября 2008

Я только недавно читал о декораторах в python, я еще не понимаю их, но мне кажется, что они могут быть решением вашей проблемы. см. вступление Брюса Экеля к декораторам по адресу: http://www.artima.com/weblogs/viewpost.jsp?thread=240808

У него есть еще несколько сообщений на эту тему.

Редактировать: Три дня спустя я наткнулся на эту статью, которая показывает, как выполнить аналогичную задачу без декораторов, в чем ее проблемы, а затем представляет декораторы и разрабатывает довольно полное решение: http://wordaligned.org/articles/echo

1 голос
/ 03 ноября 2008

Вы можете просто изменить экземпляр A и заменить функцию p1 на функцию-оболочку:

def wrapped(pre, post, f):
    def wrapper(*args, **kwargs):
        pre()
        retval = f(*args, **kwargs)
        post()
        return retval
    return wrapper

class Y:
    def __init__(self):
        self.a=A()
        self.a.p1 = wrapped(self.pre, self.post, self.a.p1)

    def pre(self): print 'X.pre'
    def post(self): print 'X.post'
1 голос
/ 03 ноября 2008

Решением «без свистков или колокольчиков» было бы написать класс-обертку для класса А, который именно это и делает.

0 голосов
/ 03 ноября 2008

Вот что я получил от Стивена Д'Апрано на comp.lang.python.

# Define two decorator factories.
def precall(pre):
    def decorator(f):
        def newf(*args, **kwargs):
            pre()
            return f(*args, **kwargs)
        return newf
    return decorator

def postcall(post):
    def decorator(f):
        def newf(*args, **kwargs):
            x = f(*args, **kwargs)
            post()
            return x
        return newf
    return decorator

Теперь вы можете обезьяна патч класса А, если хотите. Это, вероятно, не очень Идея сделать это в производственном коде, так как это повлияет на класс А. везде. [это нормально для моего приложения, так как это в основном конвертер протокола, и обрабатывается ровно один экземпляр каждого класса.]

class A:
    # in my real application, this is an imported class
    # that I cannot modify
    def p1(self): print 'A.p1'

class X:
    def __init__(self):
        self.a=A()
        A.p1 = precall(self.pre)(postcall(self.post)(A.p1))
    def pre(self): print 'X.pre'
    def post(self): print 'X.post'


x=X()
x.a.p1()

Дает желаемый результат.

X.pre
A.p1
X.post
...