Python中的模块、类和命名空间

3
我有以下情景:
我有抽象类A和B,A使用B执行一些任务。在这两个类中,都有一些“常量”参数(目前实现为类属性)需要由扩展这些抽象类的具体类设置,其中一些参数是共享的(它们应该在派生的“套件”类SubA和SubB中具有相同的值)。
我在这里面临的问题是Python中命名空间的组织方式。如果Python具有动态作用域,则理想的解决方案是将这些参数声明为模块变量,然后在创建新的扩展类套件时,可以在其新模块中轻松地覆盖它们。但是(幸运的是,因为对于大多数情况而言,这更加安全和便捷),Python并不是这样工作的。
更具体地说(不是我的实际问题,当然也不准确或现实),可以想象像nbody模拟器一样的东西:
 ATTRACTION_CONSTANT = NotImplemented # could be G or a Ke for example

 class NbodyGroup(object):
     def __init__(self):
         self.bodies = []

     def step(self):
         for a in self.bodies:
             for b in self.bodies:
                 f = ATTRACTION_CONSTANT * a.var * b.var / distance(a, b)**2
                 ...

 class Body(object):
     def calculate_field_at_surface(self):
         return ATTRACTION_CONSTANT * self.var / self.r**2

然后,其他模块可以实现一个PlanetarySystem(NBodyGroup)Planet(Body),将ATTRACTION_CONSTANT设置为6.67384E-11,而其他模块可以实现MolecularAggregate(NBodyGroup)Particle(Body),并将ATTRACTION_CONSTANT设置为8.987E9
简而言之:在模块级别上模拟全局常量的良好替代方案是什么,这些常量可以在衍生模块中“重写”(即实现第一个模块中定义的抽象类的模块)?

你能更明确地描述你的实际问题吗?在包含派生类的模块中放置一个全局 ATTRACTION_CONSTANT 定义,并使用此全局变量在两个子类中“覆盖”该值,这样做有什么问题吗? - Simon Bergot
4个回答

1

使用mixin怎么样?您可以定义(基于您的示例)PlanetarySystemConstantsMolecularAggregateConstants类,它们保存ATTRACTION_CONSTANT,然后使用class PlanetarySystem(NBodyGroup, PlanetarySystemConstants)class MolecularAggregate(NBodyGroup, MolecularAggregateConstants)来定义这些类。


到目前为止,这是唯一一个改善当前情况的答案,但我更愿意避免混合(多重继承是邪恶的!) - fortran
1
嘿,我不太同意。我会说它更像是:“多重继承可能会有问题”。如果你使用它们的原因很具体、狭窄,那么它们就有用处。 - Glenn

1

这里有几个我可以建议的事情:

  1. 将每个物体与其所属的组链接起来,这样当计算力时,物体就可以从组中访问常量。例如:

    class NbodyGroup(object):
        def __init__(self, constant):
            self.bodies = []
            self.constant = constant
    
        def step(self):
            for a in self.bodies:
                for b in self.bodies:
                    f = self.constant * a.var * b.var / distance(a, b)**2
                    ...
    
    class Body(object):
        def __init__(self, group):
            self.group = group
        def calculate_field_at_surface(self):
            return self.group.constant * self.var / self.r**2
    

    优点:这样可以自动强制要求同一组中的物体应该施加相同类型的力。缺点:从语义上讲,你可以认为一个物体应该独立于它所在的任何组。

  2. 添加一个参数来指定力的类型。例如,这可以是枚举值。

    class Force(object):
        def __init__(self, constant):
            self.constant = constant
    GRAVITY = Force(6.67e-11)
    ELECTRIC = Force(8.99e9)
    
    class NbodyGroup(object):
        def __init__(self, force):
            self.bodies = []
            self.force = force
    
        def step(self):
            for a in self.bodies:
                for b in self.bodies:
                    f = self.force.constant * a.charge(self.force) \
                          * b.charge(self.force) / distance(a, b)**2
                    ...
    
    class Body(object):
        def __init__(self, charges, r):
            # charges = {GRAVITY: mass_value, ELECTRIC: electric_charge_value}
            self.charges = charges
            ...
        def charge(self, force):
            return self.charges.get(force, 0)
        def calculate_field_at_surface(self, force):
            return force.constant * self.charge(force) / self.r**2
    

    从概念上讲,我更喜欢这种方法,因为它将通常与给定对象相关联的属性(仅限于这些属性)封装在该对象中。但如果执行速度是一个重要目标,那么这可能不是最佳设计。

希望你能将这些翻译成你的实际应用程序。


我会放弃第一种方法,因为它存在循环引用的问题... 添加参数是一个不错的选择,但很快就会变得非常冗长 :( - fortran

1

删除旧版本

您可以尝试子类化__new__来创建元类。然后在类创建时,您可以使用Python标准库的inspect模块查看前面的帧以获取子类模块,在此处获取新常量(如果找到),并修补派生类的类属性。

我暂时不会发布实现,因为对我来说非常棘手,而且有点危险。

编辑:添加实现

A.py中:

import inspect
MY_GLOBAL = 'base module'
class BASE(object):
    def __new__(cls, *args, **kwargs):
        clsObj = super(BASE, cls).__new__(cls, *args, **kwargs)
        clsObj.CLS_GLOBAL = inspect.stack()[-1][0].f_globals['MY_GLOBAL']
        return clsObj

B.py 中:

import A
MY_GLOBAL = 'derived'
print A.BASE().CLS_GLOBAL

现在你可以享受自己的作用域规则了...


这就是我目前正在做的事情,但我不喜欢它,这就是为什么我发布了这个问题,因为我想要删除重复的常量或跨类引用。 - fortran
不,ATTRACTION_CONSTANT变量在我的版本中是在类级别上定义的。请再次阅读您的问题和我的回答。 - Simon Bergot
现在实现为类属性;如果Python支持动态作用域,那么样例场景中的代码就是我想要的。 - fortran
哎呀,确实是这样。对此很抱歉。那么你的问题是不想在NbodyGroup和PlanetarySystem中有重复的ATTRACTION_CONSTANT定义? - Simon Bergot
准确地说,通常可以理解为如何“扩展”或专门化整个模块... - fortran
删除了第一个答案,并用一个疯狂的想法替换了它。 - Simon Bergot

0

在这种情况下,您应该使用属性

例如:

class NbodyGroup(object):
    @property
    def ATTRACTION_CONSTANT(self): 
        return None
    ...
    def step(self):
        for a in self.bodies:
            for b in self.bodies:
                f = self.ATTRACTION_CONSTANT * a.var * b.var / distance(a, b)**2

class PlanetarySystem(NBodyGroup):
    @property
    def ATTRACTION_CONSTANT(self): 
        return 6.67384E-11

我不认为这会改善当前的情况。 - fortran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接