1

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, e.g.:

>>> 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

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.