2012-07-02 30 views
5

Ai đó có thể hoàn toàn giải thích dòng cuối cùng của đoạn mã sau:Python Phương pháp Định nghĩa Sử dụng trang trí

def myMethod(self): 
    # do something 

myMethod = transformMethod(myMethod) 

Tại sao bạn sẽ muốn vượt qua định nghĩa cho một phương pháp thông qua phương pháp khác? Và làm thế nào mà thậm chí sẽ làm việc? Cảm ơn trước!

Trả lời

2

Đây là ví dụ về gói hàm, là khi bạn có hàm chấp nhận hàm làm đối số và trả về hàm mới sửa đổi hành vi của hàm ban đầu.

Dưới đây là một ví dụ về cách này có thể được sử dụng, đây là một wrapper đơn giản mà chỉ cần in 'Enter' và 'Thoát' trên mỗi cuộc gọi:

def wrapper(func): 
    def wrapped(): 
     print 'Enter' 
     result = func() 
     print 'Exit' 
     return result 
    return wrapped 

Và đây là một ví dụ về cách bạn có thể sử dụng này:

>>> def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() # behavior before wrapping 
Hello 
>>> say_hello = wrapper(say_hello) 
>>> say_hello() # behavior after wrapping 
Enter 
Hello 
Exit 

Để thuận tiện, Python cung cấp decorator syntax mà chỉ là một phiên bản tốc ký của chức năng gói mà làm điều tương tự vào thời điểm định nghĩa hàm, đây là cách này có thể được sử dụng:

>>> @wrapper 
... def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() 
Enter 
Hello 
Exit 
+0

Cảm ơn bạn rất nhiều vì sự giúp đỡ của bạn! Tôi đánh giá cao nó rõ ràng như thế nào. –

2

Tại sao bạn muốn chuyển định nghĩa cho phương thức thông qua phương pháp khác?

Vì bạn muốn sửa đổi hành vi của nó.

Và làm thế nào thậm chí có thể hoạt động?

Hoàn hảo, vì hàm là lớp đầu tiên trong Python.

def decorator(f): 
    def wrapper(*args, **kwargs): 
    print 'Before!' 
    res = f(*args, **kwargs) 
    print 'After!' 
    return res 
    return wrapper 

def somemethod(): 
    print 'During...' 

somemethod = decorator(somemethod) 

somemethod() 
+0

Tại sao bạn không chỉ sửa đổi mã nguồn thực tế của phương thức? Bạn có thể đưa ra một ví dụ khi chuyển định nghĩa phương thức sẽ rất có lợi? Cuối cùng, làm thế nào một trong những sẽ viết transformMethod để sửa đổi một phương pháp khác? Xin lỗi về tất cả các câu hỏi, tôi chưa bao giờ thấy loại cú pháp này trước đây nên tôi rất bối rối. –

+2

"Tại sao bạn không sửa đổi mã nguồn thực tế của phương thức?" Bởi vì bạn có thể không * có * mã nguồn, hoặc bạn cần áp dụng nó cho nhiều hàm. –

+1

@ user1495015 Hoặc có thể sửa đổi được thực hiện luôn giống nhau, chỉ một phần của những gì thực sự được sửa đổi là khác nhau. Đối với điều này, bạn hạnh phúc có thể sử dụng trang trí. – glglgl

1

Những gì bạn mô tả là trang trí, một dạng phương pháp/sửa đổi chức năng có thể được thực hiện dễ dàng hơn nhiều với cú pháp đặc biệt cho decorators.

gì bạn mô tả là tương đương với

@transformMethod 
def myMethod(self): 
    # do something 

trang trí được sử dụng rất rộng rãi, ví dụ như trong các hình thức @staticmethod, @classmethod, @functools.wraps(), @contextlib.contextmanager vv vv vv

Từ một Python nhất định phiên bản (tôi nghĩ rằng nó là 2,6), các lớp học có thể được trang trí là tốt.

Cả hai loại decoratiors vui vẻ cho phép trả lại các đối tượng mà thậm chí không có chức năng hoặc các lớp học. Ví dụ, bạn có thể trang trí một chức năng máy phát điện theo cách biến nó thành một dict, một bộ hoặc bất cứ điều gì.

apply = lambda tobeapplied: lambda f: tobeapplied(f()) 

@apply(dict) 
def mydict(): 
    yield 'key1', 'value1' 
    yield 'key2', 'value2' 
print mydict 

@apply(set) 
def myset(): 
    yield 1 
    yield 2 
    yield 1 
    yield 4 
    yield 2 
    yield 7 
print myset 

Tôi phải làm gì ở đây?

Tôi tạo một hàm có "điều cần áp dụng" và lần lượt trả về một hàm khác.

Hàm "bên trong" này có chức năng được trang trí, gọi nó và đặt kết quả của nó vào hàm ngoài và trả về kết quả này.

f() trả về đối tượng máy phát điện sau đó được đưa vào dict() hoặc set().

+0

Không, nó * là * một trang trí. '@' Chỉ là cú pháp cú pháp cho cùng một phép toán. –

+0

@ IgnacioVazquez-Abrams Bạn nói đúng. Tôi đã quá gần với định nghĩa của Python. – glglgl

+0

@ IgnacioVazquez-Abrams có, nhưng hầu hết mọi người đều thấy những người trang trí theo cách khác. Không nhất thiết là một biến đổi phương pháp chung. –

0

Bạn cần phải hiểu rằng trong Python, mọi thứ đều là một đối tượng. Một hàm là một đối tượng. Bạn có thể làm những việc tương tự với đối tượng hàm mà bạn có thể thực hiện với các loại đối tượng khác: lưu trữ trong danh sách, lưu trữ trong từ điển, quay lại từ cuộc gọi hàm, v.v.

