2015-05-05 16 views
5

Tôi cần thay đổi cuộc gọi đến hàm bên trong một hàm khác trong thời gian chạy.Thay đổi tham chiếu đến hàm trong thời gian chạy trong Python

Xét đoạn mã sau:

def now(): 
    print "Hello World!" 

class Sim: 
    def __init__(self, arg, msg): 
     self.msg = msg 
     self.func = arg 
     self.patch(self.func) 

    def now(self): 
     print self.msg 

    def run(self): 
     self.func() 

    def patch(self, func): 
     # Any references to the global now() in func 
     # are replaced with the self.now() method. 

def myfunc(): 
    now() 

Sau đó ...

>>> a = Sim(myfunc, "Hello Locals #1") 
>>> b = Sim(myfunc, "Hello Locals #2") 
>>> b.run() 
Hello Locals #2 
>>> a.run() 
Hello Locals #1 

Một người dùng đã viết mã, myfunc(), mà làm cho một cuộc gọi đến một hàm được định nghĩa toàn cầu now() và tôi không thể chỉnh sửa nó . Nhưng tôi muốn nó gọi phương thức của ví dụ Sim. Vì vậy, tôi sẽ cần phải "vá" chức năng myfunc() trong thời gian chạy.

Làm cách nào để tôi thực hiện việc này?

Một giải pháp có thể là chỉnh sửa bytecode như đã hoàn thành tại đây: http://www.jonathon-vogel.com/posts/patching_function_bytecode_with_python/ nhưng tôi tự hỏi liệu có cách nào dễ hơn không.

+0

Trong trường hợp sử dụng thực tế của bạn, là 'now' và' myfunc' được định nghĩa trong cùng một mô-đun với nhau không? Bạn có quan tâm nếu các tập quán khác của 'now' bị ảnh hưởng không? Nếu chúng nằm trong các mô-đun khác nhau, làm thế nào để 'myfunc' truy cập chức năng' now'? Cách tiếp cận được yêu cầu có thể khác đôi chút nếu nó thực hiện 'từ nhập khẩu somemodule bây giờ ... now()' vs 'import somemodule ... somemodule.now()'. – Weeble

+0

Có, nó không quan trọng. Tôi đã cập nhật ví dụ về tài khoản này. Người dùng cuối sẽ nhập toàn bộ '' now'' và '' Sim'' từ một thư viện trong mã của họ. Tôi có thể sửa đổi '' now'' toàn cầu, nhưng tôi không thể đủ khả năng tra cứu tốn kém bên trong '' now'' toàn cầu để định vị một phiên bản cục bộ. – jpcgt

Trả lời

2

Điều này phức tạp hơn dường như. Để làm điều đó bạn cần phải:

  • Tạo một lớp con dict với một phương pháp __missing__ để giữ giá trị mới của bạn now. Phương pháp __missing__ sau đó lấy bất kỳ mục nào không có trong từ điển từ thông thường globals() dict.

  • Tạo đối tượng hàm mới từ hàm myfunc() hiện có, giữ đối tượng mã của nó nhưng sử dụng từ điển mới mà bạn đã tạo cho hình cầu.

  • Gán chức năng mới trở lại thành hình cầu bằng cách sử dụng tên hàm của nó.

Đây là cách:

def now(): 
    print "Hello World!" 

class NowGlobals(dict): 
    def __init__(self, now, globals): 
     self["now"] = now 
     self.globals = globals 
    def __missing__(self, key): 
     return self.globals[key] 

class Sim(object): 
    def __init__(self, func): 
     func = self.patch(func) 
     self.func = func 
    def now(self): 
     print "Hello locals!" 
    def patch(self, func): 
     funcname = func.__name__ 
     nowglobals = NowGlobals(self.now, func.func_globals) 
     func = type(func)(func.func_code, nowglobals) 
     globals()[funcname] = func 
     return func 

def myfunc(): 
    now() 

sim = Sim(myfunc) 
myfunc() 

Có thực sự là không cần phải có nó trong một lớp học, nhưng tôi đã giữ nó như vậy vì đó là cách bạn đã viết nó ban đầu.

Nếu myfunc nằm trong một mô-đun khác, bạn sẽ cần phải viết lại Sim.patch() để vá lại vào không gian tên đó, ví dụ: module.myfunc = sim.patch(module.myfunc)

+0

Thao tác này có hoạt động trên 2 trường hợp khác nhau của '' Sim() '' như trong mã được cập nhật không? Có vẻ như nó đang sửa đổi '' myfunc'' gốc và sẽ sửa đổi bản vá trước đó. Có lẽ việc sao chép sâu '' myfunc'' trước và sau đó vá có thể làm được điều này? – jpcgt

+0

Nên làm việc với nhiều phiên bản. Rõ ràng chỉ có một phiên bản 'myfunc' có thể ở dạng hình cầu cùng một lúc, nhưng nếu bạn gọi nó thông qua cá thể' Sim' (hoặc làm cho 'Sim' có thể gọi bằng cách thêm phương thức' __call__', hoặc chỉ bằng cách gọi 'sim. func() '), mỗi cái sử dụng phiên bản' myfunc' của riêng nó. Bạn cũng có thể trì hoãn việc vá cho đến khi bạn gọi trường hợp. – kindall

