2009-07-09 16 views
6

Tôi muốn viết một trang trí rằng sẽ hạn chế số lần một chức năng có thể được thực hiện, một cái gì đó dọc theo cú pháp sau:Các kiểu trang trí python này được viết như thế nào?


@max_execs(5) 
def my_method(*a,**k): 
    # do something here 
    pass 

Tôi nghĩ rằng nó có thể viết kiểu này trang trí, nhưng tôi don không biết làm thế nào. Tôi nghĩ một chức năng sẽ không phải là lập luận đầu tiên của người trang trí này, đúng không? Tôi muốn triển khai "trang trí đơn giản", không phải một số lớp học với phương thức gọi.

Lý do cho việc này là tìm hiểu cách viết. Vui lòng giải thích cú pháp và cách trình trang trí đó hoạt động.

Trả lời

12

Đây là những gì tôi đã bỏ qua. Nó không sử dụng một lớp, nhưng nó sử dụng các thuộc tính chức năng:

def max_execs(n=5): 
    def decorator(fn): 
     fn.max = n 
     fn.called = 0 
     def wrapped(*args, **kwargs): 
      fn.called += 1 
      if fn.called <= fn.max: 
       return fn(*args, **kwargs) 
      else: 
       # Replace with your own exception, or something 
       # else that you want to happen when the limit 
       # is reached 
       raise RuntimeError("max executions exceeded") 
     return wrapped 
    return decorator 

max_execs trả về một chức năng gọi là decorator, mà lần lượt trả wrapped.decoration lưu trữ các toán tử tối đa và số lượng các toán tử hiện tại trong hai thuộc tính hàm, sau đó được kiểm tra trong wrapped.

dịch: Khi sử dụng trang trí như thế này:

@max_execs(5) 
def f(): 
    print "hi!" 

Bạn đang cơ bản làm một cái gì đó như thế này:

f = max_execs(5)(f) 
+0

khi áp dụng trang trí này, mã "dịch" bằng python như thế nào? Ví dụ, nếu phương pháp của tôi được gọi là blabla, và tôi áp dụng thuộc tính max_execs, Python sẽ thấy nó như thế nào? blabla = max_execs (5) (blabla)? – Geo

+1

Tôi đã cập nhật câu trả lời để bao gồm bản dịch, nhưng bạn có ý tưởng đúng. – mipadi

0

Tôi biết bạn đã nói bạn không muốn lớp học, nhưng không may đó là cách duy nhất tôi có thể nghĩ cách làm thế nào để làm điều đó khỏi đầu của tôi.

class mymethodwrapper: 
    def __init__(self): 
     self.maxcalls = 0 
    def mymethod(self): 
     self.maxcalls += 1 
     if self.maxcalls > 5: 
      return 
     #rest of your code 
     print "Code fired!" 

cháy nó lên như thế này

a = mymethodwrapper 
for x in range(1000): 
    a.mymethod() 

Kết quả sẽ là:

>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
+0

điều này không thể được sử dụng với bất kỳ phương pháp nào và sử dụng đối tượng có thể gọi. Tôi muốn một người trang trí thông thường. – Geo

4

Decorator chỉ đơn thuần là một callable rằng biến đổi một chức năng vào cái gì khác. Trong trường hợp của bạn, max_execs(5) phải là một cuộc gọi có thể chuyển đổi một chức năng thành một đối tượng có thể gọi khác sẽ tính và chuyển tiếp cuộc gọi.

class helper: 
    def __init__(self, i, fn): 
     self.i = i 
     self.fn = fn 
    def __call__(self, *args, **kwargs): 
     if self.i > 0: 
      self.i = self.i - 1 
      return self.fn(*args, **kwargs) 

class max_execs: 
    def __init__(self, i): 
     self.i = i 
    def __call__(self, fn): 
     return helper(self.i, fn) 

Tôi không hiểu tại sao bạn muốn giới hạn bản thân thành một hàm (chứ không phải lớp). Nhưng nếu bạn thực sự muốn ...

def max_execs(n): 
    return lambda fn, i=n: return helper(i, fn) 
3

Có hai cách để thực hiện. Cách hướng đối tượng là làm cho một lớp:

class max_execs: 
    def __init__(self, max_executions): 
     self.max_executions = max_executions 
     self.executions = 0 

    def __call__(self, func): 
     @wraps(func) 
     def maybe(*args, **kwargs): 
      if self.executions < self.max_executions: 
       self.executions += 1 
       return func(*args, **kwargs) 
      else: 
       print "fail" 
     return maybe 

Xem this question cho một lời giải thích của wraps.

Tôi thích phương pháp tiếp cận OOP ở trên cho loại trang trí này, vì bạn về cơ bản có một biến số riêng tư theo dõi số lần thực hiện. Tuy nhiên, cách tiếp cận khác là sử dụng đóng cửa, chẳng hạn như

def max_execs(max_executions): 
    executions = [0] 
    def actual_decorator(func): 
     @wraps(func) 
     def maybe(*args, **kwargs): 
      if executions[0] < max_executions: 
       executions[0] += 1 
       return func(*args, **kwargs) 
      else: 
       print "fail" 
     return maybe 
    return actual_decorator 

Điều này liên quan đến ba chức năng. Hàm max_execs được đưa ra một tham số cho số lần thực hiện và trả về một trình trang trí sẽ hạn chế bạn đối với nhiều cuộc gọi đó. Hàm đó, actual_decorator, thực hiện tương tự như phương thức __call__ của chúng tôi trong ví dụ OOP. Sự kỳ quặc duy nhất là vì chúng ta không có một lớp với các biến riêng, chúng ta cần phải biến đổi biến số executions nằm trong phạm vi bên ngoài của việc đóng cửa của chúng ta. Python 3.0 hỗ trợ điều này với câu lệnh nonlocal, nhưng trong Python 2.6 hoặc phiên bản cũ hơn, chúng tôi cần thực hiện các lệnh thực thi của chúng tôi trong danh sách để nó có thể bị đột biến.

+1

nếu ví dụ một phương thức có thuộc tính @logged có nghĩa là method = logged (method), phương thức có @max_execs (5) "được dịch" như thế nào? – Geo

+0

Cũng giống như nói max_execs (5) (f) –

2

Nếu không dựa vào một trạng thái trong một lớp học, bạn phải lưu trạng thái (tính) vào chính hàm:

def max_execs(count): 
    def new_meth(meth): 
     meth.count = count 
     def new(*a,**k): 
      meth.count -= 1 
      print meth.count    
      if meth.count>=0: 
       return meth(*a,**k) 
     return new 
    return new_meth 

@max_execs(5) 
def f(): 
    print "invoked" 

[f() for _ in range(10)] 

Nó cung cấp:

5 
invoked 
4 
invoked 
3 
invoked 
2 
invoked 
1 
invoked 
0 
-1 
-2 
-3 
-4 
+0

Rất tiếc, mipadi nhanh hơn với giải pháp tương tự. –

1

Phương pháp này không sửa đổi nội dung chức năng, thay vào đó kết thúc nó thành đối tượng có thể gọi.

Sử dụng lớp làm chậm quá trình thực hiện xuống ~ 20% so với sử dụng chức năng được vá!

def max_execs(n=1): 
    class limit_wrapper: 
     def __init__(self, fn, max): 
      self.calls_left = max 
      self.fn = fn 
     def __call__(self,*a,**kw): 
      if self.calls_left > 0: 
       self.calls_left -= 1 
       return self.fn(*a,**kw) 
      raise Exception("max num of calls is %d" % self.i) 


    def decorator(fn): 
     return limit_wrapper(fn,n) 

    return decorator 

@max_execs(2) 
def fun(): 
    print "called" 
Các vấn đề liên quan