Как я могу привязать функцию к экземпляру объекта, используя декоратор? - PullRequest
0 голосов
/ 04 января 2019

Я пытаюсь привязать функцию к экземпляру объекта. Например, у меня есть объект, и я пытаюсь привязать функцию к атрибуту условия. Дело в том, что я хочу сделать это с помощью декоратора:

class Transition(object):
    def __init__(self, enter, exit, condition=None):
        if not isinstance(enter, State) or not isinstance(exit, State):
            raise TypeError("Parameters should be an instance of the State class")
        self.enter = enter
        self.exit = exit
        self.condition = None
        self.effect = None

    def __repr__(self):
        print("Going from {} to {} with condition {} and effect {}".format(self.enter, self.exit, self.condition.__name__, self.effect.__name__)) 

    def __eq__(self, other):
        return self.enter == other.enter and self.exit == other.exit

    def is_enpoint(self, endpoints):
        """
        :parameter --> 'endpoints' is a tuple indicating where the transition flows(from, to) 
        :return --> boolean
        :description --> check if the given endpoints are valid 
        """
        return self.enter == endpoints[0] and self.exit == endpoints[1]

Затем я привязываю функцию к экземпляру объекта.

@bind
my_condition():
    return True

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

Редактировать 1:

f = Transition()
f1 = Transition()

@bind(f)
def condition1():
    return True

@bind(f1)
def condition2():
    return False

Экземпляр f должен иметь ссылку на функцию условие1 , а экземпляр f2 должен иметь ссылку на условие2 для атрибута условие

Ответы [ 3 ]

0 голосов
/ 04 января 2019

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

class Transition:
   ...

f = Transition

def bind(instance):
    def decorator(func):
        def wrapper (*args, **kwargs):
              return func(instance, *args, **kwargs)
        setattr(instance, func.__name__, wrapper)
        return wrapper
    return decorator

@bind(f)
def condition(self, ...):
    ...

Если вам нужен более плоский декоратор, вы можете использовать functools.partial - и тогда я также использую functools.wraps, так как хорошие декораторы должны использовать его в любом случае:

import functools

...

def bind(func, instance=None):
    if instance is None:
         return functools.partial(bind, instance=func)
    @functools.wraps(func)
    def wrapper(*args, **kw):
         return func(instance, *args, **kw)
    setattr(instance, func.__name__, wrapper)
    return wrapper

Это работает, потому что в Python, когда функция приписывается непосредственно к экземпляру, она ведет себя точно так же, как любой обычный атрибут: Python будет извлекать функцию, а затем вызывать ее без изменений - не изменяя ее на метод и не вставляя self аргумент). Мы делаем это, удерживая экземпляр как нелокальную переменную внутри кода декоратора.

0 голосов
/ 04 января 2019

Я думаю, что написал сообщение в блоге о подобной проблеме в прошлом. Он объясняет, как вы можете связать 2 атрибута класса Python вместе, чтобы обновление одного автоматически обновляло другое. Я проиллюстрировал 2 возможных подхода к этой проблеме. Первый объяснен в этом блоге с практическим примером. Возможно, вы также планируете использовать концепции, показанные в посте, для других целей.

0 голосов
/ 04 января 2019

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

class Foo:
    pass

def bind(fn):
    setattr(Foo, fn.__name__, fn)
    return fn

@bind
def bar(self):
    return 7

Использование:

>>> f = Foo()
>>> f.bar()
7

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

def bind(cls):
    def decorator(fn):
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
        return fn
    return decorator

Использование:

@bind(Foo)
def bar(self):
    return 7

@bind(Foo)
@property
def bat(self):
    import random
    return random.randint(1, 6)

>>> f = Foo()
>>> f.bar()
7
>>> f.bat
4

Наконец, решение, которое вы определенно должны не использовать, но я просто не мог остановиться:

from functools import partial

def bind(clsname, first, second=None):
    if second is None:  # class binding
        cls = globals()[clsname]
        fn = first
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
    else:  # instance binding
        self = first
        fn = second
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(self, name, partial(fn, self))

class BindableMeta(type):
    def __new__(cls, name, bases, dct):
        def inner(*args):
            return bind(name, *args)
        dct["bind"] = inner
        return type.__new__(cls, name, bases, dct)


class Bindable(metaclass=BindableMeta):
    pass

class Foo(Bindable):
    pass

f = Foo()
g = Foo()


@Foo.bind
def bar(self):
    return 5

@f.bind
def bat(self):
    return 5

@Foo.bind
@property
def prop(self):
    return 5


assert f.bar() == 5
assert f.bat() == 5
try:
    assert g.bat() == 5
except AttributeError:
    pass
else:
    raise RuntimeError("shouldn't work")
assert f.prop == 5
...