Khỉ vá là gì?


548

Tôi đang cố gắng để hiểu, vá khỉ là gì hay vá khỉ là gì?

Đó có phải là một cái gì đó như phương thức / toán tử quá tải hoặc ủy thác?

Nó có bất cứ điều gì phổ biến với những điều này?


Tôi nghĩ định nghĩa từ google là hữu ích và chung chung nhất:Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.
Charlie Parker

Câu trả lời:


522

Không, nó không giống bất kỳ thứ gì trong số đó. Nó chỉ đơn giản là sự thay thế động của các thuộc tính trong thời gian chạy.

Ví dụ, hãy xem xét một lớp có một phương thức get_data. Phương thức này thực hiện tra cứu bên ngoài (ví dụ trên cơ sở dữ liệu hoặc API web) và nhiều phương thức khác trong lớp gọi nó. Tuy nhiên, trong một thử nghiệm đơn vị, bạn không muốn phụ thuộc vào nguồn dữ liệu ngoài - vì vậy bạn tự động thay thế get_dataphương thức bằng một sơ khai trả về một số dữ liệu cố định.

Vì các lớp Python có thể thay đổi và các phương thức chỉ là thuộc tính của lớp, nên bạn có thể làm điều này bao nhiêu tùy thích - và trên thực tế, bạn thậm chí có thể thay thế các lớp và hàm trong một mô-đun theo cùng một cách.

Nhưng, như một nhà bình luận đã chỉ ra, hãy thận trọng khi bắt chước:

  1. Nếu bất cứ điều gì khác ngoài logic kiểm tra của bạn get_datacũng gọi, nó cũng sẽ gọi thay thế bản vá con khỉ của bạn chứ không phải là bản gốc - có thể là tốt hoặc xấu. Chỉ cần cẩn thận.

  2. Nếu một số biến hoặc thuộc tính tồn tại cũng trỏ đến get_datahàm khi bạn thay thế nó, bí danh này sẽ không thay đổi ý nghĩa của nó và sẽ tiếp tục trỏ đến bản gốc get_data. (Tại sao? Python chỉ sắp xếp lại tên get_datatrong lớp của bạn với một số đối tượng hàm khác; các ràng buộc tên khác hoàn toàn không bị ảnh hưởng.)


1
@LutzPrechelt chỉ để rõ ràng cho tôi, bạn có ý nghĩa pointing to the original get_data functiongì với ? Bạn có nghĩa là khi bạn lưu trữ một chức năng bên trong một biến nếu ai đó thay đổi chức năng đó, biến đó sẽ tiếp tục trỏ đến cái cũ?
Fabriciorissetto

3
@foveniorissetto: Bạn thường không thay đổi các đối tượng hàm trong Python. Khi bạn vá khỉ get_data, bạn đặt lại tên get_datathành hàm giả. Nếu một số tên khác ở đâu đó trong chương trình bị ràng buộc với chức năng trước đây được biết đến như là get_data, thì sẽ không có gì thay đổi đối với tên khác đó.
Lutz Prechelt

1
@LutzPrechelt Bạn có thể giải thích thêm một chút về điều này?
Calvin Ku

Tôi nghĩ rằng vá khỉ có thể hữu ích đặc biệt là để gỡ lỗi, và trong các chức năng trang trí hoặc nhà máy đối tượng. Tuy nhiên, hãy nhớ rõ ràng là tốt hơn ngầm định, vì vậy hãy đảm bảo rằng mã của bạn không nhạy cảm theo ngữ cảnh, đọc "Goto được coi là có hại", v.v ...
aoeu256

Vì vậy, nó chỉ giống như sử dụng chức năng 'eval', nơi bạn có thể chèn mã mới khi chạy?
wintermute

363

MonkeyPatch là một đoạn mã Python mở rộng hoặc sửa đổi mã khác khi chạy (thường là khi khởi động).

Một ví dụ đơn giản trông như thế này:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

Nguồn: trang MonkeyPatch trên wiki Zope.


126

Một bản vá khỉ là gì?

Nói một cách đơn giản, vá khỉ đang thực hiện các thay đổi cho một mô-đun hoặc lớp trong khi chương trình đang chạy.

Ví dụ trong cách sử dụng

Có một ví dụ về vá khỉ trong tài liệu của Pandas:

import pandas as pd
def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Để phá vỡ điều này, đầu tiên chúng tôi nhập mô-đun của chúng tôi:

import pandas as pd

