2008-11-03 8 views
6

Tôi instantiating một lớp A (mà tôi đang nhập khẩu từ ai đó khác, vì vậy tôi không thể sửa đổi nó) vào lớp học của tôi X.Python: gói phương pháp invocations với trước và sau phương pháp

Có cách nào Tôi có thể chặn hoặc gọi các cuộc gọi đến các phương thức trong A? Tức là, trong đoạn code dưới đây tôi có thể gọi

x.a.p1() 

và nhận được đầu ra

X.pre 
A.p1 
X.post 

Nhiều TIA!

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() 

Trả lời

5

Dưới đây là giải pháp tôi và các đồng nghiệp của tôi đến với:

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() 

Cung cấp:

pre 
stuff 
post 
pre 
post 
0 

Vì vậy, khi tạo một thể hiện của đối tượng của bạn, quấn nó với đối tượng PrePostCaller . Sau đó bạn tiếp tục sử dụng đối tượng như thể nó là một thể hiện của đối tượng được bao bọc. Với giải pháp này, bạn có thể thực hiện gói trên cơ sở từng trường hợp.

1

Giải pháp không có còi hoặc chuông là viết lớp bao bọc cho lớp A thực hiện điều đó.

+0

đúng, nhưng tôi muốn tránh vì chương trình thực sự của tôi bao gồm nhiều phiên bản lớp hơn tôi muốn gói. –

+0

Vâng, hãy gọi trình bao bọc giống nhau, nhưng đặt nó vào không gian tên của riêng bạn. Nó sẽ hoạt động hiệu quả như là một thay thế thả vào, và bạn không phải thay đổi mã của bạn. – Tomalak

1

Bạn chỉ có thể thay đổi Một thể hiện và thay thế các chức năng p1 với một chức năng wrapper:

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

Tôi vừa mới vừa đọc về trang trí trong python, tôi không hiểu họ được nêu ra nhưng nó có vẻ tôi rằng họ có thể là một giải pháp cho vấn đề của bạn. xem Bruce Eckel giới thiệu về người trang trí tại: http://www.artima.com/weblogs/viewpost.jsp?thread=240808

Anh ấy có thêm một vài bài đăng về chủ đề đó ở đó.

Edit: Ba ngày sau, tôi vấp ngã khi bài viết này, trong đó cho thấy làm thế nào để làm một công việc tương tự mà không trang trí, những gì là vấn đề với nó và sau đó giới thiệu trang trí và phát triển một giải pháp khá đầy đủ: http://wordaligned.org/articles/echo

0

Đây là những gì Tôi đã nhận được từ Steven D'Aprano trên 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 

Bây giờ bạn có thể vá khỉ lớp A nếu bạn muốn. Nó có lẽ không phải là một ý tưởng tuyệt vời để làm điều này trong mã sản xuất, vì nó sẽ ảnh hưởng đến lớp A ở khắp mọi nơi. [này là ok cho ứng dụng của tôi, vì nó cơ bản là một công cụ chuyển đổi giao thức và có chính xác một thể hiện của mỗi lớp được xử lý.]

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() 

Cung cấp kết quả mong muốn.

X.pre 
A.p1 
X.post 
+0

Thay vì vá Lớp chỉ vá Bản sao: self.a.p1 = precall (self.pre) (postcall (self.post) (self.a.p1)) –

1

Như những người khác đã đề cập, giải pháp trình bao bọc/trang trí có lẽ là giải pháp dễ nhất. Tôi không khuyên bạn nên sửa đổi các lớp bọc chính nó, vì những lý do tương tự mà bạn chỉ ra.

Nếu bạn có nhiều lớp bên ngoài, bạn có thể viết trình tạo mã để tạo các lớp trình bao bọc cho bạn.Vì bạn đang làm điều này trong Python, bạn thậm chí có thể thực hiện bộ tạo như một phần của chương trình, tạo ra các trình bao bọc khi khởi động, hoặc một cái gì đó.