I have the following scenario:
I have abstract classes A and B, and A uses B to perform some tasks. In both classes, there are some "constant" parameters (right now implemented as class attributes) to be set by concrete classes extending those abstract classes, and some of the parameters are shared (they should have the same value in a derived "suite" of classes SubA and SubB).
The problem I face here is with how namespaces are organized in Python. The ideal solution if Python had dynamic scoping would be to declare those parameters as module variables, and then when creating a new suite of extending classes I could just overwrite them in their new module. But (luckily, because for most cases that is safer and more convenient) Python does not work like that.
To put it in a more concrete context (not my actual problem, and of course not accurate nor realistic), imagine something like a nbody simulator with:
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
Then other module could implement a PlanetarySystem(NBodyGroup) and Planet(Body) setting ATTRACTION_CONSTANT to 6.67384E-11 and other module could implement MolecularAggregate(NBodyGroup) and Particle(Body) and set ATTRACTION_CONSTANT to 8.987E9.
In brief: what are good alternatives to emulate global constants at module level that can be "overwritten" in derived modules (modules that implement the abstract classes defined in the first module)?
-
Could you be more precise about your actual problem? What is the issue with putting one global ATTRACTION_CONSTANT definition in the module containing the derived classes and "overwriting" the value in the two subclasses with this global variable?Simon Bergot– Simon Bergot2011年12月08日 13:05:48 +00:00Commented Dec 8, 2011 at 13:05
4 Answers 4
How about using a mixin? You could define (based on your example) classes for PlanetarySystemConstants and MolecularAggregateConstants that hold the ATTRACTION_CONSTANT and then use class PlanetarySystem(NBodyGroup, PlanetarySystemConstants) and class MolecularAggregate(NBodyGroup, MolecularAggregateConstants) to define those classes.
2 Comments
Here are a few things I could suggest:
Link each body to its group, so that the body accesses the constant from the group when it calculates its force. For example:
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**2Pro: this automatically enforces the fact that bodies in the same group should exert the same kind of force. Con: semantically, you could argue that a body should exist independent of any groups in may be in.
Add a parameter to specify the type of force. This could be a value of an enumeration, for example.
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**2Conceptually, I would prefer this method because it encapsulates the properties that you typically associate with a given object (and only those) in that object. If speed of execution is an important goal, though, this may not be the best design.
Hopefully you can translate these to your actual application.
1 Comment
removed old version
you can try subclassing __new__ to create a metaclass. Then at the class creation, you can get the subclass module by looking in previous frames with the inspect module of python std, get your new constant here if you find one, and patch the class attribute of the derived class.
I won't post an implementation for the moment because it is non trivial for me, and kind of dangerous.
edit: added implementation
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
in B.py:
import A
MY_GLOBAL = 'derived'
print A.BASE().CLS_GLOBAL
now you can have fun with your own scoping rules ...
6 Comments
You should use property for this case,
eg.
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