Tiếp theo, chúng ta tạo một định nghĩa phương thức, tồn tại không ràng buộc và miễn phí ngoài phạm vi của bất kỳ định nghĩa lớp nào (vì sự phân biệt khá vô nghĩa giữa một hàm và một phương thức không liên kết, Python 3 không sử dụng phương thức không liên kết):

def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

Tiếp theo, chúng ta chỉ cần đính kèm phương thức đó vào lớp mà chúng ta muốn sử dụng nó trên:

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class

Và sau đó chúng ta có thể sử dụng phương thức trên một thể hiện của lớp và xóa phương thức khi chúng ta hoàn thành:

df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Hãy cẩn thận cho xáo trộn tên

Nếu bạn đang sử dụng tính năng xáo trộn tên (thuộc tính tiền tố với dấu gạch dưới kép, làm thay đổi tên và tôi không khuyến nghị), bạn sẽ phải đặt tên theo cách thủ công nếu bạn thực hiện việc này. Vì tôi không khuyên bạn nên xáo trộn tên, tôi sẽ không trình bày nó ở đây.

Ví dụ kiểm tra

Làm thế nào chúng ta có thể sử dụng kiến ​​thức này, ví dụ, trong thử nghiệm?

Giả sử chúng ta cần mô phỏng cuộc gọi truy xuất dữ liệu đến nguồn dữ liệu bên ngoài dẫn đến lỗi, vì chúng tôi muốn đảm bảo hành vi chính xác trong trường hợp đó. Chúng ta có thể khỉ vá cấu trúc dữ liệu để đảm bảo hành vi này. (Vì vậy, sử dụng tên phương thức tương tự như được đề xuất bởi Daniel Roseman :)

import datasource

def get_data(self):
    '''monkey patch datasource.Structure with this to simulate error'''
    raise datasource.DataRetrievalError

datasource.Structure.get_data = get_data

Và khi chúng tôi kiểm tra hành vi đó dựa trên phương pháp này gây ra lỗi, nếu được thực hiện đúng, chúng tôi sẽ nhận được hành vi đó trong kết quả kiểm tra.

Chỉ cần thực hiện các thao tác trên sẽ thay đổi Structuređối tượng trong vòng đời của quy trình, vì vậy bạn sẽ muốn sử dụng các thiết lập và phân tích trong các trường hợp không mong muốn của mình để tránh làm điều đó, ví dụ:

def setUp(self):
    # retain a pointer to the actual real method:
    self.real_get_data = datasource.Structure.get_data
    # monkey patch it:
    datasource.Structure.get_data = get_data

def tearDown(self):
    # give the real method back to the Structure object:
    datasource.Structure.get_data = self.real_get_data

