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

Преобразовать встроенный тип функции в тип метода (в 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)

Как я могу выполнить эту же задачу в 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)

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

4b9b3361

Ответ 1

Первое, что нужно правильно назвать именами:

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

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

В общем, дескриптор - это атрибут объекта с привязкой поведение ", тот, чей доступ к атрибутам был переопределен методами в протоколе дескриптора. Этими методами являются __get__, __set__ и __delete__. Если какой-либо из этих методов определен для объекта, он называется дескриптором.

Вы можете легко преобразовать функцию в метод, просто используя различный вызов __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

Вы можете легко добавить эти новые ограниченные методы к объектам:

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

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

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

И вы также можете видеть, что это хорошо согласуется с принципами функций/привязанных методов:

Словарь классов хранит методы как функции. В определении класса методы записываются с использованием def и lambda, обычных инструментов для создания функций. Единственное отличие от обычных функций состоит в том, что первый аргумент зарезервирован для экземпляра объекта. По соглашению Python ссылка экземпляра называется self, но может быть вызвана этим или любым другим именем переменной.

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

И функции становятся связанным методом при инициализации экземпляра:

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

Ответ 2

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

import compiled_extension

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

def increment: return compiled_extension.increment()

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