在Python中更改函数实现

时间:2022-03-06 21:20:35

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.

我正在编写一个提供一个函数并需要初始化步骤的模块,但是由于我需要在第一次调用时初始化的某些限制,所以我在python中寻找适当的习惯用法,这将允许我摆脱条件。

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

我意识到我不能只在模块中使用名称并在运行时更改它们,因为使用模块导入功能的模块永远不会受到模块内函数= other_fun的影响。那么,是否有任何pythonic成语可以正确的方式做到这一点?

3 个解决方案

#1


8  

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.

这个想法很简单:你只需添加一个间接层。函数总是调用_function,但是_function首先指向first,然后永远指向do_the_thing。

test.py:

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

Running test.py yields

运行test.py产生

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

我的第一个想法是使用一个生成器,但是,正如Triptych指出的那样,如果使用生成器,则无法将args传递给该函数。所以...

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

这是一种使用协程的方法(与生成器不同,它允许您发送args以及从协程接收值):

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],))

#2


3  

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):
        # ...

#3


2  

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)

(需要根据您的需要进行改进)

#1


8  

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.

这个想法很简单:你只需添加一个间接层。函数总是调用_function,但是_function首先指向first,然后永远指向do_the_thing。

test.py:

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

Running test.py yields

运行test.py产生

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

我的第一个想法是使用一个生成器,但是,正如Triptych指出的那样,如果使用生成器,则无法将args传递给该函数。所以...

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

这是一种使用协程的方法(与生成器不同,它允许您发送args以及从协程接收值):

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],))

#2


3  

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):
        # ...

#3


2  

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)

(需要根据您的需要进行改进)