从另一个模块修改一个模块的属性是不好的做法吗?

时间:2020-08-27 11:23:02

标签: python

我想定义一堆配置变量,可以将其导入项目中的所有模块中。这些变量的值在运行期间将是恒定的,但在运行之前未知。它们取决于输入。通常,我会在顶层模块中定义一个 dict ,然后将其传递给其他模块的所有函数和类。但是,我认为简单地创建一个空白的 config.py 模块可能会更清洁,该模块将由顶部模块动态填充配置变量:

# top.py
import config
config.x = x

# config.py
x = None

# other.py
import config
print(config.x)

我喜欢这种方法,因为我不必在其他模块中将参数另存为类的属性。这对我来说很有意义,因为参数本身无法描述类。

这行得通,但是被认为是不好的做法吗?

2 个答案:

答案 0 :(得分:1)

这样的问题可能会引起争议。但是我通常会说是,这是“错误的做法”,因为更改的范围和影响确实变得模糊了。请注意,您描述的用例实际上与共享配置无关,而与程序功能,对象,交换数据的模块的不同部分有关,因此,它是(meta)global变量的变体。

读取通用配置值可能会很好,但是在此过程中进行更改...您可能无法跟踪模块导入/值被修改时发生的位置以及顺序。例如,假设config.py和两个模块m1.py

import config
print(config.x)
config.x=1

m2.py

import config
print(config.x)
config.x=2

和一个main.py就可以做到:

import m1
import m2
import config
print(config.x)

或:

import m2
import m1
import config
print(config.x)

在每个模块以及实际上在其他模块(此处包括main.py)中找到配置的状态取决于导入的顺序以及何时分配值的人。即使对于完全由您控制的程序,这也可能很快变得令人困惑(错误的来源)。

对于运行时数据以及在对象和模块之间传递信息(您的示例确实是这样,而不是在模块之间预定义和共享的配置),我建议您研究一下可能在自定义状态(config)对象中描述的信息,并通过适当的接口传递它。但是实际上可能只需要一个函数/方法参数即可。确切的形式取决于您要实现的目标以及总体设计是什么。


在您的示例中,other.pytop.py之前被调用或导入时的行为有所不同,在一个最小的示例中,它似乎仍然显而易见且易于管理,但实际上并不是一个非常合理的设计。任何阅读该代码的人(包括将来的您)都应该能够遵循其逻辑,而此IMO破坏了它的流程。

关于您所描述的内容的最琐碎(和过程性的)示例,现在我希望可以更好地理解:other.py重新创建当前行为:

def do_stuff(value):
    print(value)  # We did something useful here

if __name__ == "__main__":
   do_stuff(None)  # Could also use config with defaults

您的top.py大概是切入点,并编排导入和执行操作:

import other
x = get_the_value()
other.do_stuff(x)

您当然可以引入一个接口来配置do_stuffdict或自定义类,即使在config.py中使用默认实现也是如此:

class Params:
    def __init__(self, x=None):
        self.x = x

和您的other.py

def do_stuff(params=config.Params()):
    print(params.x)  # We did something useful here

您可以在top.py上使用:

params = config.Params(get_the_value())
other.do_stuff(params)

但是您还可以使用任何特定于用例的价值来源:

class TopParams:
    def __init__(self, url):
        self.x = get_value_from_url(url)

params = TopParams("https://example.com/value-source")
other.do_stuff(params)

x甚至可以是一个property,您每次访问它时都会检索它……或者在需要时懒惰然后缓存……同样,这实际上取决于您的需求要做。

答案 1 :(得分:0)

“从另一个模块修改一个模块的属性是否是错误的做法?”

这被认为是不良行为-违反了得墨meter耳的法律,这实际上意味着“与朋友交谈,而不是与陌生人交谈”。

对象应公开行为和功能,但应隐藏数据。 DataStructures应该暴露数据,但不应具有任何方法(公开的方法)。 demeter的定律不适用于此类DataStructures。 OOP Purists可能会使用setter和getter来涵盖此类DataStructures,但在Python中确实没有任何价值。

关于这方面的文献很多,例如:https://en.wikipedia.org/wiki/Law_of_Demeter

当然,必须阅读:Robert C. Martin(鲍勃叔叔)的“ Clean Code”,也可以在YouTube上查看。

对于过程编程,将数据保留在没有任何(公开)方法的DataStructure中是完全正常的。

程序中的过程与该数据一起使用。考虑使用模块attrs,请参阅:https://www.attrs.org/en/stable/,以轻松创建此类。

我保留配置的首选方法是(此处不使用attrs):

# conf_xy.py
"""
config is code - so why use damned parsers, textfiles, xml, yaml, toml and all that
if You just can use testable code as config that can deliver the correct types, etc.
as well as hinting in Your favorite IDE ?
Here, for demonstration without using attrs package - usually I use attrs (read the docs)

"""

class ConfXY(object):
    
    def __init__(self) -> None:
        self.x: int = 1
        self.z: float = get_z_from_input()
        ...

conf_xy=ConfXY()

# other.py

from conf_xy import conf_xy

...

y = conf_xy.x * 2

... 

相关问题