Можно ли заставить функции Python вести себя как экземпляры? - PullRequest
2 голосов
/ 11 августа 2010

Я понимаю, что функции могут иметь атрибуты. Поэтому я могу сделать следующее:

def myfunc():
    myfunc.attribute += 1
    print(myfunc.attribute)

myfunc.attribute = 1

Можно ли каким-либо образом заставить такую ​​функцию вести себя так, как если бы она была экземпляром? Например, я хотел бы иметь возможность сделать что-то вроде этого:

x = clever_wrapper(myfunc)
y = clever_wrapper(myfunc)
x.attribute = 5
y.attribute = 9
x()   # I want this to print 6 (from the 5 plus increment)
y()   # I want this to print 10 (from the 9 plus increment)

В существующем виде существует только один «экземпляр» функции, поэтому attribute существует только один раз. Изменение его либо x, либо y изменяет то же значение. Я бы хотел, чтобы у каждого из них был свой attribute. Можно ли вообще это сделать? Если да, можете ли вы привести простой, функциональный пример?

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

Основные ответы, кажется, говорят сделать что-то вроде этого:

class wrapper(object):
    def __init__(self, target):
        self.target = target
    def __call__(self, *args, **kwargs):
        return self.target(*args, **kwargs)

def test(a):
    return a + test.attribute

x = wrapper(test)
y = wrapper(test)
x.attribute = 2
y.attribute = 3
print(x.attribute) 
print(y.attribute)
print(x(3))
print(y(7))

Но это не работает. Возможно, я сделал это неправильно, но там написано, что test не имеет attribute. (Я предполагаю, что это потому, что wrapper на самом деле имеет атрибут.)

Причина, по которой мне это нужно, в том, что у меня есть библиотека, которая ожидает функцию с определенной сигнатурой. Можно поместить эти функции в конвейер, чтобы они вызывались по порядку. Я хотел бы передать несколько версий одной и той же функции, но изменить их поведение в зависимости от значения атрибута. Поэтому я хотел бы иметь возможность добавлять x и y в конвейер, в отличие от необходимости реализовывать функцию test1 и функцию test2, которые оба выполняют почти одно и то же (за исключением значение атрибута).

Ответы [ 5 ]

7 голосов
/ 11 августа 2010

Вы можете создать класс с помощью метода __call__, который достиг бы аналогичной цели.

Изменить для ясности: Вместо того, чтобы myfunc сделать функцию, сделайте ее вызываемойучебный класс.Он ходит как функция и крякает как функция, но может иметь члены как класс.

2 голосов
/ 11 августа 2010

Более хороший способ:

def funfactory( attribute ):
    def func( *args, **kwargs ):
        # stuff
        print( attribute )
        # more stuff

    return func

x = funfactory( 1 )
y = funfactory( 2 )

x( ) # 1
y( ) # 2

Это работает, потому что функции являются замыканиями, поэтому они будут захватывать все локальные переменные в своей области видимости;это приводит к передаче копии attribute с помощью функции.

1 голос
/ 11 августа 2010
class Callable(object):
    def __init__(self, x):
        self.x = x

    def __call__(self):
        self.x += 1
        print self.x

>> c1 = Callable(5)
>> c2 = Callable(20)
>> c1()
6
>> c1()
7
>> c2()
21
>> c2()
22
0 голосов
/ 11 августа 2010

Генератор может быть альтернативным решением здесь:

def incgen(init):
    while True:
        init += 1
        print init
        yield

x = incgen(5)
y = incgen(9)

x.next() # prints 6
y.next() # prints 10
y.next() # prints 11
x.next() # prints 7

Вы не можете копаться в генераторе и манипулировать данными.

0 голосов
/ 11 августа 2010
#!/usr/bin/env python
# encoding: utf-8

class Callable(object):
    attribute = 0

    def __call__(self, *args, **kwargs):
        return self.attribute

def main():
    c = Callable()
    c.attribute += 1
    print c()


if __name__ == '__main__':
    main()
...