Python добавляет к функции динамически - PullRequest
16 голосов
/ 07 мая 2010

как добавить код в существующую функцию, до или после?

например, у меня есть класс:

 class A(object):
     def test(self):
         print "here"

как мне отредактировать метапрограммирование класса wit так, чтобы я сделал это

 class A(object):
     def test(self):
         print "here"

         print "and here"

может быть, есть какой-нибудь способ добавить другую функцию для проверки?

добавить еще одну функцию, например

 def test2(self):
      print "and here"

и измените оригинал на

 class A(object):
     def test(self):
         print "here"
         self.test2()

есть ли способ сделать это?

Ответы [ 7 ]

22 голосов
/ 07 мая 2010

Вы можете использовать декоратор для изменения функции, если хотите. Однако, поскольку это не декоратор, примененный во время первоначального определения функции, вы не сможете использовать синтаксический сахар @ для его применения.

>>> class A(object):
...     def test(self):
...         print "orig"
...
>>> first_a = A()
>>> first_a.test()
orig
>>> def decorated_test(fn):
...     def new_test(*args, **kwargs):
...         fn(*args, **kwargs)
...         print "new"
...     return new_test
...
>>> A.test = decorated_test(A.test)
>>> new_a = A()
>>> new_a.test()
orig
new
>>> first_a.test()
orig
new

Обратите внимание, что это изменит метод и для существующих экземпляров.

EDIT : изменен список аргументов для декоратора до версии лучше с использованием args и kwargs

10 голосов
/ 07 мая 2010

Типичный способ добавить функциональность в функцию - это использовать декоратор (используя функцию обертывания ):

from functools import wraps

def add_message(func):
    @wraps
    def with_additional_message(*args, **kwargs)
        try:
            return func(*args, **kwargs)
        finally:
            print "and here"
    return with_additional_message

class A:
    @add_message
    def test(self):
        print "here"

Конечно, это действительно зависит от того, чего вы пытаетесь достичь. Я часто использую декораторы, но если бы все, что я хотел сделать, это напечатать дополнительные сообщения, я бы, вероятно, сделал что-то вроде

class A:
    def __init__(self):
        self.messages = ["here"]

    def test(self):
        for message in self.messages:
            print message

a = A()
a.test()    # prints "here"

a.messages.append("and here")
a.test()    # prints "here" then "and here"

Это не требует метапрограммирования, но опять же ваш пример, вероятно, был значительно упрощен от того, что вам действительно нужно делать. Возможно, если вы опубликуете более подробную информацию о ваших конкретных потребностях, мы можем лучше посоветовать, какой будет подход Pythonic.

РЕДАКТИРОВАТЬ: Поскольку кажется, что вы хотите вызывать функции, вы можете иметь список функций вместо списка сообщений. Например:

class A:
    def __init__(self):
        self.funcs = []

    def test(self):
        print "here"
        for func in self.funcs:
            func()

def test2():
    print "and here"

a = A()
a.funcs.append(test2)
a.test()    # prints "here" then "and here"

Обратите внимание, что если вы хотите добавить функции, которые будут вызываться всеми экземплярами A, тогда вы должны сделать funcs полем класса, а не полем экземпляра, например,

class A:
    funcs = []
    def test(self):
        print "here"
        for func in self.funcs:
            func()

def test2():
    print "and here"

A.funcs.append(test2)

a = A()
a.test()    # print "here" then "and here"
3 голосов
/ 23 сентября 2015

Этот ответ позволяет изменить исходную функцию без создания оболочки. Я нашел следующие два нативных типа Python, которые полезны для ответа на ваш вопрос:

types.FunctionType

А

types.CodeType

Этот код, кажется, делает работу:

import inspect
import copy
import types
import dill
import dill.source


#Define a function we want to modify:
def test():
    print "Here"

#Run the function to check output
print '\n\nRunning Function...'
test()
#>>> Here

#Get the source code for the test function:
testSource = dill.source.getsource(test)
print '\n\ntestSource:'
print testSource


#Take the inner part of the source code and modify it how we want:
newtestinnersource = ''
testSourceLines = testSource.split('\n')
linenumber = 0 
for line in testSourceLines:
    if (linenumber > 0):
        if (len(line[4:]) > 0):
            newtestinnersource += line[4:] + '\n'
    linenumber += 1
newtestinnersource += 'print "Here2"'
print '\n\nnewtestinnersource'
print newtestinnersource


#Re-assign the function's code to be a compiled version of the `innersource`
code_obj = compile(newtestinnersource, '<string>', 'exec')
test.__code__ = copy.deepcopy(code_obj)
print '\n\nRunning Modified Function...'
test() #<- NOW HAS MODIFIED SOURCE CODE, AND PERFORMS NEW TASK
#>>>Here
#>>>Here2
3 голосов
/ 07 мая 2010

Выше приведено много действительно хороших предложений, но я не видел, чтобы функция передавала вызов. Может выглядеть примерно так:

class A(object):
    def test(self, deep=lambda self: self):
        print "here"
        deep(self)
def test2(self):
    print "and here"

Используя это:

>>> a = A()
>>> a.test()
here
>>> a.test(test2)
here
and here
2 голосов
/ 07 мая 2010

Почему бы не использовать наследование?

class B(A):
    def test(self):
        super(B, self).test()
        print "and here"
1 голос
/ 04 ноября 2010

Скопируйте и вставьте, наслаждайтесь !!!!!

#!/usr/bin/env python 

def say(host, msg): 
   print '%s says %s' % (host.name, msg) 

def funcToMethod(func, clas, method_name=None): 
   setattr(clas, method_name or func.__name__, func) 

class transplant: 
   def __init__(self, method, host, method_name=None): 
      self.host = host 
      self.method = method 
      setattr(host, method_name or method.__name__, self) 

   def __call__(self, *args, **kwargs): 
      nargs = [self.host] 
      nargs.extend(args) 
      return apply(self.method, nargs, kwargs) 

class Patient: 
   def __init__(self, name): 
      self.name = name 

if __name__ == '__main__': 
   jimmy = Patient('Jimmy') 
   transplant(say, jimmy, 'say1') 
   funcToMethod(say, jimmy, 'say2') 

   jimmy.say1('Hello') 
   jimmy.say2(jimmy, 'Good Bye!') 
0 голосов
/ 07 мая 2010

Если ваш класс A наследуется от объекта, вы можете сделать что-то вроде:

def test2():
    print "test"

class A(object):
    def test(self):
        setattr(self, "test2", test2)
        print self.test2
        self.test2()

def main():
    a = A()
    a.test()

if __name__ == '__main__':
    main()

Этот код не интересен, и вы не можете использовать self в своем новом методе, который вы добавили. Я нашел этот код забавным, но никогда не буду его использовать. Я не люблю динамически менять сам объект.

Это самый быстрый и понятный способ.

...