Nano Hash - криптовалюты, майнинг, программирование

украшение функции класса вызываемым экземпляром

Я пытаюсь украсить функцию, заменив ее экземпляром вызываемого класса:

class FunctionFaker( object ):
    def __init__( self, f ):
        self.f= f
    def empty_function( self ):
        pass
    def __call__( self, *args, **kwargs ):
        self.f( *args, **kwargs)

def fakefunction( f ):
    '''a decorator that transforms a function into a FunctionFaker'''
    return FunctionFaker(f)

@fakefunction
def dosomething():
    pass

dosomething.empty_function()
dosomething()

Это работает, как и ожидалось.

Однако, как только я пытаюсь украсить метод класса:

class Test( object ):
    @fakefunction
    def dosomething(self):
        pass

t=Test()
t.dosomething.empty_function()
t.dosomething()

Я получаю TypeError: dosomething() takes exactly 1 argument (0 given).

Теперь я думаю я могу ответить, почему:

Для поддержки вызовов методов функции включают метод __get__() для связывания методов во время доступа к атрибуту. Это означает, что все функции не являются дескрипторами данных, которые возвращают связанные или несвязанные методы в зависимости от того, вызываются ли они из объекта или класса.

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

Как я могу реализовать вызываемый класс, способный заменить метод экземпляра?

06.10.2014

  • Я только что понял, что могу просто реализовать __get__ и вернуть types.MethodType, но я действительно не понимаю, как это все еще позволяет вам вызывать empty_function. Становится ли он просто атрибутом экземпляра MethodType? И это разумный поступок? 07.10.2014
  • Подпись — types.MethodType(callable, instance, class), и вы можете передать любой вызываемый объект, включая экземпляр класса, который имеет функцию __call__. 07.10.2014

Ответы:


1

Я только что понял, что могу просто реализовать __get__ и вернуть types.MethodType, но я действительно не понимаю, как это все еще позволяет вам вызывать empty_function.

Это потому, что MethodType имеет метод __getattribute__, который делегирует неизвестные атрибуты своему im_func:

>>> t.dosomething
<bound method Test.? of <__main__.Test object at 0x107639550>>
>>> 'empty_function' in dir(t.dosomething)
False
>>> t.dosomething.__getattribute__
<method-wrapper '__getattribute__' of instancemethod object at 0x109583aa0>
>>> t.dosomething.__getattribute__('empty_function')
<bound method FunctionFaker.empty_function of <__main__.FunctionFaker object at 0x1095f2510>>

Конечно, в CPython API-интерфейс C не совсем точно отражает различия на уровне Python между __getattribute__ и __getattr__, поэтому он на самом деле реализован с помощью пользовательского getattro. Подробности можно прочитать в источнике.

Становится ли он просто атрибутом экземпляра MethodType?

Да, но только динамически, предоставляя вам атрибут основного вызываемого объекта.

Я не думаю, что они специально предназначались для того, чтобы экземпляры классов заменяли функции собственными дескрипторами методов. Но эта поддержка нужна даже для простых случаев присоединения атрибутов к методам. Например, с автономной функцией вы можете использовать атрибут функции, например, для кэша мемоизации или настройки отложенной инициализации при первом вызове. Если бы MethodType не делегировал доступ к атрибутам своему объекту im_func, перемещение такой функции в класс сломало бы его, и разработчик не смог бы это исправить, если бы не знал, как работают дескрипторы, и не переписал метод уродливым образом.

На самом деле до версии 2.3 у методов даже не было __dict__; как видно из источника, все атрибуты, кроме слотов C, были делегированы im_func (путем эффективного дублирования обычного механизма делегирования всего im_func, но переноса ошибок). По этому поводу были некоторые дебаты, которые вы, вероятно, могли бы найти, выполнив поиск в архивах python-dev поста Кристиана Тисмера в период до версии 2.4 с соответствующей темой (это может быть эту тему, но я не читал ее целиком…). Начиная с версии 2.4, методы теперь выполняют обычный механизм поиска (за исключением особого случая __doc__) и делегируют im_func только в случае сбоя.

