Перегрузить метод функцией во время выполнения - PullRequest
4 голосов
/ 01 декабря 2009

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

class SomeClass(object):
    def __init__(self):
        def __(self, arg):
            self.doStuff(arg)
        self.overLoaded = __
    def doStuff(self, string):
        print string

SomeClass().overLoaded("test string")

Это возвращает ошибку параметра, потому что я предоставляю overLoaded () только один аргумент вместо двух. Есть ли какая-то магия, чтобы сказать интерпретатору, что теперь это метод класса (я пытался украсить его с помощью @classmethod, я всегда понимал, что это его цель ??)

Ответы [ 3 ]

8 голосов
/ 01 декабря 2009

Не беспокойтесь о параметре self, функция уже имеет его из локальной области видимости.

class SomeClass(object):
    def __init__(self):
        def __(arg):
            self.bar(arg)
        self.foo = __
    def foo(self, arg):
        print "foo", arg
    def bar(self, arg):
        print "bar", arg

SomeClass().foo("thing") # prints "bar thing"

При создании экземпляра (после __new__, iirc, но до __init__) Python связывает все методы для автоматического предоставления экземпляра в качестве первого аргумента. Если вы добавляете метод позже, вам нужно предоставить экземпляр вручную. Поскольку вы определяете функцию с self уже в области видимости, вам не нужно передавать ее снова.

Модуль Python new не является решением, поскольку он устарел с версии 2.6. Если вы хотите создать «настоящий» метод экземпляра, сделайте это с частичным декоратором, как это:

import functools

class SomeClass(object):
    def __init__(self):
        def __(self, arg):
            self.bar(arg)
        self.foo = functools.partial(__, self)
    def foo(self, arg):
        print "foo", arg
    def bar(self, arg):
        print "bar", arg

SomeClass().foo("thing") # prints "bar thing"
3 голосов
/ 01 декабря 2009

Проблема в том, что вы пытаетесь добавить новый метод экземпляра (не метод класса), и он не связывается должным образом. В Python есть функция модуля для ручного связывания функций с экземплярами.

import new
self.method = new.instancemethod(func, self, class)

Редактировать: Очевидно, модуль new устарел. Вместо этого используйте модуль types для метамагии.

import types
self.method = types.MethodType(func, self, class)
0 голосов
/ 01 декабря 2009

sj26 решение хорошее. Другой вариант, если вы хотите настроить метод, который может быть перегружен с помощью любой пользовательской функции или с помощью другого метода объекта, это создать собственный дескриптор. Этот дескриптор может использоваться в качестве декоратора (аналог @classmethod или @staticmethod); и это позволяет вам сохранять функцию в словаре экземпляра и возвращает ее как метод:

import types

class overloadable(object):
    def __init__(self, func):
        self._default_func = func
        self._name = func.__name__

    def __get__(self, obj, type=None):
        func = obj.__dict__.get(self._name, self._default_func)
        return types.MethodType(func, obj, type)

    def __set__(self, obj, value):
        if hasattr(value, 'im_func'): value = value.im_func
        obj.__dict__[self._name] = value

    def __delete__(self, obj):
        del obj.__dict__[self._name]

Теперь мы можем просто украсить функцию с помощью "@overloadable":

class SomeClass(object):
    def doStuff(self, string):
        print 'do stuff:', string
    @overloadable
    def overLoaded(self, arg):
        print 'default behavior:', arg

И это будет правильно делать, когда мы перегрузим его для данного экземпляра:

>>> sc = SomeClass()
>>> sc.overLoaded("test string")    # Before customization
default behavior: test string

>>> sc.overLoaded = sc.doStuff      # Customize
>>> sc.overLoaded("test string")
do stuff: test string

>>> del sc.overLoaded               # Revert to default behavior
>>> sc.overLoaded("test string")
default behavior: test string
...