+0

Điều này hiệu quả. Tôi sẽ chỉ ra rằng '' globals() [funcname] = name'' sẽ thay đổi '' myfunc'' ban đầu, do đó nó sẽ là tùy chọn tùy thuộc vào kết quả cần thiết. Cá nhân tôi chỉ nhận xét nó ra và bây giờ làm những gì tôi muốn. – jpcgt

-1

Các hàm có thuộc tính func_globals. Trong trường hợp này bạn thực hiện patch như vậy:

def now(): 
    print "Hello World!" 


class Sim: 
    def __init__(self, arg): 
     self.func = arg 
     self.patch(self.func) 
     self.func() 

    def now(self): 
     print "Hello Locals!" 

    def patch(self, func): 
     # Any references to the global now() in func 
     # are replaced with the self.now() method. 
     func.func_globals['now'] = self.now 


def myfunc(): 
    now() 


Sim(myfunc) 

nào in:

Hello World! 
Hello Locals! 
+0

Điều này sẽ làm cho * tất cả * các cuộc gọi thành 'now' được thay thế, không chỉ là các cuộc gọi đến' now' từ trong 'myfunc'. – BrenBarn

+0

Rất tiếc. Bỏ lỡ điều đó. Chỉnh sửa câu trả lời của tôi. –

+0

Điều này trộn lẫn chức năng bạn muốn vá và chức năng bạn muốn sử dụng chức năng khác. Ngoài ra, nó vẫn thay thế các cuộc gọi đến 'now' từ các chức năng khác trong mô-đun. – user2357112

0

Không rất thanh lịch, nhưng bạn có thể quấn func của bạn với một chức năng mà sửa đổi môi trường toàn cầu chỉ trong khi func đang chạy.

def now(): 
    print "Hello World!" 

class Sim: 
    def __init__(self, arg, msg): 
     self.msg = msg 
    self.func = arg 
    self.patch(self.func) 

    def now(self): 
    print self.msg 

    def run(self): 
    self.func() 

    def patch(self, func): 
    # Any references to the global now() in func 
    # are replaced with the self.now() method. 

    def newfunc(): 
     global now 
     tmp = now 
     now = self.now 
     func() 
     now = tmp 

    self.func = newfunc 


def myfunc(): 
    now() 
1

Câu trả lời này chỉ dành cho mục đích giải trí. Làm ơn đừng bao giờ làm thế.

Nó có thể được thực hiện bằng cách thay thế myfunc với một chức năng wrapper mà làm cho một bản sao của từ điển biến toàn cầu, thay thế các giá trị vi phạm 'now', làm cho một chức năng mới với các đối tượng quy tắc ứng bản gốc myfunc và từ điển toàn cầu mới, và sau đó gọi hàm mới thay vì myfunc gốc. Như thế này:

import types 
def now(): 
    print("Hello World!") 

class Sim: 
    def __init__(self, arg): 
     self.func = arg 
     self.patch(self.func) 
     self.func() 

    def now(self): 
     print("Hello Locals!") 

    def patch(self, func): 

     def wrapper(*args, **kw): 
      globalcopy = globals().copy() 
      globalcopy['now'] = self.now 
      newfunc = types.FunctionType(func.__code__, globalcopy, func.__name__) 
      return newfunc(*args, **kw) 
     globals()[func.__name__] = wrapper 

def myfunc(): 
    now() 

def otherfunc(): 
    now() 

Sau đó:

>>> myfunc() 
Hello World! 
>>> otherfunc() 
Hello World! 
>>> Sim(myfunc) 
Hello World! 
<__main__.Sim instance at 0x0000000002AD96C8> 
>>> myfunc() 
Hello Locals! 
>>> otherfunc() 
Hello World! 

Có rất nhiều lý do để không làm điều này. Nó sẽ không hoạt động nếu chức năng now mà các truy cập myfunc không có trong cùng một mô-đun như Sim. Nó có thể phá vỡ những thứ khác. Ngoài ra, có một instantiation lớp đơn giản như Sim(myfunc) thay đổi trạng thái toàn cầu theo cách này thực sự là điều ác.

0

Bạn có thể sử dụng gói mock, đã xử lý phần lớn công việc khó khăn trong việc vá cuộc gọi chức năng. Nó là một cài đặt của bên thứ ba trong Python 2 (và các phiên bản đầu của 3?), Nhưng là một phần của thư viện chuẩn Python 3 là unittest.mock. Nó chủ yếu dành cho thử nghiệm, nhưng bạn có thể thử sử dụng nó ở đây.

import mock 

def now(): 
    print "Hello World!" 

class Sim: 
    # Your code from above here 

def myfunc(): 
    now() 

myfunc() # Outputs Hello World! 

s = Sim(myfunc, "Hello Locals #1") 
mock.patch('__main__.now', wraps=s.now).start() 

myfunc() # Now outputs Hello Locals #1 
Các vấn đề liên quan