I am writing a module that provides one function and needs an initialization step, however due to certain restrictions I need to initialize on first call, so I am looking for the proper idiom in python that would allow me to get rid of the conditional.
#with conditional
module.py
initialized = False
def function(*args):
if not initialized: initialize()
do_the_thing(*args)
I'd like to get rid of that conditional with something like this(it does not work):
#with no conditional
module.py
def function(*args):
initialize()
do_the_thing(*args)
function = do_the_thing
I realize that I cannot just use names in the module and change them at runtime because modules using from module import function
will never be affected with a function=other_fun
inside the module.
So, is there any pythonic idiom that could do this the right way?
My take on this: you shouldn't do this.
In case you need a "function" which has "initialization step" and normal work mode, you need a class instance. Do not try to be clever, future readers of your code would hate you for that :)
# module.py
class ThingDoer(object):
def __init__(self):
# initialize
def do_the_thing(self, *args):
# ...
The nothing-fancy way (of the methods I post here, this is probably the best way to do it):
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
The idea is simple: you just add a layer of indirection. function
always calls _function
, but _function
points first at firsttime
, then forever after at do_the_thing
.
test.py:
from module import function
function(1)
function([2,3])
Running test.py yields
initialize
('doing things', 1)
('doing things', [2, 3])
My first thought was to use a generator, but, as Triptych points out, there is no way to pass args to the function if you use a generator. So...
here is a way using a coroutine (which, unlike a generator, allows you to send args to -- as well as receive values from -- the coroutine):
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))
Running
print(function(1, x = 2))
print(function([2, 3]))
yields
initialize
('doing things', (1,), {'x': 2})
('result', (1,))
('doing things', ([2, 3],), {})
('result', ([2, 3],))
You could also use a decorator, it's maybe more flexible if you have several functions to initialize:
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
(needs to be improved depending on your needs)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With