:Author: Santosh Philip.
EpBunch is at the heart of what makes eppy easy to use. Specifically Epbunch is what allows us to use the syntax building.Name
and building.North_Axis
. Some advanced coding had to be done to make this happen. Coding that would be easy for professional programmers, but not for us ordinary folk :-(
Most of us who are going to be coding eppy are not professional programmers. I was completely out of my depth when I did this coding. I had the code reviewed by programmers who do this for a living (at python meetups in the Bay Area). In their opinion, I was not doing anything fundamentally wrong.
Below is a fairly long explanation, to ease you into the code. Read through the whole thing without trying to understand every detail, just getting a birds eye veiw of the explanation. Then read it again, you will start to grok some of the details. All the code here is working code, so you can experiment with it.
To understand how EpBunch or Bunch is coded, one has to have an understanding of the magic methods of Python. (For a background on magic methods, take a look at http://www.rafekettler.com/magicmethods.html) Let us dive straight into this with some examples
In [1]:
adict = dict(a=10, b=20) # create a dictionary
print adict
print adict['a']
print adict['b']
What happens when we say d['a'] ?
This is where the magic methods come in. Magic methods are methods that work behind the scenes and do some magic. So when we say d['a'], The dict is calling the method __getitem__('a')
.
Magic methods have a double underscore "__
", called dunder methods for short
Let us override that method and see what happens.
In [1]:
class Funnydict(dict): # we are subclassing dict here
def __getitem__(self, key):
value = super(Funnydict, self).__getitem__(key)
return "key = %s, value = %s" % (key, value)
funny = Funnydict(dict(a=10, b=20))
print funny
The print worked as expected. Now let us try to print the values
In [3]:
print funny['a']
print funny['b']
Now that worked very differently from a dict
So it is true, funny['a'] does call __getitem__()
that we just wrote
Let us go back to the variable adict
In [4]:
# to jog our memory
print adict
In [5]:
# this should not work
print adict.a
What method gets called when we say adict.a ?
The magic method here is __getattr__
() and __setattr__()
. Shall we override them and see if we can get the dot notation to work ?
In [6]:
class Like_bunch(dict):
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
self[name] = value
lbunch = Like_bunch(dict(a=10, b=20))
print lbunch
Works like a dict so far. How about lbunch.a ?
In [7]:
print lbunch.a
print lbunch.b
Yipeee !!! I works
How about lbunch.nota = 100
In [8]:
lbunch.anot = 100
print lbunch.anot
All good here. But don't trust the code above too much. It was simply done as a demonstration of dunder methods and is not fully tested.
Eppy uses the bunch library to do something similar. You can read more about the bunch library in the previous section.
Once again let us open a small idf file to test.
In [9]:
# you would normaly install eppy by doing
# python setup.py install
# or
# pip install eppy
# or
# easy_install eppy
# if you have not done so, uncomment the following three lines
import sys
# pathnameto_eppy = 'c:/eppy'
pathnameto_eppy = '../../../'
sys.path.append(pathnameto_eppy)
In [10]:
from eppy import modeleditor
from eppy.modeleditor import IDF
iddfile = "../../../eppy/resources/iddfiles/Energy+V7_2_0.idd"
fname1 = "../../../eppy/resources/idffiles/V_7_2/dev1.idf"
IDF.setiddname(iddfile)
idf1 = IDF(fname1)
idf1.printidf()
In [11]:
dtls = idf1.model.dtls
dt = idf1.model.dt
idd_info = idf1.idd_info
In [12]:
dt['MATERIAL:AIRGAP']
Out[12]:
In [13]:
obj_i = dtls.index('MATERIAL:AIRGAP')
obj_idd = idd_info[obj_i]
obj_idd
Out[13]:
For the rest of this section let us look at only one airgap object
In [14]:
airgap = dt['MATERIAL:AIRGAP'][0]
airgap
Out[14]:
Let us review our knowledge of bunch
In [15]:
from bunch import Bunch
adict = {'a':1, 'b':2, 'c':3}
bunchdict = Bunch(adict)
print bunchdict
print bunchdict.a
print bunchdict.b
print bunchdict.c
Bunch lets us use dot notation on the keys of a dictionary. We need to find a way of making airgap.Name
work. This is not straightforward because, airgap is list and Bunch works on dicts. It would be easy if airgap was in the form {'Name' : 'F04 Wall air space resistance', 'Thermal Resistance' : 0.15}
.
The rest of this section is a simplified version of how EpBunch works.
In [16]:
class EpBunch(Bunch):
def __init__(self, obj, objls, objidd, *args, **kwargs):
super(EpBunch, self).__init__(*args, **kwargs)
self.obj = obj
self.objls = objls
self.objidd = objidd
The above code shows how EpBunch is initialized. Three variables are passed to EpBunch to initialize it. They are obj, objls, objidd
.
In [17]:
obj = airgap
objls = ['key', 'Name', 'Thermal_Resistance'] # a function extracts this from idf1.idd_info
objidd = obj_idd
#
print obj
print objls
# let us ignore objidd for now
Now we override __setattr__()
and __getattr__()
in the following way
In [18]:
class EpBunch(Bunch):
def __init__(self, obj, objls, objidd, *args, **kwargs):
super(EpBunch, self).__init__(*args, **kwargs)
self.obj = obj
self.objls = objls
self.objidd = objidd
def __getattr__(self, name):
if name in ('obj', 'objls', 'objidd'):
return super(EpBunch, self).__getattr__(name)
i = self.objls.index(name)
return self.obj[i]
def __setattr__(self, name, value):
if name in ('obj', 'objls', 'objidd'):
super(EpBunch, self).__setattr__(name, value)
return None
i = self.objls.index(name)
self.obj[i] = value
In [19]:
# Let us create a EpBunch object
bunch_airgap = EpBunch(obj, objls, objidd)
In [20]:
print bunch_airgap.Name
print bunch_airgap.Thermal_Resistance
In [21]:
print bunch_airgap.obj
Let us change some values using the dot notation
In [22]:
bunch_airgap.Name = 'Argon in gap'
In [23]:
print bunch_airgap.Name
In [24]:
print bunch_airgap.obj
Using the dot notation the value is changed in the list
Let us make sure it actually has done that.
In [25]:
idf1.model.dt['MATERIAL:AIRGAP'][0]
Out[25]:
EpBunch
acts as a wrapper around idf1.model.dt['MATERIAL:AIRGAP'][0]
In other words EpBunch
is just Syntactic Sugar for idf1.model.dt['MATERIAL:AIRGAP'][0]
At this point your reaction may, "I don't see how all those values in idf1.model.dt
changed". If such question arises in your mind, you need to read the following:
This is especially important if you are experienced in other languages, and you expect the behavior to be a little different. Actually follow and read those links in any case.
The code for EpBunch in the earlier section will work, but has been simplified for clarity. In file bunch_subclass.py
take a look at the class EpBunch_1 . This class does the first override of __setattr__
and __getattr__
. You will see that the code is a little more involved, dealing with edge conditions and catching exceptions.
EpBunch_1 also defines __repr__
. This lets you print EpBunch in a human readable format. Further research indicates that __str__
should have been used to do this, not __repr__
:-(
EpBunch_2
is subclassed from EpBunch_1
.
It overrides __setattr__
and __getattr__
to add a small functionality that has not been documented or used. The idea was to give the ability to shorten field names with alias. So building.Maximum_Number_of_Warmup_Days
could be made into building.warmupdays
.
I seemed like a good idea when I wrote it. Ignore it for now, although it may make a comeback :-)
EpBunch_3
is subclassed from EpBunch_2
.
EpBunch_3 adds the ability to add functions to EpBunch objects. This would allow the object to make calculations using data within the object. So BuildingSurface:Detailed
object has all the geometry data of the object. The function 'area' will let us calculate the are of the object even though area is not a field in BuildingSurface:Detailed
.
So you can call idf1.idfobjects["BuildingSurface:Detailed"][0].area
and get the area of the surface.
At the moment, the functions can use only data within the object for it's calculation. We need to extend this functionality so that calculations can be done using data outside the object. This would be useful in calculating the volume of a Zone. Such a calculation would need data from the surfaces that the aone refers to.
EpBunch_4
is subclassed from EpBunch_3
.
EpBunch_4
overrides _setitem__
and __getitem__
. Right now airgap.Name
works. This update allows airgap["Name"]
to work correctly too
EpBunch_5
is subclassed from EpBunch_4
.
EpBunch_5
adds functions that allows you to call functions getrange
and checkrange
for a field
EpBunch = EpBunch_5
Finally EpBunch_5
is named as EpBunch. So the rest of the code uses EpBunch and in effect it uses Epbunch_5