Преобразовать тип встроенной функции в тип метода (в Python 3)

Рассмотрим простую функцию, такую как

def increment(self):
    self.count += 1

который запускается через Cython и компилируется в модуль расширения. Предположим теперь, что я хотел бы сделать эту функцию методом класса. Например:

class Counter:
    def __init__(self):
        self.count = 0

from compiled_extension import increment
Counter.increment = increment

Теперь это не будет работать, так как соглашение о вызовах на уровне C будет нарушено. Например:

>>> c = Counter()
>>> c.increment()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: increment() takes exactly one argument (0 given)

Но в Python 2 мы можем преобразовать функцию в несвязанный метод, выполнив:

Counter.increment = types.MethodType(increment, None, Counter)

How can I accomplish this same thing in Python 3?

Одним простым способом является использование тонкой обертки:

from functools import wraps
def method_wraper(f):
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return wraps(f)(wrapper)

Counter.increment = method_wrapper(increment)

Есть ли более эффективный способ сделать это?

 bdew20 июн. 2012 г., 14:22
Проблема в том, что встроенные функции и функции python обрабатываются по-разному во время выполнения, не имея ничего общего с тем, чтобы находиться в другом модуле. Комментарий о PyMethod_Type действительно не применяется, поскольку он представляет метод, определенный в python, а не встроенные функции / методы.
 ederag18 сент. 2016 г., 10:04
Не следует ли «читать» конвертировать функцию вbound Способ & Quot; ?
 JBernardo24 мая 2012 г., 18:50
У меня была эта проблема, пытаясь построить класс Heapq, используя уродливыйheapq модуль. Ваше решение хорошо. Это можно сделать в одну строку, но с той же эффективностью:def method_wraper(f): return functools.wraps(f)(lambda *a, **kw: f(*a, **kw))
 JAB24 мая 2012 г., 19:10
Также,docs.python.org/py3k/howto/… & quot; фактическая реализация C PyMethod_Type в Objects / classobject.c представляет собой один объект с двумя различными представлениями в зависимости от того, установлено ли поле im_self или равно NULL (эквивалент C для None). & quot; Это создает впечатление, что эта проблема вообще не должна возникать, если Python каким-либо образом не обновляет это поле непосредственно для методов объекта при создании экземпляра объекта.
 JAB24 мая 2012 г., 19:01
... Интересно, что назначение работает нормально, если функция определена в том же модуле (несвязанный метод, который присваивается классу и привязывается к экземпляру). Так что это только проблема с расширениями C или с функциями в разных модулях? В любом случае, вы можете проверитьstackoverflow.com/questions/7490879/…, что может немного помочь вам.

Ответы на вопрос(2)

Импортируйте расширение следующим образом:

import compiled_extension

В вашем классе вы пишете:

def increment: return compiled_extension.increment()

Это кажется более читабельным и может быть более эффективным.

>>> def increment(obj):
...     obj.count += 1
...
>>> class A(object):
...     def __init__(self):
...         self.count = 0
...
>>> o = A()
>>> o.__init__
<bound method A.__init__ of <__main__.A object at 0x0000000002766EF0>>
>>> increment
<function increment at 0x00000000027797C8>

Так что собственные именаfunctions а такжеbound methods, Теперь вы можете посмотреть, какBind an Unbound Method и вы, вероятно, в конечном итоге читать оdescriptors:

In general, a descriptor is an object attribute with "binding behavior", one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__, __set__, and __delete__. If any of those methods are defined for an object, it is said to be a descriptor.

Вы можете легко преобразовать функцию в метод, просто используя различные вызовы__get__

>>> increment.__get__(None, type(None))
<function increment at 0x00000000027797C8>
>>> increment.__get__(o, type(o))
<bound method A.increment of <__main__.A object at 0x00000000027669B0>>

И это работает как шарм:

>>> o = A()
>>> increment.__get__(None, type(None))(o)
>>> o.count
1
>>> increment.__get__(o, type(o))()
>>> o.count
2

Вы можете легко добавить этиnewly bounded methods к объектам:

def increment(obj):
    obj.count += 1

def addition(obj, number):
    obj.count += number

class A(object):
    def __init__(self):
        self.count = 0

o = A()
o.inc = increment.__get__(o)
o.add = addition.__get__(o)
print(o.count) # 0
o.inc()
print(o.count) # 1
o.add(5)
print(o.count) # 6

Или создайте свой собственныйdescriptor что будет конвертироватьfunction вbound method:

class BoundMethod(object):
    def __init__(self, function):
        self.function = function

    def __get__(self, obj, objtype=None):
        print('Getting', obj, objtype)
        return self.function.__get__(obj, objtype)

class B(object):
    def __init__(self):
        self.count = 0

    inc = BoundMethod(increment)
    add = BoundMethod(addition)


o = B()
print(o.count) # 0
o.inc()
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 1
o.add(5) 
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 6

И вы также можете видеть, что это хорошо согласуется сfunction/bound method principles:

Class dictionaries store methods as functions. In a class definition, methods are written using def and lambda, the usual tools for creating functions. The only difference from regular functions is that the first argument is reserved for the object instance. By Python convention, the instance reference is called self but may be called this or any other variable name.

To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class.

А такжеfunctions становитсяbound method во время инициализации экземпляра:

>>> B.add
# Getting None <class '__main__.B'>
<function addition at 0x00000000025859C8>
>>> o.add
# Getting <__main__.B object at 0x00000000030B1128> <class '__main__.B'>
<bound method B.addition of <__main__.B object at 0x00000000030B1128>>
 06 дек. 2015 г., 15:30
Я думаю, что ваш ответ не то, что хочет ОП.
 17 дек. 2014 г., 17:38
Обычно встроенный тип в CPython используетmethod_descriptor, который связан какbuiltin_function_or_methodнапример,str.upper.__get__('a').__self__ == 'a',builtin_function_or_method сам по себеnot дескриптор. Последнее то, что создает Cython, поэтому OP ищет способ обернуть его в дескриптор, такой какpartialmethod (3.4+).

Ваш ответ на вопрос