И это разумный поступок?

Это немного странно, и может быть проще добавить атрибут empty_function к объекту функции, вместо того, чтобы оборачивать его в класс… но я не думаю, что это слишком неразумно. (Я предполагаю, что вы спрашиваете о своем коде, а не о том, как реализованы MethodType и дескрипторы.)

07.10.2014
  • Удивительно. Означает ли MethodType реализация __getattribute__, что разработчики Python как-то предвидели эту потребность? Или есть другая причина? Документы по MethodType довольно скудны, есть ли какие-либо ссылки на это? 07.10.2014
  • Хорошо, я ответил на первую половину вашего комментария в отредактированном ответе. Что касается второй половины… нет, на самом деле нет никакой ссылки, кроме чтения источника и/или реверс-инжиниринга. (Дескриптор HOWTO, на который вы ссылаетесь, просто указывает на тот же источник в classobject.c, на который я ссылался для деталей.) В Python есть несколько подобных мест; они постепенно очищаются по мере того, как разработчики PyPy и других реализаций их поднимают, но этот до сих пор не задокументирован. 07.10.2014

  • 2

    Вы на правильном пути. Вы хотите, чтобы ваш класс также был дескриптором:

    import types
    
    class FunctionFaker( object ):
        def __init__(self, f):
            self.f= f
    
        def empty_function(self):
            pass
    
        def __call__(self, *args, **kwargs):
            self.empty_function()
            self.f(*args, **kwargs)
    
        def __get__(self, instance, cls=None):
            # see https://docs.python.org/2/howto/descriptor.html#functions-and-methods
            return types.MethodType(self, instance, cls)
    
    @FunctionFaker
    def foo(arg1):
      print "in foo", arg1
    
    class Bar(object):
      @FunctionFaker
      def bar(self, arg1):
        print "in bar", self, arg1
    
    foo('hello')  # in foo hello
    Bar().bar('world')  # in bar <__main__.Bar object at 0x7fc50b90fb10> world
    Bar().bar.empty_function()
    

    Теперь, если ваш декоратор привязан к классу, он ведет себя как дескриптор (привязывая соответствующий экземпляр к self в оформленной функции), а если он не привязан к классу, он ведет себя как обычный декоратор. Аккуратный.

    07.10.2014
  • Нет, на самом деле это не сработает; если он вернет partial вместо MethodType, empty_function поднимет AttributeError. 07.10.2014
  • @abarnert - я не совсем уверен, откуда OP вызывает empty_function, но, похоже, это работает в моих тестах. . . 07.10.2014
  • Он уже создает дескриптор, который возвращает types.MethodType, точно так же, как обычная функция, точно так, как явно рекомендуют документы. Поскольку MethodType имеет делегирующий __getattribute__, t.dosomething.empty_function() работает. С вашим изменением t.dosomething теперь partial, поэтому t.dosomething.empty_function() повысит AttributeError. 07.10.2014
  • @abanert на высоте. Но в этот момент мне действительно было интересно, сработает ли это 07.10.2014
  • @abarnert - Ааа, теперь я вижу, чего хотел ОП. Я не понимал, что цель состояла в том, чтобы добавить атрибут к функции. И спасибо за то, что явно рекомендуют документы. Я не помнил этого в документах по дескрипторам. Жаль, что в документах types больше не говорится о сигнатурах различных типов и о том, что они делают... 07.10.2014
  • @mgilson: я тоже этого не помнил, но он ссылался на это в вопросе. (И я согласен с вами по поводу types документов. Даже основных материалов, доступных в строках документации во время выполнения, нет в веб-документах, и это одно из немногих мест в Python, где это не соответствует действительности.) 07.10.2014
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..