重构 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 重命名为 _BaseParamsBaseParamsBase 或类似名称:)

然后在模块层定义:

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)