Python Class update parent variables by subclass - python

With my limited understanding of #property,#setter, and #getter, I came up with following code.
class BitCounts:
sign_bit = 0
exponent_bits = 0
mantissa_bits = 0
_total_bits = 0
#property
def total_bits(self):
return self._total_bits
#total_bits.setter
def total_bits(self):
self._total_bits = self.sign_bit + self.exponent_bits + self.mantissa_bits
class Single(BitCounts):
sign_bit = 1
offset = 0x7F
exponent_bits = 8
mantissa_bits = 23
_total_bits = BitCounts.total_bits
class Double(BitCounts):
sign_bit = 1
offset = 0x400
exponent_bits = 11
mantissa_bits = 52
_total_bits = BitCounts.total_bits
My intention is to use the subclass Single and Double in other functions as a set of options like so, for example:
def some_function(option=Single):
print("exponent bit counts are: %d", option.exponent_bits)
print("mantissa bit counts are: %d", option.mantissa_bits)
print("total bit counts are: %d", option.total_bits)
I would like total_bits to be automatically recalculated using values from subclass Single or Double.
I am trying to avoid extra functions to perform the calculation at subclass level.
With above codes, by calling Single.total_bits, or Double.total_bits, I am only getting a message saying <property object at 0x000002258DF7CB30>, what did I do wrong, and how can I fix it?

The way you are using subclasses with hard-coded static values suggests these should be instances not subclasses. This is also suggested by your temptation to use self even though you haven't made any instances. self refers to a particular instance.
Also, setters typically take a value as an argument. You don't have that in your setter because total_bits is completely dependent on other values. As such you should just move your setter calculation to the getter and return the result of the calculation.
Consider:
class BitCounts:
def __init__(self, sign,offset, exponent, mantissa):
self.sign_bit = sign
self.offset = offset
self.exponent_bits = exponent
self.mantissa_bits = mantissa
#property
def total_bits(self):
return self.sign_bit + self.exponent_bits + self.mantissa_bits
# now make two instances:
single = BitCounts(1, 0x7F, 8, 23 )
double = BitCounts(1, 0x400, 11, 52)
print(single.total_bits)
# 32
print(double.total_bits)
# 64

You can use:
class Single(BitCounts):
sign_bit = 1
offset = 0x7F
exponent_bits = 8
mantissa_bits = 23
_total_bits = BitCounts.total_bits
def get_total_bits(self):
# Update values here, example below
self._total_bits = self._total_bits + 1
return self._total_bits
Then call:
option = Single()
option.get_total_bits()

The problem here is that you are trying to call an instance method from a class.
class A:
#property
def foo(self):
return 1
print(A.foo) # prints an object of function "foo"
print(A().foo) # prints "1"
To accomplish this, at least from my knowledge you need to use a metaclass similar to what they do here: Per-class #property decorator in Python

Related

Call a method on a new object from a variable

I have a class containing a list and some boolean methods.
class Cls:
data = [] // populated in __init__()
def flag1(self):
def flag2(self):
def flag3(self): # these all return booleans, based on the data
I want to create a higher level function, taking a parameter one of the flags, manipulating the data in a number of ways, applying the flag to the new data, and counting the number of results.
Something like:
def hof(self, fn):
count = 0
for i in range(1, 10):
new_obj = Cls(self.data+i)
if new_obj.fn():
count +=1
Is there any way to accomplish this without turning all the flags into static methods ?
===
Edit: Made it work, in a very hackish way:
class Cls:
data = []
def __init__(self):
self.data = value
class flag1(self):
return True
class flag2(self):
return False
# The hackish part
flag_dict = {
1: flag1,
2: flag2,
}
def hof(self, flag):
count = 0
for i in range(1,10):
new_obj = Cls(self.data + [i])
if self.flag_dict[flag](new_obj):
count +=1
return count
But it seems like a hack, and it's not quite understandable. Could someone point to a better way ?
Thanks.
You should be able to just pass the methods into the function like instance.hof(Cls.flag1), and internally, write it as if fn(new_obj):, with no need to make it a staticmethod.

calling a method inside a class-Python

