In Python ist es relativ einfach, eine Methode oder Funktion innerhalb eines Moduls zu überschreiben. Qiita hat auch einige Artikel.
Diese Techniken optimieren den Attributzugriff wie folgt: Ich spiele nicht mit einer Funktion als Objekt.
class SomeClass:
def original_method(self):
print('call original_method')
def new_method(self):
print('call new_method')
some_instance1 = SomeClass()
some_instance2 = SomeClass()
# some_Schreiben Sie die Methode von instance1 neu
some_instance1.original_method = type(some_instance1.original_method)(new_method, some_instance1)
some_instance1.original_method() # new_method()Wird genannt
some_instance2.original_method() # original_method()Wird genannt
#Schreiben Sie die Methoden für alle Instanzen neu
SomeClass.original_method = new_method
some_instance1.original_method() # new_method()Wird genannt
some_instance2.original_method() # new_method()Wird genannt
import some_module
def new_func():
print('call new_func')
##Schreiben Sie die Funktion im Modul neu
some_module.original_func = new_func
some_module.original_func() # new_func()Wird genannt
Meistens sind diese Techniken ziemlich gut. Es gibt jedoch einige Fälle, in denen Sie nicht überschreiben können, selbst wenn Sie mit dem Attributzugriff spielen.
#Wenn es vor dem Überschreiben des Attributs abgerufen wurde,
original_method = some_instance1.original_method
#Auch wenn Sie das Attribut überschreiben
type(some_instance1).original_method = new_method
#Es gibt keine Auswirkung auf die zuerst herausgenommene
original_method() #Original original_method()Wird genannt
import some_module
from some_module import original_func
#Gleiches gilt für Funktionen in Modulen
some_module.original_func = new_func
original_func() # original_func()Wird genannt
Ich möchte das gesamte Programm überschreiben, auch wenn die Attribute zuerst abgerufen wurden.
import some_module
from some_module import original_func #Auch wenn es zuerst herausgenommen wurde
def new_func():
print('call new_func')
overwrite_func(some_module.original_func, new_func) #Später überschreiben
original_func() #Hier neu_func()Ich möchte, dass du angerufen wirst
def overwrite_func(orig, new):
from uuid import uuid4
kw = 'kw' + str(uuid4()).replace('-', '')
exec("def outer():\n " + '='.join(list(orig.__code__.co_freevars) + ['None'])
+ "\n def inner(*args, " + kw + "=new, **kwargs):\n "
+ ','.join(orig.__code__.co_freevars)
+ "\n return " + kw + "(*args, **kwargs)\n return inner",
locals(), globals())
inner = outer()
orig.__code__ = inner.__code__
orig.__defaults__ = inner.__defaults__
orig.__kwdefaults__ = inner.__kwdefaults__
Zuerst dachte ich, ich würde nur "code" überschreiben, aber die Anzahl von "__code __. Co_freevars" (die Anzahl der Variablen der äußeren Funktion, die die in der Funktion definierte Funktion intern verwendet?) Ist eine. Es scheint, dass es nicht zugewiesen werden kann, wenn es nicht erledigt ist, also wird die Anzahl der Freevars mit exec
angepasst.
Wenn es sich um eine Prototypversion handelt, geht die Signatur verloren, daher habe ich sie so weit wie möglich belassen.
Dem Schlüsselwortargument wird jedoch __overwrite_func
hinzugefügt, um __code __. Co_freevars
anzupassen.
def overwrite_func(orig, new, signature=None):
import inspect
from types import FunctionType
from textwrap import dedent
assert isinstance(orig, FunctionType), (orig, type(orig))
assert isinstance(new, FunctionType), (new, type(new))
if signature is None:
signature = inspect.signature(orig)
params = [
(str(p).split(':')[0].split('=')[0], p)
for k, p in signature.parameters.items()
if k != '__overwrite_func'
]
default = {p.name: p.default for _, p in params}
anno = {p.name: p.annotation for _, p in params}
args_kwargs = [
k if k[0] == '*' or p.kind == p.POSITIONAL_ONLY else k + '=' + k
for k, p in params
]
signature_ = [
(k + (':anno["' + k + '"]' if p.annotation != p.empty else '')
+ ('=default["' + k + '"]' if p.default != p.empty else ''),
not (p.kind == p.VAR_KEYWORD or p.kind == p.KEYWORD_ONLY))
for k, p in params
]
signature__ = [s for s, positional in signature_ if positional]
signature__.append('__overwrite_func=new')
signature__.extend(s for s, positional in signature_ if not positional)
signature__ = '(' + ', '.join(signature__) + ')'
if signature.return_annotation is not inspect.Signature.empty:
anno['return'] = signature.return_annotation
signature__ += ' -> anno["return"]'
source = dedent("""
def outer():
""" + '='.join(list(orig.__code__.co_freevars) + ['None']) + """
def inner""" + signature__ + """:
""" + ', '.join(orig.__code__.co_freevars) + """
return __overwrite_func(""" + ', '.join(args_kwargs) + """)
return inner
""")
globals_ = {}
exec(source, dict(new=new, default=default, anno=anno), globals_)
inner = globals_['outer']()
globals_.clear()
orig.__code__ = inner.__code__
orig.__defaults__ = inner.__defaults__
orig.__kwdefaults__ = inner.__kwdefaults__
orig.__annotations__ = inner.__annotations__
Die diesmal erstellte Funktion ist nicht universell. Es ist machtlos für spezielle Funktionen, die keinen code haben, oder aufrufbare Objekte, die call implementieren. Bitte benutzen Sie es nach Angaben der anderen Partei.
overwrite_func(print, new_func) #assert ist deaktiviert
# → AttributeError: 'builtin_function_or_method' object has no attribute '__code__'
Achten Sie auf Speicherlecks, da sich in __overwrite_func
Verweise auf Überschreibungsfunktionen ansammeln.
def copy_func(f):
"""https://stackoverflow.com/questions/13503079"""
import functools
import types
assert isinstance(f, types.FunctionType), (f, type(f))
g = types.FunctionType(
f.__code__,
f.__globals__,
name=f.__name__,
argdefs=f.__defaults__,
closure=f.__closure__,
)
g.__kwdefaults__ = f.__kwdefaults__
functools.update_wrapper(g, f)
return g
def add_hook(func, pre_call=None, post_call=None, except_=None, finally_=None):
import inspect
func_sig = inspect.signature(func)
func_copy = copy_func(func)
def hook(*args, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
if pre_call is not None:
pre_call(func_copy, bound_args)
try:
return_ = func_copy(*args, **kwargs)
except BaseException as e:
if except_ is not None:
except_(func_copy, bound_args, e)
raise
else:
if post_call is not None:
post_call(func_copy, bound_args, return_)
finally:
if finally_ is not None:
finally_(func_copy, bound_args)
return return_
overwrite_func(func, hook)
Sie können später eine Rückruffunktion hinzufügen.
def callback(f, args, result):
print(result)
add_hook(original_func, post_call=callback)
original_func() # original_func()Vor dem Rückruf angerufen()Wird genannt.
Ich konnte es tun, aber wenn ich es nicht tun muss, sollte ich es nicht tun.
Die Testfälle reichen wahrscheinlich nicht aus, da ich die Spezifikationen für __code__
nicht vollständig verstehe.
Bitte lassen Sie mich wissen, wenn es einen Fall gibt, der nicht funktioniert.
Recommended Posts