(Trong khi ở trên là tốt, nó có lẽ là một ý tưởng tốt hơn để sử dụng mockthư viện để vá các mã. mock'S patchtrang trí sẽ ít lỗi hơn so với thực hiện ở trên, mà sẽ đòi hỏi nhiều dòng mã và do đó nhiều cơ hội để giới thiệu lỗi Tôi vẫn chưa xem lại mã mocknhưng tôi tưởng tượng nó sử dụng phương pháp vá khỉ theo cách tương tự.)


Vì vậy, gánh nặng cho khỉ là để lưu trữ một tài liệu tham khảo cho phương pháp thực sự? ví dụ, điều gì xảy ra nếu một người quên bước "giữ lại một con trỏ", nó bị mất?
Tommy

3
@T Mom Nếu tính theo phương thức "ghi đè" về 0 - đó là rác được thu thập và do đó "mất" cho vòng đời của quy trình (hoặc trừ khi mô-đun mà nó khởi tạo được tải lại, nhưng điều đó thường không được khuyến khích).
Aaron Hall

33

Theo Wikipedia :

Trong Python, thuật ngữ khỉ chỉ dùng để sửa đổi động của một lớp hoặc mô-đun khi chạy, được thúc đẩy bởi ý định vá mã bên thứ ba hiện tại như một cách khắc phục lỗi hoặc tính năng không hoạt động như bạn mong muốn.


16

Đầu tiên: vá khỉ là một hack ác (theo ý kiến ​​của tôi).

Nó thường được sử dụng để thay thế một phương thức ở cấp độ mô-đun hoặc lớp bằng cách thực hiện tùy chỉnh.

Usecase phổ biến nhất là thêm một cách giải quyết cho một lỗi trong mô-đun hoặc lớp khi bạn không thể thay thế mã gốc. Trong trường hợp này, bạn thay thế mã "sai" thông qua việc vá khỉ bằng cách triển khai bên trong mô-đun / gói của riêng bạn.


8
Trong trường hợp một số mô-đun vá khỉ điều tương tự: bạn sẽ phải chịu số phận.
Andreas Jung

49
Mặc dù sức mạnh của nó thực sự có thể nguy hiểm nói chung, nhưng nó rất tuyệt để thử nghiệm
dkrikun

1
Usecase phổ biến nhất thực sự là để thử nghiệm, đặc biệt là thử nghiệm đơn vị. Bạn muốn chỉ kiểm tra mã của mình, vì vậy bạn vá bất kỳ cuộc gọi bên ngoài nào để trả về kết quả mong đợi.
bông cải xanh

1
Nó không phải là xấu, tôi sử dụng nó để vá lỗi trong phần mềm của người khác cho đến khi một bản phát hành mới xuất hiện thay vì giả mạo và tạo ra sự phụ thuộc mới.
Nurettin

1
Việc vá khỉ có thể được thực hiện theo "cách chức năng thuần túy" không phải là cách biến đổi, "nhạy cảm theo ngữ cảnh", giống như goto bằng cách chỉ vá trong các trình trang trí trả về một phiên bản vá mới của lớp / phương thức của bạn (thay vì sửa đổi nó). Rất nhiều lập trình viên C # / Java không biết về phát triển theo định hướng REPL, vì vậy họ mã hóa trong các IDE của họ yêu cầu mọi thứ phải được xác định tĩnh. Vì C # / Java không có bản vá khỉ, họ cho rằng cái ác của nó khi họ thấy nó trong JavaScript, Smalltalk, Lisp, Python, v.v ... vì nó đi ngược lại thực tiễn phát triển theo định hướng IDE tĩnh của họ.
aoeu256

13

Khỉ vá chỉ có thể được thực hiện trong các ngôn ngữ động, trong đó python là một ví dụ tốt. Thay đổi một phương thức trong thời gian chạy thay vì cập nhật định nghĩa đối tượng là một ví dụ, tương tự, việc thêm các thuộc tính (cho dù phương thức hoặc biến) trong thời gian chạy được coi là vá khỉ. Chúng thường được thực hiện khi làm việc với các mô-đun mà bạn không có nguồn, sao cho các định nghĩa đối tượng không thể dễ dàng thay đổi.

Điều này được coi là xấu vì nó có nghĩa là định nghĩa của một đối tượng không mô tả hoàn toàn hoặc chính xác cách nó thực sự hoạt động.


Tuy nhiên, vá khỉ có thể hữu ích miễn là thay vì sửa đổi một đối tượng hoặc lớp hiện có, bạn tạo một phiên bản mới của một đối tượng được vá trong các thành viên bên trong một công cụ trang trí hét lên "hey tôi sẽ vá bạn".
aoeu256

Bạn có thể sử dụng các chú thích trên các thành viên được vá để lưu trữ trong thành viên được vá mà trình trang trí đã được sử dụng để vá trong các bản vá. Hãy nói rằng bạn có một trình trang trí không thể xóa được, tạo ra một phiên bản hoàn tác mới của một đối tượng hàm với một phương thức hoàn tác. Bạn có thể đặt trong trình trang trí một trường vá chỉ vào trang trí không thể xóa của bạn.
aoeu256

5

Khỉ vá là mở lại các lớp hoặc phương thức hiện có trong lớp khi chạy và thay đổi hành vi, nên sử dụng một cách thận trọng, hoặc bạn chỉ nên sử dụng nó khi bạn thực sự cần.

Vì Python là ngôn ngữ lập trình động, Các lớp có thể thay đổi để bạn có thể mở lại chúng và sửa đổi hoặc thậm chí thay thế chúng.


1

Khỉ vá là gì? Khỉ vá là một kỹ thuật được sử dụng để tự động cập nhật hành vi của một đoạn mã trong thời gian chạy.

Tại sao nên dùng khỉ vá? Nó cho phép chúng ta sửa đổi hoặc mở rộng hành vi của các thư viện, mô-đun, lớp hoặc phương thức trong thời gian chạy mà không thực sự sửa đổi mã nguồn

Kết luận Khỉ vá là một kỹ thuật tuyệt vời và bây giờ chúng ta đã học cách làm điều đó trong Python. Tuy nhiên, như chúng ta đã thảo luận, nó có nhược điểm riêng và nên được sử dụng cẩn thận.

Để biết thêm thông tin Vui lòng tham khảo [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.