简体   繁体   中英

Dynamically inserting a function into a Python class

I am writing a metaclass that amongst other things wants to add a method to the classes it creates. Let's forget about the metaclass for now though and just look at simple adding of methods.

In order to dynamically add an instance method I can do:

class Foo:
    def bar(self, x):
        print(f"In bar() with {x} and {self}")

def func(self, x):
    print(f"In func() with {x} and {self}")

Foo.func = func

After that I can do:

>>> f = Foo()                                                                                                                                              
>>> f.bar(7)                                                                                                                                               
In bar() with 7 and <__main__.Foo object at 0x7f912a7e57f0>                                                                                                
>>> f.func(7)                                                                                                                                              
In func() with 7 and <__main__.Foo object at 0x7f912a7e57f0>

So the methods bar and func seem to function identically, but there are some discernible differences, eg:

>>> f.bar.__qualname__                                                                                                                                     
'Foo.bar'                                                                                                                                                  
>>> f.func.__qualname__                                                                                                                                 
'func'

f.func.__module__ could also potentially be different from f.bar.__module__ , depending on where everything is defined.

What do I have to change in Construction 2 (below) in order for both constructions to behave exactly the same (no code that uses the Foo class could change its behaviour depending on which construction is used)?

# Construction 1
class Foo:
    def func(self):
        pass

# Construction 2
class Foo:
    pass
def func(self):
    pass
Foo.func = func

I have created a decorator that hopefully implements a sensible version of Construction 2, but what could I still be missing/breaking by monkey patching like that?

class instance_method_of:

    def __init__(self, cls, name=None):
        self.cls = cls
        self.name = name

    def __call__(self, func):
        if self.name is not None:
            func.__name__ = self.name
        func.__qualname__ = f'{self.cls.__qualname__}.{func.__name__}'
        func.__module__ = self.cls.__module__
        setattr(self.cls, func.__name__, func)
        return func

class Foo:
    pass

@instance_method_of(Foo)
def func(self):
    pass

It seems to cover everything: Although I would go with a function decorator like :

from pathlib import Path

def patch(f):
    cls = next(iter(f.__annotations__.values()))
    name = f.__defaults__[0]
    f.__qualname__ = f"{cls.__name__}.{f.__name__}"
    f.__module__ = cls.__module__
    if name is None:
        setattr(cls,f.__name__,f)
    else:
        f.__qualname__ = f"{cls.__name__}.{name}"
        setattr(cls,name,f)
    return f

@patch
def new(self:Path, name:str=None):
    "new"
    return list(self.iterdir())

path = Path()
path.new()

mostly adapted from fastai version

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM