[Python3] Schreiben Sie das Codeobjekt der Funktion neu

Einführung

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

Was Sie tun möchten (wie man es benutzt)

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

Gemacht (Prototyp)

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.

Gemacht (fertige Version)

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__

Hinweis

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.

Anwendungsbeispiel

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.

abschließend

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

[Python3] Schreiben Sie das Codeobjekt der Funktion neu
[Python] Wert des Funktionsobjekts (?)
[Python] Ruft den Zeichencode der Datei ab
Schreiben Sie Python2-Code in Python3 um (2to3)
der Zen von Python
[Python] Lesen Sie den Quellcode von Flasche Teil 2
Holen Sie sich den Aufrufer einer Funktion in Python
[Python] Lesen Sie den Quellcode von Flasche Teil 1
Code zum Überprüfen des Betriebs von Python Matplot lib
Konvertieren Sie den Zeichencode der Datei mit Python3
Auf dem Weg zum Ruhestand von Python2
[Python] Berechnen Sie den Durchschnittswert des Pixelwerts RGB des Objekts
[Python] Der Ursprung des Namens der Python-Funktion
Lassen Sie uns die Grundlagen des Python-Codes von TensorFlow aufschlüsseln
Erläutern Sie den Code von Tensorflow_in_ROS
Über die Aufzählungsfunktion (Python)
Holen Sie sich den Rückkehrcode eines Python-Skripts von bat
#Eine Funktion, die den Zeichencode einer Zeichenfolge zurückgibt
2.x, 3.x Serienzeichencode von Python
Über die Funktionen von Python
Die Kraft der Pandas: Python
[Python] Ändern Sie die Cache-Steuerung von Objekten, die in den Cloud-Speicher hochgeladen wurden
Versuchen Sie, die Funktionsliste des Python> os-Pakets abzurufen
Lassen Sie das Gleichungsdiagramm der linearen Funktion in Python zeichnen
Schreiben Sie den Datensatzadditionsknoten von SPSS Modeler mit Python neu.
Der Prozess, Python-Code objektorientiert zu machen und zu verbessern
26.11.2015 Python> Funktionsliste des Moduls anzeigen> Mathematik importieren> Verzeichnis (Mathematik)
Die Geschichte von Python und die Geschichte von NaN
[Python] Der Stolperstein des Imports
Erster Python 3 ~ Der Beginn der Wiederholung ~
Existenz aus Sicht von Python
pyenv-change die Python-Version von virtualenv
Ruft die Attribute eines Objekts ab
[Python] Machen Sie die Funktion zu einer Lambda-Funktion
[Python] Die potenzielle Feldplanung von Python Robotics verstehen
Überprüfung der Grundlagen von Python (FizzBuzz)
Berühren Sie das Objekt des neuronalen Netzes
[Python] Lesen Sie den Flask-Quellcode
Informationen zur Grundlagenliste der Python-Grundlagen
[OpenCV; Python] Zusammenfassung der Funktion findcontours
Lernen Sie die Grundlagen von Python ① Grundlegende Anfänger
Python-Funktion ①
Wie Sie die interne Struktur eines Objekts in Python kennen
Messen Sie die Testabdeckung von Push-Python-Code auf GitHub.
Versuchen Sie, die stochastische Massenfunktion der Binomialverteilung in Python zu transkribieren
[Python] -Funktion
Erste Python ② Versuchen Sie, Code zu schreiben, während Sie die Funktionen von Python untersuchen
Ich habe den Code geschrieben, um den Brainf * ck-Code in Python zu schreiben
Eine Funktion, die die Verarbeitungszeit einer Methode in Python misst
[Python3] Definition eines Dekorators, der die Ausführungszeit einer Funktion misst
Fassen wir den Grad der Kopplung zwischen Modulen mit Python-Code zusammen
Schreiben Sie den Sampling-Knoten von SPSS Modeler mit Python (2) neu: Layered Sampling, Cluster Sampling
[Python] Eine einfache Funktion zum Ermitteln der Mittelkoordinaten eines Kreises
Python-Funktion ②
Installation von Visual Studio Code und Installation von Python