2012-08-11 15 views
7

Bu işlevim varsa, iç işlevi kendi özel sürümüm ile değiştirmek için ne yapmalıyım?İç içe geçmiş işlevler için eşdeğer bir eşdeğer var mı?

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 

Sınıfları kullanmak, bu yöntemi geçersiz kılarak kolayca yapılabilir. Yine de, iç içe geçmiş fonksiyonlarla bunu nasıl yapacağımı anlayamıyorum. foo'u bir sınıf (veya başka bir şey) olarak değiştirmek, değiştirilemediğim belirli bir içe aktarılmış modülden geldiği için bir seçenek değildir.

+0

kullanılır:

İşte
# 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 uygulanması var? Daha sonraki işlevlerde ('baz' gibi) kullanılır mı? Siz * bu vakalarda değiştirmek istersiniz? –

+0

Tam olarak. Bunu yapmanın herhangi bir yolu var mı? – Paolo

+0

İşlevlerin çeşitli iç detaylarına erişebilirsiniz, çünkü bunlar sadece nesnelerdir. Daha fazla bilgi için dir işlevini ve dil referansını kullanın. – Marcin

cevap

10

İşte bunu yapmanın bir yolu olarak geçirebilirsiniz. (@DSM tarafından belirtildiği gibi). Ne yazık ki sadece foo işlevine atlayabiliyor ve sadece okunaklı oldukları için içeriden uzaklaşamıyoruz, yani yapmamız gereken, el ile oluşturduğumuz bir kopyasını değiştirmek.

# 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() 

Ben bütün davaları yakalamayacaksin onun eminim.

  1. büyük argüman listesi CodeType geçirilen
  2. The: Ama bazı kadar düzenli yapabilirdi

    Çirkin bit (eski piton 2.5.1 benim için) örneğin çalışır çirkin tuple, yalnızca bir üyeyi geçersiz kılan co_consts'dan yapılmıştır. Tüm bilgiler, hangisinin değiştirileceğini belirlemek için co_consts içinde bulunur - bu yüzden daha akıllı bir işlev bunu yapabilir. İçine print(foo.func_code.co_consts) kullanarak içten içe kazdım.

Sen tercüman komutunu help(types.CodeType) kullanarak CodeType ve FunctionType hakkında bazı bilgiler bulabilirsiniz.

GÜNCELLEME: Bunun çok çirkin olduğunu düşündüm, bu yüzden daha güzel hale getirmek için yardımcı bir işlev oluşturdum. yardımcısı ile yazabilirsiniz: `bar`

# 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, {}) 
+0

Her iki kodlama yaklaşımının da bu cevaplara sahip olması, içgörünün iki katı kadardır. – MikeiLL

3

Sen fonksiyon internals hack tarafından "doğru şeyi yapar" diye yeni bir foo oluşturarak, isteğe bağlı bir parametre

def foo(bar=None): 
    def _bar(): 
     # I want to change this 
     pass 
    if bar is None: 
     bar = _bar 
+0

Anladığımdan emin değilim. Cevabınız verilen işlevi değiştirmeyi gerektirmiyor mu? Belki de net olmadım ama orijinal foo'yu değiştiremem. Btw, sorumu biraz düzenleyeceğim. – Paolo

+4

OP'nin bir çeşit maymunpatch seçeneği aradığını düşünüyorum, çünkü 'foo' değiştirilemeyen belirli bir ithal modülden geliyor. " – PaulMcG