7

この関数がある場合、内部関数を独自のカスタム バージョンに置き換えるにはどうすればよいですか?

def foo():
    def bar():
        # I want to change this
        pass

    # here starts a long list of functions I want to keep unchanged
    def baz():
        pass

クラスを使用すると、メソッドをオーバーライドして簡単に実行できます。ただし、ネストされた関数でそれを行う方法がわかりません。クラス(またはその他のもの)に変更fooすることはオプションではありません。これは、変更できない特定のインポートされたモジュールからのものであるためです。

4

2 に答える 2

11

関数の内部をハッキングして「正しいことをする」新しい foo を作成する方法の 1 つを次に示します。( @DSM で述べたように)。残念ながら、関数に飛び込んfooでその内部をいじることはできません。それらはほとんどが読み取り専用としてマークされているためです。そのため、作成したコピーを手動で変更する必要があります。

# Here's the original function
def foo():
  def bar():
    print("    In bar orig")
  def baz():
    print("  Calling bar from baz")
    bar()
  print("Foo calling bar:")
  bar()
  print("Foo calling baz:")
  baz()

# Here's using it
foo()

# Now lets override the bar function

import types

# This is our replacement function
def my_bar():
  print("   Woo hoo I'm the bar override")

# This creates a new code object used by our new foo function 
# based on the old foo functions code object.
foocode = types.CodeType(
    foo.func_code.co_argcount,
    foo.func_code.co_nlocals,
    foo.func_code.co_stacksize,
    foo.func_code.co_flags,
    foo.func_code.co_code,
    # This tuple is a new version of foo.func_code.co_consts
    # NOTE: Don't get this wrong or you will crash python.
    ( 
       foo.func_code.co_consts[0],
       my_bar.func_code,
       foo.func_code.co_consts[2],
       foo.func_code.co_consts[3],
       foo.func_code.co_consts[4]
    ),
    foo.func_code.co_names,
    foo.func_code.co_varnames,
    foo.func_code.co_filename,
    foo.func_code.co_name,
    foo.func_code.co_firstlineno,
    foo.func_code.co_lnotab,
    foo.func_code.co_freevars,
    foo.func_code.co_cellvars )

# This is the new function we're replacing foo with
# using our new code.
foo = types.FunctionType( foocode , {})

# Now use it
foo()

すべてのケースをキャッチできるわけではないと確信しています。しかし、それは例では機能します(古いpython 2.5.1の私にとって)

いくつかの片付けでできる醜いビットは次のとおりです。

  1. CodeType に渡される巨大な引数リスト
  2. co_consts1 つのメンバーのみをオーバーライドして構築された醜いタプル。すべての情報は co_consts にあり、どれを置き換えるかを決定します。したがって、よりスマートな関数でこれを行うことができます。を使って手で内部を掘りましたprint( foo.func_code.co_consts )

CodeTypeおよびに関する情報はFunctionType、インタープリター コマンド を使用して見つけることができますhelp( types.CodeType )

更新:これはあまりにも醜いと思ったので、ヘルパー関数を作成して見栄えを良くしました。ヘルパーを使用すると、次のように記述できます。

# Use our function to get a new version of foo with "bar" replaced by mybar    
foo = monkey_patch_fn( foo, "bar", my_bar )

# Check it works
foo()

の実装は次のmonkey_patch_fnとおりです。

# Returns a copy of original_fn with its internal function
# called name replaced with new_fn.
def monkey_patch_fn( original_fn, name, new_fn ):

  #Little helper function to pick out the correct constant
  def fix_consts(x):
    if x==None: return None
    try:
      if x.co_name == name:
        return new_fn.func_code
    except AttributeError, e:
        pass
    return x

  original_code = original_fn.func_code
  new_consts = tuple( map( fix_consts, original_code.co_consts ) )
  code_type_args = [
     "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code",
     "co_consts", "co_names", "co_varnames", "co_filename", "co_name",
     "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars" ]

  new_code = types.CodeType(
     *[ ( getattr(original_code,x) if x!="co_consts" else new_consts )
        for x in code_type_args ] )
  return types.FunctionType( new_code, {} )
于 2012-08-11T05:07:29.523 に答える
3

オプションのパラメータとして渡すことができます

def foo(bar=None):
    def _bar():
        # I want to change this
        pass
    if bar is None:
        bar = _bar
于 2012-08-11T03:06:57.273 に答える