重构 Python class 以在不显着改变调用模式的情况下采用动态参数
Refactoring a Python class to take a dynamic parameter without changing calling pattern dramatically
我在现有代码库的 base_params.py
模块中有一个 Python class,它看起来像这样:
import datetime
class BaseParams:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
来电者这样使用它:
from base_params import BaseParams as params
print(params.PATH1)
现在,我想用运行时动态指定的值(例如通过 CLI 参数)替换 TIMESTAMP
值。
有没有一种方法可以在 Python 中做到这一点,而不需要我的调用者以戏剧性的方式重构他们的代码?这目前让我感到困惑,因为 class BaseParams
的内容在 'compile' 时间执行,因此没有机会按当前结构传递动态值。在我现有的一些代码中,这个对象在 'compile' 时被视为“完全准备好”,例如,它的值用作函数参数默认值:
def some_function(value1, value2=params.PATH1):
...
我想知道是否有一些方法可以使用 Python 模块 and/or 滥用 Python 的 __special_methods__
来使这个现有的代码模式或多或少地工作照原样,没有进行某种更深入的重构。
我目前的期望是“这实际上是不可能的”,因为最后一个示例在函数签名中指定了默认值。但我认为我应该咨询 Python 向导,看看是否有合适的 Pythonic 方法解决这个问题。
是的,您只需要确保在定义 class 之前以及在定义任何使用 class 的属性作为默认参数的函数之前解析命令行参数(但应该已经是这样了)。
(为简单起见,使用sys.argv
。最好使用实参解析器,例如argparse
)
import datetime
import sys
class BaseParams:
try:
TIMESTAMP = sys.argv[1]
except IndexError:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
print(BaseParams.TIMESTAMP)
$ python main.py dummy-argument-from-cli
产出
dummy-argument-from-cli
而
$ python main.py
产出
2021-06-26 02:32:12.882601
在定义 class 之后,您仍然可以完全替换 class 属性的值:
BaseParams.TIMESTAMP = <whatever>
当然还有一些更“神奇”的事情你可以做,比如某种 class 工厂。从 Python 3.7 开始,您还可以利用模块 __getattr__
为 BaseParams
class (PEP 562)
创建一种工厂
在 base_params.py
中,您可以将 BaseParams
重命名为 _BaseParams
或 BaseParamsBase
或类似名称:)
然后在模块层定义:
def __getattr__(attr):
if attr == 'BaseParams':
params = ... # whatever code you need to determine class attributes for BaseParams
return type('BaseParams', (_BaseParams,), params)
raise AttributeError(attr)
我在现有代码库的 base_params.py
模块中有一个 Python class,它看起来像这样:
import datetime
class BaseParams:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
来电者这样使用它:
from base_params import BaseParams as params
print(params.PATH1)
现在,我想用运行时动态指定的值(例如通过 CLI 参数)替换 TIMESTAMP
值。
有没有一种方法可以在 Python 中做到这一点,而不需要我的调用者以戏剧性的方式重构他们的代码?这目前让我感到困惑,因为 class BaseParams
的内容在 'compile' 时间执行,因此没有机会按当前结构传递动态值。在我现有的一些代码中,这个对象在 'compile' 时被视为“完全准备好”,例如,它的值用作函数参数默认值:
def some_function(value1, value2=params.PATH1):
...
我想知道是否有一些方法可以使用 Python 模块 and/or 滥用 Python 的 __special_methods__
来使这个现有的代码模式或多或少地工作照原样,没有进行某种更深入的重构。
我目前的期望是“这实际上是不可能的”,因为最后一个示例在函数签名中指定了默认值。但我认为我应该咨询 Python 向导,看看是否有合适的 Pythonic 方法解决这个问题。
是的,您只需要确保在定义 class 之前以及在定义任何使用 class 的属性作为默认参数的函数之前解析命令行参数(但应该已经是这样了)。
(为简单起见,使用sys.argv
。最好使用实参解析器,例如argparse
)
import datetime
import sys
class BaseParams:
try:
TIMESTAMP = sys.argv[1]
except IndexError:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
print(BaseParams.TIMESTAMP)
$ python main.py dummy-argument-from-cli
产出
dummy-argument-from-cli
而
$ python main.py
产出
2021-06-26 02:32:12.882601
在定义 class 之后,您仍然可以完全替换 class 属性的值:
BaseParams.TIMESTAMP = <whatever>
当然还有一些更“神奇”的事情你可以做,比如某种 class 工厂。从 Python 3.7 开始,您还可以利用模块 __getattr__
为 BaseParams
class (PEP 562)
在 base_params.py
中,您可以将 BaseParams
重命名为 _BaseParams
或 BaseParamsBase
或类似名称:)
然后在模块层定义:
def __getattr__(attr):
if attr == 'BaseParams':
params = ... # whatever code you need to determine class attributes for BaseParams
return type('BaseParams', (_BaseParams,), params)
raise AttributeError(attr)