【问题标题】:Modules, classes and namespaces in PythonPython 中的模块、类和命名空间
【发布时间】:2012-01-15 19:23:54
【问题描述】:

我有以下场景:

我有抽象类AB,而A 使用B 来执行一些任务。在这两个类中,有一些“常量”参数(现在实现为类属性)由扩展这些抽象类的具体类设置,并且一些参数是共享的(它们在派生的“套件”中应该具有相同的值SubASubB 的类)。

我在这里面临的问题是命名空间在 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 定义放在包含派生类的模块中并用这个全局变量“覆盖”两个子类中的值有什么问题?

标签: python class module namespaces


【解决方案1】:

使用 mixin 怎么样?您可以(根据您的示例)为包含 ATTRACTION_CONSTANTPlanetarySystemConstantsMolecularAggregateConstants 定义类,然后使用 class PlanetarySystem(NBodyGroup, PlanetarySystemConstants)class MolecularAggregate(NBodyGroup, MolecularAggregateConstants) 定义这些类。

【讨论】:

  • +1 到目前为止,这是改善当前情况的唯一答案,但我更愿意远离混合(多重继承是邪恶的!)
  • 嘿。我不得不不同意。我会说它更像:“多重继承可能是邪恶的”。如果您使用它们有一个特定的、狭隘的原因,那么它们就是有目的的。
【解决方案2】:

我可以建议以下几点:

  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
    

    Pro:这会自动强制执行同一组中的物体应该施加相同类型的力这一事实。缺点:从语义上讲,你可以争辩说一个主体应该独立于可能存在的任何组而存在。

  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
    

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

希望您可以将这些转换为您的实际应用程序。

【讨论】:

  • 我会因为循环引用而放弃第一种方法......添加参数是一个不错的选择,但它会很快变得非常冗长:(
【解决方案3】:

已移除旧版本

您可以尝试子类化__new__ 来创建元类。然后在创建类时,您可以通过使用 python std 的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

现在您可以享受自己的范围规则带来的乐趣...

【讨论】:

  • 这是我目前正在做的,我不喜欢它,这就是我发布问题的原因,因为我想删除重复的常量或跨类引用
  • 不,ATTRACTION_CONSTANT 变量在我的版本中是在类级别定义的。再次阅读您的问题和我的回答。
  • 再次阅读完整的问题文本:现在实现为类属性;如果 Python 支持动态范围,那么示例场景中的代码就是我想要的。
  • 确实糟糕。非常遗憾。所以你的问题是你不想在 NbodyGroup 和 PlanetarySystem 中有重复的 ATTRACTION_CONSTANT 定义?
  • 没错!一般来说,它可以理解为如何将模块作为一个整体“扩展”或专门化......
【解决方案4】:

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

例如。

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

【讨论】:

  • 我看不出这如何改善目前的情况
猜你喜欢
  • 2014-01-26
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
  • 2019-03-14
  • 1970-01-01
  • 2016-05-21
  • 2020-07-14
  • 1970-01-01
相关资源
最近更新 更多