class Time:
def __init__(self,x,y,z):
self.hour=x
self.minute=y
self.second=z
def __str__(self):
return "({:02d}:{:02d}:{:02d})".format(self.hour, self.minute, self.second)
def time_to_int(time):
minutes=time.hour*60+time.minute
seconds=minutes*60+time.second
return seconds
def int_to_time(seconds):
time=Time()
minutes,time.second=divmod(seconds,60)
time.hour,time.minute=divmod(minutes,60)
return time
def add_time(t1,t2):
seconds=time_to_int(t1)+time_to_int(t2)
return int_to_time(seconds)
start=Time(9,45,00)
running=Time(1,35,00)
done=add_time(start,running)
print(done)
I am new to python and i've been doing some practice lately.I came across a question and i've written the code for the same.But I am repeatedly getting an error: "add_time is not defined". I tried defining a main() method but then it doesn't print anything.Please help.
You haven't created an object to the above class.
Any function/method inside a class can only be accessed by an object of that class .For more information on the fundamentals of Object Oriented Programming, please check this page.
Meanwhile for this to work, define your class in the following way :
class Time:
def __init__(self,x=None,y=None,z=None):
self.hour=x
self.minute=y
self.second=z
def __str__(self):
return "({:02d}:{:02d}:{:02d})".format(self.hour, self.minute, self.second)
def time_to_int(time):
minutes=time.hour*60+time.minute
seconds=minutes*60+time.second
return seconds
def int_to_time(seconds):
time=Time()
minutes,time.second=divmod(seconds,60)
time.hour,time.minute=divmod(minutes,60)
return time
def add_time(t1,t2):
seconds=time_to_int(t1)+time_to_int(t2)
return int_to_time(seconds)
and outside the class block, write the following lines :
TimeObject = Time()
start=Time(9,45,00)
running=Time(1,35,00)
TimeObject.add_time(start,running)
print "done"
I however suggest you to write the add_time function outside the class because you are passing the objects to the class as the parameters to the function within the same class and it is considered as a bad design in object oriented programming.
Hope it helps. Cheers!
This works fine for me as long as you specified 3 args in your constructor
def int_to_time(seconds):
time=Time(0,0,0) # just set your 3 positionals args here
minutes,time.second=divmod(seconds,60)
time.hour,time.minute=divmod(minutes,60)
return time
Another way to avoid it could be:
class Time:
def __init__(self,x=0,y=0,z=0):
self.hour=x
self.minute=y
self.second=z
If you want to add your functions to your class (such as time_to_int, int_to_time or even add_time) then you will need to indent with one more level of 4 spaces and add self to your method parameters
Hii Mathers25,
I solve your problem try this below code to get the best output,
class TimeClass:
def __init__(self,x,y,z):
self.hour = x
self.minute = y
self.second = z
def __str__(self):
return "({:02d}:{:02d}:{:02d})".format(self.hour, self.minute, self.second)
def time_to_int(self,time):
minutes = (time.hour * 60) + time.minute
seconds = (minutes * 60) + time.second
return seconds
def int_to_time(self,seconds):
time = TimeClass(0,0,0)
minutes,time.second=divmod(seconds,60)
time.hour,time.minute=divmod(minutes,60)
return time
def add_time(self,t1,t2):
seconds = self.time_to_int(t1) + self.time_to_int(t2)
# Call method int_to_time() using self keyword.
return self.int_to_time(seconds)
# First time object create that time set value is 0 of hour,minute and second
TimeObject = TimeClass(0,0,0)
# After create second object
start=TimeClass(9,45,00)
# After create thired Object
running=TimeClass(1,35,00)
# Store the value which return by add_time()
done = TimeObject.add_time(start,running)
# Display the value of done variable
print(done)
class Employee:
def __init__(self):
self.wage = 0
self.hours_worked = 0
def calculate_pay(self):
return self.wage * self.hours_worked
alice = Employee()
alice.wage = 9.25
alice.hours_worked = 35
print('Alice:\n Net pay: {:.2f}'.format(alice.calculate_pay()))
barbara = Employee()
barbara.wage = 11.50
barbara.hours_worked = 20
print('Barbara:\n Net pay: {:.2f}'.format(barbara.calculate_pay()))
Works for me:
class C:
def f(a, b):
return a + b
x = f(1,2)
print(C.x)
but you should not do such things. Code in class-level is executing when class is "creating", usually you want static methods or class methods (decorated with #staticmethod or #classmethod) and execute code in some function/instantiated class. Also you can execute it on top (module) level if this is the simple script. Your snippet is "bad practice": class level (i'm talking about indentation) is for declarations, not for execution of something. On class-level is normal to execute code which is analogue of C macros: for example, to call decorator, to transform some method/attribute/etc - static things which are "pure" functions!

Access class variables with same function

I want to create a function within a class that can access two different members with the same function. For example in the code below, I want both of the lines below to use the 'apply' function on different variables in the class
print(state.apply(rate))
print(state.apply(wage))
I had thought if I put in a dummy variable in the function definition (called exposure), it would replace it with the variables passed to the function (rate and wage in the example below). What is the correct way of doing this in python 3?
class State():
def __init__(self):
self.rate = 0
self.wage = 0
def apply(self, exposure):
self.exposure = self.exposure - 1
return self.exposure
state = State()
rate = State.rate
wage = State.wage
print(state.apply(rate))
print(state.apply(wage))
EDIT: I had made a typo where I had State instead of state in each print statement. I have now corrected this
This would be the only way:
class State:
def __init__ (self):
self.rate = 0
self.wage = 0
def apply (self, exposure):
setattr(self, exposure, getattr(self, exposure) - 1)
return getattr(self, exposure)
>>> state = State()
>>> print(state.apply('rate'))
-1
>>> print(state.apply('wage'))
-1
>>> print(state.apply('wage'))
-2
Note that those are instance variables, so you cannot access them using the type, State, but only using the object, state.
However, I would say, that whatever you are trying, you’re doing it wrong. If you describe your actual problem, we may be able to suggest a way better solution for it instead.

Using class to define multiple variables in python

I'm still new to python and this is probably going be one of those (stupid) boring questions. However, any help will be much appreciated. I'm programing something that involves many variables and I've decided to use a class to encapsulate all variables (hopefully making it easier to "read" for me as time passes), but it's not working as I thought it will. So, without further ado here is a part of the class that captures the gist.
import numpy as np
class variable:
def __init__(self, length):
self.length = length # time length`
def state_dynamic(self):
length = self.length
return np.zeros((2, np.size(length)))
def state_static(self):
length = self.length
return np.zeros((2, np.size(length)))
def control_dynamic(self):
length = self.length
return np.zeros((2, np.size(length)))
def control_static(self):
length = self.length
return np.zeros((2, np.size(length)))
def scheduling(self):
length = self.length
return np.zeros(np.size(length))
def disturbance(self):
length = self.length
dummy = np.random.normal(0., 0.1, np.size(length))
for i in range(20):
dummy[i+40] = np.random.normal(0., 0.01) + 1.
dummy[80:100] = 0.
return dummy
I've also tried this one:
import numpy as np
class variable:
def __init__(self, type_1, type_2, length):
self.type_1 = type_1 # belongs to set {state, control, scheduling, disturbance}
self.type_2 = type_2 # belongs to set {static, dynamic, none}
self.length = length # time length
def type_v(self):
type_1 = self.type_1
type_2 = self.type_2
length = self.length
if type_1 == 'state' and type_2 == 'dynamic':
return np.zeros((2, np.size(length)))
elif type_1 == 'state' and type_2 == 'static':
return np.zeros((2, np.size(length)))
elif type_1 == 'control' and type_2 == 'dynamic':
return np.zeros((2, np.size(length)))
elif type_1 == 'control' and type_2 == 'static':
return np.zeros((2, np.size(length)))
elif type_1 == 'scheduling' and type_2 == 'none':
return np.zeros(np.size(length))
elif type_1 == 'disturbance' and type_2 == 'none':
dummy = np.random.normal(0., 0.1, np.size(length))
for i in range(20):
dummy[i+40] = np.random.normal(0., 0.01) + 1.
dummy[80:100] = 0.
return dummy
Now, using the first one (the outcome is the same for the second class as well), when I write the following, say:
In [2]: time = np.linspace(0,10,100)
In [5]: v = variable(time)
In [6]: v1 = v.state_dynamic
In [7]: v1.size
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/home/<ipython-input-7-e6a5d17aeb75> in <module>()
----> 1 v1.size
AttributeError: 'function' object has no attribute 'size'
In [8]: v2 = variable(np.size(time)).state_dynamic
In [9]: v2
Out[9]: <bound method variable.state_dynamic of <__main__.variable instance at 0x3ad0a28>>
In [10]: v1[0,0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/<ipython-input-10-092bc2b9f982> in <module>()
----> 1 v1[0,0]
TypeError: 'instancemethod' object has no attribute '__getitem__'
I was hoping that by writing
variable(length).state_dynamic
I'll access
np.zeros((2, np.size(length)))
Anyway, if I made something utterly stupid please let me know :) and feel free to give any kind of advice. Thank you in advance for your time and kind attention. Best regards.
EDIT #1:
#wheaties:
Thank you for a quick reply and help :)
What I'm currently trying to do is the following. I have to plot several "variables", e.g., state, control, dropouts, scheduling and disturbances. All the variables depend on three parameters, namely, dynamic, static and horizon. Further, state and control are np.zeros((2, np.size(length))), dropouts and scheduling are np.zeros(np.size(length)) and disturbance has specific form (see above). Initially, I declared them in the script and the list is very long and looks ugly. I use these variables to store responses of dynamical systems considered and to plot them. I don't know if this is a good way of doing this and if you have any suggestion please share.
Thanks again for your help.
Do you mean you want named access to a bunch of state information? The ordinary python idiom for class variables would look like this:
class Variable(object):
def __init__ (self, state_dynamic, state_static, control_static, control_dynamic, scheduling):
self.state_dynamic = state_dynamic
self.state_static = state_static
self.control_static = control_static
self.control_dynamic = control_dynamic
self.scheduling = control_dynamic
Which essentially creates a bucket with named fields that hold values you put in via the constructor. You can also create lightweight data classes using the namedtuple factory class, which avoids some of the boilerplate.
The other python idiom that might apply is to use the #property decorator as in #wheaties answer. This basically disguises a function call to make it look like a field. If what you're doing can be reduced to a functional basis this would make sense. This is an example of the idea (not based on your problem set, since I'm not sure I grok what you're doing in detail with all those identical variables) -- in this case I'm making a convenience wrapper for pulling individual flags out that are stored in a python number but really make a bit field:
class Bits(object):
def __init__(self, integer):
self.Integer = integer # pretend this is an integer between 0 and 8 representing 4 flags
#property
def locked(self):
# low bit = locked
return self.Integer & 1 == 1
#property
def available(self):
return self.Integer & 2 == 2
#property
def running_out_of_made_up_names(self):
return self.Integer & 4 == 4
#property
def really_desperate_now(self):
return self.Integer & 8 == 8
example = Bits(7)
print example.locked
# True
print example.really_desperate_now
# False
A method in Python is a function. If you want to get a value from a member function you have to end it with (). That said, some refactoring may help eliminate boilerplate and reduce the problem set size in your head. I'd suggest using a #property for some of these things, combined with a slight refactor
class variable:
def __init__(self, length):
self.length = length # time length`
#property
def state_dynamic(self):
return self.np_length
#property
def state_static(self):
return self.np_length
#property
def control_dynamic(self):
return self.np_length
#property
def control_static(self):
return self.np_length
#property
def scheduling(self):
return self.np_length
#property
def np_length(self):
return np.zeros(2, np.size(self.length))
That way you can use those functions as you would a member variable like you tried before:
var = variable(length).state_dynamic
What I can't tell from all this is what the difference is between all these variables? I don't see a single one. Are you assuming that you have to access them in order? If so, that's bad design and a problem. Never make that assumption.