Lý do thường gặp cho mã như bạn đã hiển thị là "bọc" đối tượng hàm khác. Ví dụ, đây là một trình bao bọc để in giá trị được trả về bởi một hàm.

def print_wrapper(fn): 
    def new_wrapped_fn(*args): 
     x = fn(*args) 
     print("return value of %s is: %s" % (fn.__name__, repr(x))) 
     return x 
    return new_wrapped_fn 

def test(a, b): 
    return a * b 

test = print_wrapper(test) 

test(2, 3) # prints: return value of test is: 6 

Đây là một nhiệm vụ hữu ích và một nhiệm vụ phổ biến như vậy, Python có hỗ trợ đặc biệt cho nó. Google tìm kiếm cho "trang trí Python".

0

Trong câu hỏi ban đầu, bạn đã hỏi "Tại sao bạn muốn chuyển định nghĩa cho phương thức thông qua phương pháp khác?" Sau đó, trong một bình luận, bạn hỏi "Tại sao bạn không sửa đổi mã nguồn thực tế của phương thức?" Tôi thực sự nghĩ đó là một câu hỏi rất hay, và một câu hỏi khó trả lời mà không cần vẫy tay, bởi vì những người trang trí chỉ trở nên thực sự hữu ích khi mã của bạn đạt đến một mức độ phức tạp nhất định. Tuy nhiên, tôi nghĩ rằng điểm trang trí sẽ trở nên rõ ràng hơn nếu bạn xem xét hai chức năng sau:

def add_x_to_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i + x) 
    return result 

def subtract_x_from_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i - x) 
    return result 

Bây giờ, hai chức năng ví dụ có một số sai sót - trong thực tế đời sống, ví dụ, bạn muốn có lẽ chỉ viết lại chúng như là sự hiểu biết danh sách - nhưng chúng ta hãy bỏ qua những sai sót hiển nhiên cho thời điểm này, và giả vờ rằng chúng ta phải viết chúng theo cách này, như là for vòng lặp qua chuỗi. Bây giờ chúng ta đối mặt với vấn đề hai chức năng của chúng tôi làm gần như điều tương tự, chỉ khác nhau tại một thời điểm chính. Điều đó có nghĩa là chúng ta đang lặp lại chính mình ở đây! Và đó là một vấn đề. Bây giờ chúng ta phải duy trì nhiều dòng mã hơn, để lại nhiều chỗ cho các lỗi xuất hiện, và nhiều chỗ cho các lỗi ẩn sau khi chúng xuất hiện.

Một cách tiếp cận cổ điển cho vấn đề này có thể là để tạo ra một chức năng mà mất một chức năng, và áp dụng nó trên một chuỗi, như thế này:

def my_map(func, x, seq): 
    result = [] 
    for i in seq: 
     result.append(func(i, x)) 
    return result 

Bây giờ tất cả chúng ta phải làm là xác định funcs cụ thể để chuyển tới my_map (đây thực sự chỉ là phiên bản chuyên dụng của chức năng tích hợp sẵn trong map).

def sub(a, b): 
    return a - b 

def add(a, b): 
    return a + b 

Và chúng ta có thể sử dụng chúng như thế này:

added = my_map(sub, x, seq) 

Nhưng phương pháp này có vấn đề của nó. Đó là một chút khó đọc hơn các chức năng độc lập ban đầu của chúng tôi, ví dụ; và mỗi lần chúng tôi muốn thêm hoặc trừ x từ danh sách các mục, chúng tôi phải chỉ định hàm và giá trị làm đối số. Nếu chúng tôi làm điều này rất nhiều, chúng tôi muốn có một tên hàm duy nhất luôn đề cập đến cùng một hành động - điều đó sẽ cải thiện khả năng đọc và làm cho việc hiểu những gì đang xảy ra trong mã của chúng tôi trở nên dễ dàng hơn. Chúng tôi thể quấn trên trong khác chức năng ...

def add_x_to_sequence(x, seq): 
    return my_map(add, x, seq) 

Nhưng bây giờ chúng tôi đang lặp lại mình một lần nữa!Và chúng tôi cũng đang tạo ra một sự gia tăng các chức năng, làm lộn xộn không gian tên của chúng ta.

Trang trí cung cấp cách thoát khỏi những vấn đề này. Thay vì chuyển hàm sang hàm khác mỗi lần, chúng tôi có thể chuyển hàm đó một lần. Đầu tiên chúng ta định nghĩa một hàm wrapper:

def vectorize(func): 
    def wrapper(x, seq): 
     result = [] 
     for i in seq: 
      result.append(func(i, x)) 
     return result 
    return wrapper 

Bây giờ tất cả chúng ta phải làm là xác định một chức năng và vượt qua nó để ở trên, gói nó:

def add_x_to_sequence(a, b): 
    return a + b 
add_x_to_sequence = vectorize(add_x_to_sequence) 

Hoặc, sử dụng cú pháp trang trí:

@vectorize 
def add_x_to_sequence(a, b): 
    return a + b 

Bây giờ chúng tôi có thể viết nhiều hàm khác nhau vectorize d và for logic cho tất cả chúng xảy ra chỉ trong một pl át chủ. Bây giờ chúng tôi không phải sửa chữa hoặc tối ưu hóa nhiều chức năng khác nhau một cách riêng biệt; tất cả các lỗi liên quan đến vòng lặp của chúng tôi và tối ưu hóa liên quan đến vòng lặp xảy ra ở cùng một nơi; và chúng tôi vẫn nhận được tất cả các lợi ích dễ đọc của các chức năng được xác định đặc biệt.

Các vấn đề liên quan