Изменение реализации функции в Python - PullRequest
5 голосов
/ 05 ноября 2011

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

#with conditional
module.py
initialized = False
def function(*args):
   if not initialized: initialize()
   do_the_thing(*args)

Я бы хотел избавиться от этого условия с помощью чего-то вроде этого (это не работает):

#with no conditional
module.py
def function(*args):
   initialize()
   do_the_thing(*args)
   function = do_the_thing

Я понимаю, что не могу просто использовать имена в модуле и изменять их во время выполнения, потому что на модули, использующие from module import function, никогда не повлияет function=other_fun внутри модуля.
Итак, есть ли какая-нибудь питоническая идиома, которая могла бы сделать это правильно?

Ответы [ 3 ]

8 голосов
/ 05 ноября 2011

Ничего необычного способа (из методов, которые я публикую здесь, это, вероятно, лучший способ сделать это):

module.py:

def initialize():
    print('initialize')
def do_the_thing(args):
    print('doing things',args)
def function(args):
    _function(args)
def firsttime(args):
    global _function
    initialize()
    do_the_thing(args)
    _function=do_the_thing
_function=firsttime

Идея проста: вы просто добавляете слой косвенности.function всегда вызывает _function, но _function указывает сначала на firsttime, а затем навсегда на do_the_thing.

test.py:

from module import function
function(1)
function([2,3])

Запуск test.py приводит к

initialize
('doing things', 1)
('doing things', [2, 3])

Моей первой мыслью было использование генератора, но, как указывает Триптих, невозможно передать аргументы функции, если вы используете генератор.Итак ...

вот способ использования сопрограммы (который, в отличие от генератора, позволяет отправлять аргументы - и получать значения от - сопрограммы):

module.py:

def coroutine(func):
    # http://www.dabeaz.com/coroutines/index.html
    def start(*args,**kwargs):
        cr = func(*args,**kwargs)
        cr.next()
        return cr
    return start

def initialize():
    print('initialize')

def do_the_thing(*args, **kwargs):
    print('doing things', args, kwargs)
    return ('result', args)

@coroutine
def _function():
    args, kwargs = (yield)
    initialize()
    while True:
        args, kwargs = (yield do_the_thing(*args, **kwargs))
_function = _function().send
def function(*args, **kwargs):
    # This is purely to overcome the limitation that send can only accept 1 argument
    return _function((args,kwargs))

Запуск

print(function(1, x = 2))
print(function([2, 3]))

Выход

initialize
('doing things', (1,), {'x': 2})
('result', (1,))
('doing things', ([2, 3],), {})
('result', ([2, 3],))
3 голосов
/ 05 ноября 2011

Мое мнение: вы не должны этого делать.

Если вам нужна «функция» с «шагом инициализации» и нормальным рабочим режимом, вам нужен экземпляр класса. Не пытайтесь быть умным, будущие читатели вашего кода возненавидят вас за это:)

# module.py

class ThingDoer(object):
    def __init__(self):
        # initialize

    def do_the_thing(self, *args):
        # ...
2 голосов
/ 05 ноября 2011

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

import functools    

def initialize(initialize_function):
    def wrap(fn):
        fn.initialized = False
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            if not fn.initialized:
                initialize_function()
                fn.initialized = True
            return fn(*args, **kwargs)
        return wrapper
    return wrap

def initialize_first_fn():
    print('first function initalized')

def initialize_second_fn():
    print('second function initalized')

@initialize(initialize_first_fn)
def first_fn(*args):
   print(*args)

@initialize(initialize_second_fn)
def second_fn(*args):
   print(*args)


>>>first_fn('initialize', 'please')
first function initalized
initialize please
>>> first_fn('it works')
it works
>>> second_fn('initialize', 'please')
second function initalized
initialize please
>>> second_fn('it also works')
it also works

(необходимо улучшить в зависимости от ваших потребностей)

...