2010-08-12 32 views
35

Tôi không thực sự cần phải làm điều này, nhưng chỉ là tự hỏi, có cách nào để ràng buộc một người trang trí cho tất cả các hàm trong một lớp chung, chứ không phải chỉ rõ nó cho mọi chức năng.Gắn một trang trí cho tất cả các hàm trong một lớp

Tôi cho rằng nó sẽ trở thành một loại khía cạnh, chứ không phải là một trang trí và nó cảm thấy hơi lạ, nhưng đã suy nghĩ cho một cái gì đó như thời gian hoặc auth nó muốn được khá gọn gàng.

Trả lời

20

Cách sạch nhất để thực hiện việc này hoặc để thực hiện các sửa đổi khác đối với định nghĩa lớp, là xác định metaclass.

Ngoài ra, chỉ cần áp dụng trang trí của bạn vào cuối của định nghĩa lớp:

class Something: 
    def foo(self): pass 

for name, fn in inspect.getmembers(Something): 
    if isinstance(fn, types.UnboundMethodType): 
     setattr(Something, name, decorator(fn)) 

Đối với Python 3 thay thế types.UnboundMethodType với types.FunctionType. Trong thực tế tất nhiên bạn sẽ muốn áp dụng trang trí của bạn một cách chọn lọc hơn, và ngay sau khi bạn muốn trang trí tất cả, nhưng một phương pháp bạn sẽ khám phá ra rằng nó dễ dàng và linh hoạt hơn để sử dụng cú pháp trang trí trong theo cách truyền thống.

+0

Tôi hoàn toàn đồng ý, tôi cũng thoải mái hơn nhiều với trang trí rõ ràng của mọi chức năng hơn là một trang trí ngầm. Chỉ tò mò là tất cả. – dochead

-1

Bạn có thể ghi đè phương thức __getattr__. Nó không thực sự gắn một trang trí, nhưng nó cho phép bạn trả lại một phương pháp trang trí. Bạn có thể muốn làm điều gì đó như thế này:

class Eggs(object): 
    def __getattr__(self, attr): 
     return decorate(getattr(self, `_` + attr)) 

Có một số đệ quy xấu giấu trong đó bạn sẽ muốn bảo vệ chống lại, nhưng đó là sự khởi đầu.

+0

Đừng bạn có nghĩa là '__getattribute__'? –

+0

@JaceBrowning: Không, tôi không nghĩ vậy. Nếu Eggs.attr thực sự được gọi là Eggs._attr, nó sẽ hoạt động. Nếu bạn muốn ghi đè quyền truy cập vào Eggs.attr, đó là thuộc tính thực tế, thì có thể. Đó là nơi mà những thứ đệ quy phức tạp xuất hiện. – nmichaels

+0

Tôi hiểu những gì bạn đang làm bây giờ - tất cả các phương pháp undecorated của bạn đều bắt đầu bằng '_'. Bạn có thể tránh điều này bằng cách sử dụng '__getattribute__', nhưng bạn nói đúng, có một số đệ quy phức tạp trong đó để tránh. –

21

Mỗi lần bạn nghĩ đến việc thay đổi định nghĩa lớp, bạn có thể sử dụng trình trang trí lớp hoặc metaclass. ví dụ. sử dụng metaclass

import types 

class DecoMeta(type): 
    def __new__(cls, name, bases, attrs): 

     for attr_name, attr_value in attrs.iteritems(): 
     if isinstance(attr_value, types.FunctionType): 
      attrs[attr_name] = cls.deco(attr_value) 

     return super(DecoMeta, cls).__new__(cls, name, bases, attrs) 

    @classmethod 
    def deco(cls, func): 
     def wrapper(*args, **kwargs): 
     print "before",func.func_name 
     result = func(*args, **kwargs) 
     print "after",func.func_name 
     return result 
     return wrapper 

class MyKlass(object): 
    __metaclass__ = DecoMeta 

    def func1(self): 
     pass 

MyKlass().func1() 

Output:

before func1 
after func1 

Lưu ý: nó sẽ không trang trí staticmethod và classmethod

+1