Python: Idiomatic properties for structured data?

I've got a bad smell in my code. Perhaps I just need to let it air out for a bit, but right now it's bugging me.
I need to create three different input files to run three Radiative Transfer Modeling (RTM) applications, so that I can compare their outputs. This process will be repeated for thousands of sets of inputs, so I'm automating it with a python script.
I'd like to store the input parameters as a generic python object that I can pass to three other functions, who will each translate that general object into the specific parameters needed to run the RTM software they are responsible. I think this makes sense, but feel free to criticize my approach.
There are many possible input parameters for each piece of RTM software. Many of them over-lap. Most of them are kept at sensible defaults, but should be easily changed.
I started with a simple dict
config = {
day_of_year: 138,
time_of_day: 36000, #seconds
solar_azimuth_angle: 73, #degrees
solar_zenith_angle: 17, #degrees
...
}
There are a lot of parameters, and they can be cleanly categorized into groups, so I thought of using dicts within the dict:
config = {
day_of_year: 138,
time_of_day: 36000, #seconds
solar: {
azimuth_angle: 73, #degrees
zenith_angle: 17, #degrees
...
},
...
}
I like that. But there are a lot of redundant properties. The solar azimuth and zenith angles, for example, can be found if the other is known, so why hard-code both? So I started looking into python's builtin property. That lets me do nifty things with the data if I store it as object attributes:
class Configuration(object):
day_of_year = 138,
time_of_day = 36000, #seconds
solar_azimuth_angle = 73, #degrees
#property
def solar_zenith_angle(self):
return 90 - self.solar_azimuth_angle
...
config = Configuration()
But now I've lost the structure I had from the second dict example.
Note that some of the properties are less trivial than my solar_zenith_angle example, and might require access to other attributes outside of the group of attributes it is a part of. For example I can calculate solar_azimuth_angle if I know the day of year, time of day, latitude, and longitude.
What I'm looking for:
A simple way to store configuration data whose values can all be accessed in a uniform way, are nicely structured, and may exist either as attributes (real values) or properties (calculated from other attributes).
A possibility that is kind of boring:
Store everything in the dict of dicts I outlined earlier, and having other functions run over the object and calculate the calculatable values? This doesn't sound fun. Or clean. To me it sounds messy and frustrating.
An ugly one that works:
After a long time trying different strategies and mostly getting no where, I came up with one possible solution that seems to work:
My classes: (smells a bit func-y, er, funky. def-initely.)
class SubConfig(object):
"""
Store logical groupings of object attributes and properties.
The parent object must be passed to the constructor so that we can still
access the parent object's other attributes and properties. Useful if we
want to use them to compute a property in here.
"""
def __init__(self, parent, *args, **kwargs):
super(SubConfig, self).__init__(*args, **kwargs)
self.parent = parent
class Configuration(object):
"""
Some object which holds many attributes and properties.
Related configurations settings are grouped in SubConfig objects.
"""
def __init__(self, *args, **kwargs):
super(Configuration, self).__init__(*args, **kwargs)
self.root_config = 2
class _AConfigGroup(SubConfig):
sub_config = 3
#property
def sub_property(self):
return self.sub_config * self.parent.root_config
self.group = _AConfigGroup(self) # Stinky?!
How I can use them: (works as I would like)
config = Configuration()
# Inspect the state of the attributes and properties.
print("\nInitial configuration state:")
print("config.rootconfig: %s" % config.root_config)
print("config.group.sub_config: %s" % config.group.sub_config)
print("config.group.sub_property: %s (calculated)" % config.group.sub_property)
# Inspect whether the properties compute the correct value after we alter
# some attributes.
config.root_config = 4
config.group.sub_config = 5
print("\nState after modifications:")
print("config.rootconfig: %s" % config.root_config)
print("config.group.sub_config: %s" % config.group.sub_config)
print("config.group.sub_property: %s (calculated)" % config.group.sub_property)
The behavior: (output of execution of all of the above code, as expected)
Initial configuration state:
config.rootconfig: 2
config.group.sub_config: 3
config.group.sub_property: 6 (calculated)
State after modifications:
config.rootconfig: 4
config.group.sub_config: 5
config.group.sub_property: 20 (calculated)
Why I don't like it:
Storing configuration data in class definitions inside of the main object's __init__() doesn't feel elegant. Especially having to instantiate them immediately after definition like that. Ugh. I can deal with that for the parent class, sure, but doing it in a constructor...
Storing the same classes outside the main Configuration object doesn't feel elegant either, since properties in the inner classes may depend on the attributes of Configuration (or their siblings inside it).
I could deal with defining the functions outside of everything, so inside having things like
#property
def solar_zenith_angle(self):
return calculate_zenith(self.solar_azimuth_angle)
but I can't figure out how to do something like
#property
def solar.zenith_angle(self):
return calculate_zenith(self.solar.azimuth_angle)
(when I try to be clever about it I always run into <property object at 0xXXXXX>)
So what is the right way to go about this? Am I missing something basic or taking a very wrong approach? Does anyone know a clever solution?
Help! My python code isn't beautiful! I must be doing something wrong!
Phil,
Your hesitation about func-y config is very familiar to me :)
I suggest you to store your config not as a python file but as a structured data file. I personally prefer YAML because it looks clean, just as you designed in the very beginning. Of course, you will need to provide formulas for the auto calculated properties, but it is not too bad unless you put too much code. Here is my implementation using PyYAML lib.
The config file (config.yml):
day_of_year: 138
time_of_day: 36000 # seconds
solar:
azimuth_angle: 73 # degrees
zenith_angle: !property 90 - self.azimuth_angle
The code:
import yaml
yaml.add_constructor("tag:yaml.org,2002:map", lambda loader, node:
type("Config", (object,), loader.construct_mapping(node))())
yaml.add_constructor("!property", lambda loader, node:
property(eval("lambda self: " + loader.construct_scalar(node))))
config = yaml.load(open("config.yml"))
print "LOADED config.yml"
print "config.day_of_year:", config.day_of_year
print "config.time_of_day:", config.time_of_day
print "config.solar.azimuth_angle:", config.solar.azimuth_angle
print "config.solar.zenith_angle:", config.solar.zenith_angle, "(calculated)"
print
config.solar.azimuth_angle = 65
print "CHANGED config.solar.azimuth_angle = 65"
print "config.solar.zenith_angle:", config.solar.zenith_angle, "(calculated)"
The output:
LOADED config.yml
config.day_of_year: 138
config.time_of_day: 36000
config.solar.azimuth_angle: 73
config.solar.zenith_angle: 17 (calculated)
CHANGED config.solar.azimuth_angle = 65
config.solar.zenith_angle: 25 (calculated)
The config can be of any depth and properties can use any subgroup values. Try this for example:
a: 1
b:
c: 3
d: some text
e: true
f:
g: 7.01
x: !property self.a + self.b.c + self.b.f.g
Assuming you already loaded this config:
>>> config
<__main__.Config object at 0xbd0d50>
>>> config.a
1
>>> config.b
<__main__.Config object at 0xbd3bd0>
>>> config.b.c
3
>>> config.b.d
'some text'
>>> config.b.e
True
>>> config.b.f
<__main__.Config object at 0xbd3c90>
>>> config.b.f.g
7.01
>>> config.x
11.01
>>> config.b.f.g = 1000
>>> config.x
1004
UPDATE
Let us have a property config.b.x which uses both self, parent and subgroup attributes in its formula:
a: 1
b:
x: !property self.parent.a + self.c + self.d.e
c: 3
d:
e: 5
Then we just need to add a reference to parent in subgroups:
import yaml
def construct_config(loader, node):
attrs = loader.construct_mapping(node)
config = type("Config", (object,), attrs)()
for k, v in attrs.iteritems():
if v.__class__.__name__ == "Config":
setattr(v, "parent", config)
return config
yaml.add_constructor("tag:yaml.org,2002:map", construct_config)
yaml.add_constructor("!property", lambda loader, node:
property(eval("lambda self: " + loader.construct_scalar(node))))
config = yaml.load(open("config.yml"))
And let's see how it works:
>>> config.a
1
>>> config.b.c
3
>>> config.b.d.e
5
>>> config.b.parent == config
True
>>> config.b.d.parent == config.b
True
>>> config.b.x
9
>>> config.a = 1000
>>> config.b.x
1008
Well, here's an ugly way to at least make sure your properties get called:
class ConfigGroup(object):
def __init__(self, config):
self.config = config
def __getattribute__(self, name):
v = object.__getattribute__(self, name)
if hasattr(v, '__get__'):
return v.__get__(self, ConfigGroup)
return v
class Config(object):
def __init__(self):
self.a = 10
self.group = ConfigGroup(self)
self.group.a = property(lambda group: group.config.a*2)
Of course, at this point you might as well forego property entirely and just check if the attribute is callable in __getattribute__.
Or you could go all out and have fun with metaclasses:
def config_meta(classname, parents, attrs):
defaults = {}
groups = {}
newattrs = {'defaults':defaults, 'groups':groups}
for name, value in attrs.items():
if name.startswith('__'):
newattrs[name] = value
elif isinstance(value, type):
groups[name] = value
else:
defaults[name] = value
def init(self):
for name, value in defaults.items():
self.__dict__[name] = value
for name, value in groups.items():
group = value()
group.config = self
self.__dict__[name] = group
newattrs['__init__'] = init
return type(classname, parents, newattrs)
class Config2(object):
__metaclass__ = config_meta
a = 10
b = 2
class group(object):
c = 5
#property
def d(self):
return self.c * self.config.a
Use it like this:
>>> c2.a
10
>>> c2.group.d
50
>>> c2.a = 6
>>> c2.group.d
30
Final edit (?): if you don't want to have to "backtrack" using self.config in subgroup property definitions, you can use the following instead:
class group_property(property):
def __get__(self, obj, objtype=None):
return super(group_property, self).__get__(obj.config, objtype)
def __set__(self, obj, value):
super(group_property, self).__set__(obj.config, value)
def __delete__(self, obj):
return super(group_property, self).__del__(obj.config)
class Config2(object):
...
class group(object):
...
#group_property
def e(config):
return config.group.c * config.a
group_property receives the base config object instead of the group object, so paths always start from the root. Therefore, e is equivalent to the previously defined d.
BTW, supporting nested groups is left as an exercise for the reader.
Wow, I just read an article about descriptors on r/python today, but I don't think hacking descriptors is going to give you what you want.
The only thing I know that handles sub-configurations like that is flatland. Here's how it would work in Flatland anyhow.
But you could do:
class Configuration(Form):
day_of_year = Integer
time_of_day = Integer
class solar(Form):
azimuth_angle = Integer
solar_angle = Integer
Then load the dictionary in
config = Configuration({
day_of_year: 138,
time_of_day: 36000, #seconds
solar: {
azimuth_angle: 73, #degrees
zenith_angle: 17, #degrees
...
},
...
})
I love flatland, but I'm not sure you gain much by using it.
You could add a metaclass or decorator to your class definition.
something like
def instantiate(klass):
return klass()
class Configuration(object):
#instantiate
class solar(object):
#property
def azimuth_angle(self):
return self.azimuth_angle
That might be better. Then create a nice __init__ on Configuration that can load all the data from a dictionary. I dunno maybe someone else has a better idea.
Here's something a little more complete (without as much magic as LaC's answer, but slightly less generic).
def instantiate(clazz): return clazz()
#dummy functions for testing
calc_zenith_angle = calc_azimuth_angle = lambda(x): 3
class Solar(object):
def __init__(self):
if getattr(self,'azimuth_angle',None) is None and getattr(self,'zenith_angle',None) is None:
return AttributeError("must have either azimuth_angle or zenith_angle provided")
if getattr(self,'zenith_angle',None) is None:
self.zenith_angle = calc_zenith_angle(self.azimuth_angle)
elif getattr(self,'azimuth_angle',None) is None:
self.azimuth_angle = calc_azimuth_angle(self.zenith_angle)
class Configuration(object):
day_of_year = 138
time_of_day = 3600
#instantiate
class solar(Solar):
azimuth_angle = 73
#zenith_angle = 17 #not defined
#if you don't want auto-calculation to be done automagically
class ConfigurationNoAuto(object):
day_of_year = 138
time_of_day = 3600
#instantiate
class solar(Solar):
azimuth_angle = 73
#property
def zenith_angle(self):
return calc_zenith_angle(self.azimuth_angle)
config = Configuration()
config_no_auto = ConfigurationNoAuto()
>>> config.day_of_year
138
>>> config_no_auto.day_of_year
138
>>> config_no_auto.solar.azimuth_angle
73
>>> config_no_auto.solar.zenith_angle
3
>>> config.solar.zenith_angle
3
>>> config.solar.azimuth_angle
7
I think I would rather subclass dict so that it fell back to a default if no data was available. Something like this:
class fallbackdict(dict):
...
defaults = { 'pi': 3.14 }
x_config = fallbackdict(defaults)
x_config.update({
'planck': 6.62606957e-34
})
The other aspect can be addressed with callables. Wether this is elegant or ugly depends on wether datatype declarations are useful:
pi: (float, 3.14)
calc = lambda v: v[0](v[1])
x_config.update({
'planck': (double, 6.62606957e-34),
'calculated': (lambda x: 1.0 - calc(x_config['planck']), None)
})
Depending on the circumstances, the lambda might be broken out if it is used many times.
Don't know if it is better, but it mostly preserves the dictionary style.

Categories

Resources