Модули, классы и пространства имен в Python - PullRequest
3 голосов
/ 08 декабря 2011

У меня есть следующий сценарий:

У меня есть абстрактные классы 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.

Вкратце: каковы хорошие альтернативы для эмуляции глобальных констант на уровне модулей, которые можно «перезаписать» в производных модулях (модулях, реализующих абстрактные классы, определенные в первом модуле)?

Ответы [ 4 ]

1 голос
/ 08 декабря 2011

удалена старая версия

вы можете попробовать создать подкласс __new__ для создания метакласса.Затем при создании класса вы можете получить модуль подкласса, просматривая предыдущие кадры с помощью модуля inspect модуля python std, получая новую константу здесь, если вы ее найдете, и исправляя атрибут класса производного класса.

На данный момент я не буду публиковать реализацию, потому что она не тривиальна для меня и опасна.

edit : добавлена ​​реализация

in 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

теперь вы можете повеселиться с вашими собственными правилами определения объема ...

1 голос
/ 08 декабря 2011

Вот несколько вещей, которые я мог бы предложить:

  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
    

    Концептуально, я бы предпочел этот метод, потому что он инкапсулирует свойства, которые вы обычно связываете с данным объектом (и только с теми) в этом объекте. Если скорость выполнения является важной целью, возможно, это не лучший дизайн.

Надеюсь, вы сможете перевести их в свое реальное приложение.

1 голос
/ 08 декабря 2011

Как насчет использования миксина?Вы можете определить (на основе вашего примера) классы для PlanetarySystemConstants и MolecularAggregateConstants, которые содержат ATTRACTION_CONSTANT, а затем использовать class PlanetarySystem(NBodyGroup, PlanetarySystemConstants) и class MolecularAggregate(NBodyGroup, MolecularAggregateConstants) для определения этих классов.

0 голосов
/ 08 декабря 2011

Вы должны использовать свойство для этого случая,

например.

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
...