Thông tin tuyệt vời. Tôi đã sử dụng kỹ thuật này để tạo các phương pháp thử tại [Kiểm tra tệp vàng/được phê duyệt tất cả các phương thức trong một lớp thử nghiệm đối với mọi tệp trong thư mục qua metaprogramming metaclass bằng Python] (https://gist.github.com/patkujawa-wf/1f569d245bbfc73f83a3) . – Pat

+0

Tôi không thực sự hiểu sự khác biệt giữa '__new__' và' __init__' khi nói đến metaclasses. Cả hai dường như làm việc cho vấn đề này, mặc dù họ nhận được các đối số khác nhau. – Pat

+0

@Pat, nó tương tự như '__new__' và' __init__' cho một lớp bình thường, '__new__' được gọi để xây dựng đối tượng (lớp trong trường hợp này),' __init__' được gọi để khởi tạo đối tượng đó (lớp trong trường hợp này) Vì vậy, trong hầu hết trường hợp, nó có thể không quan trọng trừ khi bạn có yêu cầu làm điều gì đó trước hoặc sau khi tạo lớp/obj. –

1

Tất nhiên rằng metaclasses là cách pythonic nhất để đi khi bạn muốn thay đổi đường đi python đó tạo ra các đối tượng. Có thể thực hiện bằng cách ghi đè phương thức __new__ của lớp học của bạn. Nhưng có một số điểm xung quanh vấn đề này (đặc biệt cho python 3.X) mà tôi muốn đề cập đến:

  1. types.FunctionType không bảo vệ các phương pháp đặc biệt được trang trí, vì chúng là các loại chức năng. Như một cách tổng quát hơn, bạn chỉ có thể trang trí các đối tượng mà tên của chúng không được bắt đầu bằng dấu gạch dưới kép (__). Một lợi ích khác của phương pháp này là nó cũng bao gồm những đối tượng mà tồn tại trong không gian tên và bắt đầu với __ nhưng không có chức năng như __qualname__, __module__ vv
  2. Các namespace lập luận trong phần đầu __new__ 's không chứa các thuộc tính lớp trong số __init__. Lý do là __new__ thực thi trước __init__ (khởi tạo).

  3. Không cần sử dụng classmethod làm trang trí, như trong hầu hết các lần bạn nhập trang trí của mình từ mô-đun khác.

  4. Nếu lớp học của bạn chứa mục toàn cầu (bên cạnh __init__) vì không được trang trí cùng với việc kiểm tra xem tên có bắt đầu bằng __ hay không, bạn có thể kiểm tra loại với types.FunctionType để đảm bảo rằng bạn không trang trí một đối tượng không có chức năng.

Đây là một metacalss mẫu mà bạn có thể sử dụng:

class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # if your decorator is a class method of the metaclass use 
     # `my_decorator = cls.my_decorator` in order to invoke the decorator. 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 

Demo:

def my_decorator(func): 
     def wrapper(self, arg): 
      # You can also use *args instead of (self, arg) and pass the *args 
      # to the function in following call. 
      return "the value {} gets modified!!".format(func(self, arg)) 
     return wrapper 


class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # my_decorator = cls.my_decorator (if the decorator is a classmethod) 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 


class MyClass(metaclass=TheMeta): 
    # a = 10 
    def __init__(self, *args, **kwargs): 
     self.item = args[0] 
     self.value = kwargs['value'] 

    def __getattr__(self, attr): 
     return "This class hasn't provide the attribute {}.".format(attr) 

    def myfunction_1(self, arg): 
     return arg ** 2 

    def myfunction_2(self, arg): 
     return arg ** 3 

myinstance = MyClass(1, 2, value=100) 
print(myinstance.myfunction_1(5)) 
print(myinstance.myfunction_2(2)) 
print(myinstance.item) 
print(myinstance.p) 

Output:

the value 25 gets modified!! 
the value 8 gets modified!! 
1 
This class hasn't provide the attribute p. # special method is not decorated. 

Đối với kiểm tra i 3rd tem từ các ghi chú nói trên, bạn có thể bỏ ghi chú dòng a = 10 và làm print(myinstance.a) và xem kết quả sau đó thay đổi sự hiểu biết từ điển trong __new__ như sau sau đó xem kết quả một lần nữa:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\ 
      else my_decorator(v) for k, v in namespace.items()} 
Các vấn đề liên quan