I've just been reading an article that talks about implementing a parser in python:
http://effbot.org/zone/simple-top-down-parsing.htm
The general idea behind the code is described in this paper: http://mauke.hopto.org/stuff/papers/p41-pratt.pdf
Being fairly new to writing parsers in python so I'm trying to write something similar as a learning exercise. However when I attempted to try to code up something similar to what was found in the article I am getting an TypeError: unbound method TypeError. This is the first time I've encountered such an error and I've spent all day trying to figure this out but I haven't solved the issue. Here is a minimal code example (in it's entirety) that has this problem:
import re
class Symbol_base(object):
""" A base class for all symbols"""
id = None # node/token type name
value = None #used by literals
first = second = third = None #used by tree nodes
def nud(self):
""" A default implementation for nud """
raise SyntaxError("Syntax error (%r)." % self.id)
def led(self,left):
""" A default implementation for led """
raise SyntaxError("Unknown operator (%r)." % self.id)
def __repr__(self):
if self.id == "(name)" or self.id == "(literal)":
return "(%s %s)" % (self.id[1:-1], self.value)
out = [self.id, self.first, self.second, self.third]
out = map(str, filter(None,out))
return "(" + " ".join(out) + ")"
symbol_table = {}
def symbol(id, bindingpower=0):
""" If a given symbol is found in the symbol_table return it.
If the symblo cannot be found theni create the appropriate class
and add that to the symbol_table."""
try:
s = symbol_table[id]
except KeyError:
class s(Symbol_base):
pass
s.__name__ = "symbol:" + id #for debugging purposes
s.id = id
s.lbp = bindingpower
symbol_table[id] = s
else:
s.lbp = max(bindingpower,s.lbp)
return s
def infix(id, bp):
""" Helper function for defining the symbols for infix operations """
def infix_led(self, left):
self.first = left
self.second = expression(bp)
return self
symbol(id, bp).led = infix_led
#define all the symbols
infix("+", 10)
symbol("(literal)").nud = lambda self: self #literal values must return the symbol itself
symbol("(end)")
token_pat = re.compile("\s*(?:(\d+)|(.))")
def tokenize(program):
for number, operator in token_pat.findall(program):
if number:
symbol = symbol_table["(literal)"]
s = symbol()
s.value = number
yield s
else:
symbol = symbol_table.get(operator)
if not symbol:
raise SyntaxError("Unknown operator")
yield symbol
symbol = symbol_table["(end)"]
yield symbol()
def expression(rbp = 0):
global token
t = token
token = next()
left = t.nud()
while rbp < token.lbp:
t = token
token = next()
left = t.led(left)
return left
def parse(program):
global token, next
next = tokenize(program).next
token = next()
return expression()
def __main__():
print parse("1 + 2")
if __name__ == "__main__":
__main__()
When I try to run this with pypy:
Traceback (most recent call last):
File "app_main.py", line 72, in run_toplevel
File "parser_code_issue.py", line 93, in <module>
__main__()
File "parser_code_issue.py", line 90, in __main__
print parse("1 + 2")
File "parser_code_issue.py", line 87, in parse
return expression()
File "parser_code_issue.py", line 81, in expression
left = t.led(left)
TypeError: unbound method infix_led() must be called with symbol:+ instance as first argument (got symbol:(literal) instance instead)
I'm guessing this happens because I don't create an instance for the infix operations but I'm not really wanting to create an instance at that point. Is there some way I can change those methods without creating instances?
Any help explaining why this is happening and what I can do to fix the code is greatly appreciated!
Also is this behaviour going to change in python 3?
You forgot to create an instance of the symbol in your tokenize() function; when not a number, yield symbol(), not symbol:
else:
symbol = symbol_table.get(operator)
if not symbol:
raise SyntaxError("Unknown operator")
yield symbol()
With that one change your code prints:
(+ (literal 1) (literal 2))
You haven't bound new function to the instance of your object.
import types
obj = symbol(id, bp)
obj.led = types.MethodType(infix_led, obj)
See accepted answer to another SO question
Related
This question already has answers here:
Getting the name of a variable as a string
(32 answers)
Closed 4 months ago.
Is it possible to get the original variable name of a variable passed to a function? E.g.
foobar = "foo"
def func(var):
print var.origname
So that:
func(foobar)
Returns:
>>foobar
EDIT:
All I was trying to do was make a function like:
def log(soup):
f = open(varname+'.html', 'w')
print >>f, soup.prettify()
f.close()
.. and have the function generate the filename from the name of the variable passed to it.
I suppose if it's not possible I'll just have to pass the variable and the variable's name as a string each time.
EDIT: To make it clear, I don't recommend using this AT ALL, it will break, it's a mess, it won't help you in any way, but it's doable for entertainment/education purposes.
You can hack around with the inspect module, I don't recommend that, but you can do it...
import inspect
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.getframeinfo(frame[0]).code_context[0].strip()
args = string[string.find('(') + 1:-1].split(',')
names = []
for i in args:
if i.find('=') != -1:
names.append(i.split('=')[1].strip())
else:
names.append(i)
print names
def main():
e = 1
c = 2
foo(e, 1000, b = c)
main()
Output:
['e', '1000', 'c']
To add to Michael Mrozek's answer, you can extract the exact parameters versus the full code by:
import re
import traceback
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
vars_name = re.compile(r'\((.*?)\).*$').search(code).groups()[0]
print vars_name
return
foobar = "foo"
func(foobar)
# PRINTS: foobar
Looks like Ivo beat me to inspect, but here's another implementation:
import inspect
def varName(var):
lcls = inspect.stack()[2][0].f_locals
for name in lcls:
if id(var) == id(lcls[name]):
return name
return None
def foo(x=None):
lcl='not me'
return varName(x)
def bar():
lcl = 'hi'
return foo(lcl)
bar()
# 'lcl'
Of course, it can be fooled:
def baz():
lcl = 'hi'
x='hi'
return foo(lcl)
baz()
# 'x'
Moral: don't do it.
Another way you can try if you know what the calling code will look like is to use traceback:
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
code will contain the line of code that was used to call func (in your example, it would be the string func(foobar)). You can parse that to pull out the argument
You can't. It's evaluated before being passed to the function. All you can do is pass it as a string.
#Ivo Wetzel's answer works in the case of function call are made in one line, like
e = 1 + 7
c = 3
foo(e, 100, b=c)
In case that function call is not in one line, like:
e = 1 + 7
c = 3
foo(e,
1000,
b = c)
below code works:
import inspect, ast
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.findsource(frame[0])[0]
nodes = ast.parse(''.join(string))
i_expr = -1
for (i, node) in enumerate(nodes.body):
if hasattr(node, 'value') and isinstance(node.value, ast.Call)
and hasattr(node.value.func, 'id') and node.value.func.id == 'foo' # Here goes name of the function:
i_expr = i
break
i_expr_next = min(i_expr + 1, len(nodes.body)-1)
lineno_start = nodes.body[i_expr].lineno
lineno_end = nodes.body[i_expr_next].lineno if i_expr_next != i_expr else len(string)
str_func_call = ''.join([i.strip() for i in string[lineno_start - 1: lineno_end]])
params = str_func_call[str_func_call.find('(') + 1:-1].split(',')
print(params)
You will get:
[u'e', u'1000', u'b = c']
But still, this might break.
You can use python-varname package
from varname import nameof
s = 'Hey!'
print (nameof(s))
Output:
s
Package below:
https://github.com/pwwang/python-varname
For posterity, here's some code I wrote for this task, in general I think there is a missing module in Python to give everyone nice and robust inspection of the caller environment. Similar to what rlang eval framework provides for R.
import re, inspect, ast
#Convoluted frame stack walk and source scrape to get what the calling statement to a function looked like.
#Specifically return the name of the variable passed as parameter found at position pos in the parameter list.
def _caller_param_name(pos):
#The parameter name to return
param = None
#Get the frame object for this function call
thisframe = inspect.currentframe()
try:
#Get the parent calling frames details
frames = inspect.getouterframes(thisframe)
#Function this function was just called from that we wish to find the calling parameter name for
function = frames[1][3]
#Get all the details of where the calling statement was
frame,filename,line_number,function_name,source,source_index = frames[2]
#Read in the source file in the parent calling frame upto where the call was made
with open(filename) as source_file:
head=[source_file.next() for x in xrange(line_number)]
source_file.close()
#Build all lines of the calling statement, this deals with when a function is called with parameters listed on each line
lines = []
#Compile a regex for matching the start of the function being called
regex = re.compile(r'\.?\s*%s\s*\(' % (function))
#Work backwards from the parent calling frame line number until we see the start of the calling statement (usually the same line!!!)
for line in reversed(head):
lines.append(line.strip())
if re.search(regex, line):
break
#Put the lines we have groked back into sourcefile order rather than reverse order
lines.reverse()
#Join all the lines that were part of the calling statement
call = "".join(lines)
#Grab the parameter list from the calling statement for the function we were called from
match = re.search('\.?\s*%s\s*\((.*)\)' % (function), call)
paramlist = match.group(1)
#If the function was called with no parameters raise an exception
if paramlist == "":
raise LookupError("Function called with no parameters.")
#Use the Python abstract syntax tree parser to create a parsed form of the function parameter list 'Name' nodes are variable names
parameter = ast.parse(paramlist).body[0].value
#If there were multiple parameters get the positional requested
if type(parameter).__name__ == 'Tuple':
#If we asked for a parameter outside of what was passed complain
if pos >= len(parameter.elts):
raise LookupError("The function call did not have a parameter at postion %s" % pos)
parameter = parameter.elts[pos]
#If there was only a single parameter and another was requested raise an exception
elif pos != 0:
raise LookupError("There was only a single calling parameter found. Parameter indices start at 0.")
#If the parameter was the name of a variable we can use it otherwise pass back None
if type(parameter).__name__ == 'Name':
param = parameter.id
finally:
#Remove the frame reference to prevent cyclic references screwing the garbage collector
del thisframe
#Return the parameter name we found
return param
If you want a Key Value Pair relationship, maybe using a Dictionary would be better?
...or if you're trying to create some auto-documentation from your code, perhaps something like Doxygen (http://www.doxygen.nl/) could do the job for you?
I wondered how IceCream solves this problem. So I looked into the source code and came up with the following (slightly simplified) solution. It might not be 100% bullet-proof (e.g. I dropped get_text_with_indentation and I assume exactly one function argument), but it works well for different test cases. It does not need to parse source code itself, so it should be more robust and simpler than previous solutions.
#!/usr/bin/env python3
import inspect
from executing import Source
def func(var):
callFrame = inspect.currentframe().f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
expression = source.asttokens().get_text(callNode.args[0])
print(expression, '=', var)
i = 1
f = 2.0
dct = {'key': 'value'}
obj = type('', (), {'value': 42})
func(i)
func(f)
func(s)
func(dct['key'])
func(obj.value)
Output:
i = 1
f = 2.0
s = string
dct['key'] = value
obj.value = 42
Update: If you want to move the "magic" into a separate function, you simply have to go one frame further back with an additional f_back.
def get_name_of_argument():
callFrame = inspect.currentframe().f_back.f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
return source.asttokens().get_text(callNode.args[0])
def func(var):
print(get_name_of_argument(), '=', var)
If you want to get the caller params as in #Matt Oates answer answer without using the source file (ie from Jupyter Notebook), this code (combined from #Aeon answer) will do the trick (at least in some simple cases):
def get_caller_params():
# get the frame object for this function call
thisframe = inspect.currentframe()
# get the parent calling frames details
frames = inspect.getouterframes(thisframe)
# frame 0 is the frame of this function
# frame 1 is the frame of the caller function (the one we want to inspect)
# frame 2 is the frame of the code that calls the caller
caller_function_name = frames[1][3]
code_that_calls_caller = inspect.findsource(frames[2][0])[0]
# parse code to get nodes of abstract syntact tree of the call
nodes = ast.parse(''.join(code_that_calls_caller))
# find the node that calls the function
i_expr = -1
for (i, node) in enumerate(nodes.body):
if _node_is_our_function_call(node, caller_function_name):
i_expr = i
break
# line with the call start
idx_start = nodes.body[i_expr].lineno - 1
# line with the end of the call
if i_expr < len(nodes.body) - 1:
# next expression marks the end of the call
idx_end = nodes.body[i_expr + 1].lineno - 1
else:
# end of the source marks the end of the call
idx_end = len(code_that_calls_caller)
call_lines = code_that_calls_caller[idx_start:idx_end]
str_func_call = ''.join([line.strip() for line in call_lines])
str_call_params = str_func_call[str_func_call.find('(') + 1:-1]
params = [p.strip() for p in str_call_params.split(',')]
return params
def _node_is_our_function_call(node, our_function_name):
node_is_call = hasattr(node, 'value') and isinstance(node.value, ast.Call)
if not node_is_call:
return False
function_name_correct = hasattr(node.value.func, 'id') and node.value.func.id == our_function_name
return function_name_correct
You can then run it as this:
def test(*par_values):
par_names = get_caller_params()
for name, val in zip(par_names, par_values):
print(name, val)
a = 1
b = 2
string = 'text'
test(a, b,
string
)
to get the desired output:
a 1
b 2
string text
Since you can have multiple variables with the same content, instead of passing the variable (content), it might be safer (and will be simpler) to pass it's name in a string and get the variable content from the locals dictionary in the callers stack frame. :
def displayvar(name):
import sys
return name+" = "+repr(sys._getframe(1).f_locals[name])
If it just so happens that the variable is a callable (function), it will have a __name__ property.
E.g. a wrapper to log the execution time of a function:
def time_it(func, *args, **kwargs):
start = perf_counter()
result = func(*args, **kwargs)
duration = perf_counter() - start
print(f'{func.__name__} ran in {duration * 1000}ms')
return result
For understanding decorators in Python, i created in a class an example. But when i run it i receive an error.
class Operation:
def __init__(self, groupe):
self.__groupe = groupe
#property
def groupe(self):
return self.__groupe
#groupe.setter
def groupe(self, value):
self.__groupe = value
def addition(self, func_goodbye):
ln_house = len('house')
ln_school = len('school')
add = ln_house + ln_school
print('The result is :' + str(add))
return func_goodbye
#addition
def goodbye(self):
print('Goodbye people !!')
if __name__ == '__main__':
p1 = Operation('Student')
p1.goodbye()
I receive this error :
Traceback (most recent call last):
File "Operation.py", line 1, in
class Operation:
File "Operation.py", line 21, in Operation
#addition
TypeError: addition() missing 1 required positional argument: 'func_goodbye'
You can have a class scoped decorator, however there won't be a self when the decorator is called
a decorator:
#foo
def bar(): ...
is roughly equivalent to
def bar(): ...
bar = foo(bar)
in your particular example, if you remove the self parameter, it should function as you expect:
def addition(func_goodbye):
ln_house = len('house')
ln_school = len('school')
add = ln_house + ln_school
print('The result is :' + str(add))
return func_goodbye
#addition
def goodbye(self):
print('Goodbye people !!')
for good measure, I might del addition after that just to ensure it isn't accidentally called later
(an aside: one unfortunate side-effect of this is many linters and type checkers will consider this "odd" so I've yet to find a way to appease them (for example mypy))
import operator
import re
from ply import lex, yacc
class Lexer(object):
tokens = [
'COMMA',
'TILDE',
'PARAM',
'LP',
'RP',
'FUNC'
]
# Regular expression rules for simple tokens
t_COMMA = r'\,'
t_TILDE = r'\~'
t_PARAM = r'[^\s\(\),&:\"\'~]+'
def __init__(self, dict_obj):
self.dict_obj = dict_obj
def t_LP(self, t):
r'\('
return t
def t_RP(self, t):
r'\)'
return t
def t_FUNC(self, t):
# I want to generate token for this FUNC from the keys of model map
# For eg: r'key1|key2'
r'(?i)FUNC'
return t
# Define a rule so we can track line numbers
def t_newline(self, t):
r'\n+'
t.lexer.lineno += len(t.value)
# A string containing ignored characters (spaces and tabs)
t_ignore = ' \t'
# Error handling rule
def t_error(self, t):
print("Illegal character '%s' on line %d, column %d" % (t.value[0], t.lexer.lineno, t.lexer.lexpos))
t.lexer.skip(1)
# Build the lexer
def build_lexer(self, **kwargs):
self.lexer = lex.lex(module=self, **kwargs)
return self.lexer
class Parser(object):
tokens = Lexer.tokens
def __init__(self, **kwargs):
self.parser = yacc.yacc(module=self, **kwargs)
self.lexer = None
self._dict_obj = None
self.error = ""
self.result = ""
#property
def dict_obj(self):
return self._dict_obj
#dict_obj.setter
def dict_obj(self, dict_obj):
self._dict_obj = dict_obj
self.lexer = Lexer(self._dict_obj).build_lexer()
# Handles LP expression RP
def p_expression(self, p):
"""
expression : LP expression RP
"""
# Handles TILDE PARAM - call search
def p_tilde_param(self, p):
"""
expression : TILDE PARAM
"""
p[0] = p[2]
return p[0]
# Handles ANY LP PARAM RP - call search
def p_expression_any(self, p):
"""
expression : FUNC LP PARAM RP
"""
p[0] = p[3]
return p[0]
# Error handling rule
def p_error(self, p):
if p:
stack_state_str = " ".join([symbol.type for symbol in self.parser.symstack[1:]])
self.error = "Syntax error at %s, type %s, on line %d, Parser state: %s %s . %s" % (
p.value, p.type, p.lineno, self.parser.state, stack_state_str, p
)
else:
self.error = "SYNTAX ERROR IN INPUT"
def get_result(self, input_):
input_ = input_.strip()
if input_:
self.result = self.parser.parse(input_, lexer=self.lexer)
return self.result
else:
raise ValueError("EMPTY EXPRESSION ERROR")
def parser(input_):
par_obj = Parser()
par_obj.dict_obj = {
'key1' : 'value1',
'key2' : 'value2'
}
return par_obj.get_result(input_)
result = parser("~hello")
Above is the code of lexer and parser using ply library. I have just encapsulated all of my code in the class form. Problems which i am facing:
1.) I'm trying to pass a dict_obj to the parser class. I don't know what i am doing wrong and getting an error like :
AttributeError: 'Parser' object has no attribute 'dict_obj'
2.) What I'm trying to do?
I want to pass this dict_obj to the parser class and then pass it to the lexer class as well and then make use of it in the lexer one of the tokens methods (t_FUNC) method. In this method my regex will return keys of the this dict obj.
I think i'm doing something wrong and hence failing to implement it. Please help.
In your constructor (__init__) for the Parser object, you ask Ply to generate a parser before the Parser object is fully constructed:
def __init__(self, **kwargs):
self.parser = yacc.yacc(module=self, **kwargs)
# This is the critical line:
self._dict_obj = None
In order to construct a parser from the object (yacc.yacc(module=self)), Ply needs to iterate over all the object's attributes. For example, it needs to find all the parser functions in order to extract their docstrings in order to determine the grammar.
Ply uses the dir built-in function to make a dictionary of all the object's attributes. Because your Parser object has a custom attribute dict_obj, that key is returned from dir and so Ply tries to cache that attribute with its value. But when it calls gettattr(module, 'dict_obj'), the getter is called, and the getter tries to return self._dict_obj. However, self._dict_obj has not yet been defined, so that ends up throwing an error:
AttributeError: 'Parser' object has no attribute '_dict_obj'
Note that this is not the error message you reported in your question; that error says that there is no attribute dict_obj. Perhaps that was a copy-and-paste error.
If you move the call to yacc.yacc to the end of the initialiser, that particular problem goes away:
def __init__(self, **kwargs):
self.lexer = None
self._dict_obj = None
self.error = ""
self.result = ""
self.parser = yacc.yacc(module=self, **kwargs)
However, there are a number of other problems in the code excerpt which make it difficult to verify this solution. These include:
There is no LexerNmsysSearch. I assumed you meant Lexer.
There is no node_expression. I have no idea what that is supposed to be so I just removed the test.
Your grammar does not match the input you are testing, so the parser immediately throws a syntax error. I changed the input to "(~hello)" in an attempt to produce something parseable.
The parser actions do not set semantic values, so self.parse.parse() doesn't return any value. This causes get_result to throw an error.
At that point, I gave up on trying to produce anything sensible out of the code. For future reference, please ensure that error messages are quoted exactly and that sample code included in the question can be run.
I have the following simple class definition:
def apmSimUp(i):
return APMSim(i)
def simDown(sim):
sim.close()
class APMSimFixture(TestCase):
def setUp(self):
self.pool = multiprocessing.Pool()
self.sims = self.pool.map(
apmSimUp,
range(numCores)
)
def tearDown(self):
self.pool.map(
simDown,
self.sims
)
Where class APMSim is defined purely by plain simple python primitive types (string, list etc.) the only exception is a static member, which is a multiprocessing manager.list
However, when I try to execute this class, I got the following error information:
Error
Traceback (most recent call last):
File "/home/peng/git/datapassport/spookystuff/mav/pyspookystuff_test/mav/__init__.py", line 77, in setUp
range(numCores)
File "/usr/lib/python2.7/multiprocessing/pool.py", line 251, in map
return self.map_async(func, iterable, chunksize).get()
File "/usr/lib/python2.7/multiprocessing/pool.py", line 567, in get
raise self._value
MaybeEncodingError: Error sending result: '[<pyspookystuff.mav.sim.APMSim object at 0x7f643c4ca8d0>]'. Reason: 'TypeError("can't pickle thread.lock objects",)'
Which is strange as thread.lock cannot be found anywhere, I strictly avoid any multithreading component (as you can see, only multiprocessing component is used). And none of these component exist in my class, or only as static member, what should I do to make this class picklable?
BTW, is there a way to exclude a black sheep member from pickling? Like Java's #transient annotation?
Thanks a lot for any help!
UPDATE: The following is my full APMSim class, please see if you find anything that violates it picklability:
usedINums = mav.manager.list()
class APMSim(object):
global usedINums
#staticmethod
def nextINum():
port = mav.nextUnused(usedINums, range(0, 254))
return port
def __init__(self, iNum):
# type: (int) -> None
self.iNum = iNum
self.args = sitl_args + ['-I' + str(iNum)]
#staticmethod
def create():
index = APMSim.nextINum()
try:
result = APMSim(index)
return result
except Exception as ee:
usedINums.remove(index)
raise
#lazy
def _sitl(self):
sitl = SITL()
sitl.download('copter', '3.3')
sitl.launch(self.args, await_ready=True, restart=True)
print("launching .... ", sitl.p.pid)
return sitl
#lazy
def sitl(self):
self.setParamAndRelaunch('SYSID_THISMAV', self.iNum + 1)
return self._sitl
def _getConnStr(self):
return tcp_master(self.iNum)
#lazy
def connStr(self):
self.sitl
return self._getConnStr()
def setParamAndRelaunch(self, key, value):
wd = self._sitl.wd
print("relaunching .... ", self._sitl.p.pid)
v = connect(self._getConnStr(), wait_ready=True) # if use connStr will trigger cyclic invocation
v.parameters.set(key, value, wait_ready=True)
v.close()
self._sitl.stop()
self._sitl.launch(self.args, await_ready=True, restart=True, wd=wd, use_saved_data=True)
v = connect(self._getConnStr(), wait_ready=True)
# This fn actually rate limits itself to every 2s.
# Just retry with persistence to get our first param stream.
v._master.param_fetch_all()
v.wait_ready()
actualValue = v._params_map[key]
assert actualValue == value
v.close()
def close(self):
self._sitl.stop()
usedINums.remove(self.iNum)
lazy decorator is from this library:
https://docs.python.org/2/tutorial/classes.html#generator-expressions
It would help to see how your class looks, but if it has methods from multiprocessing you may have issues just pickling it by default. Multiprocessing objects can use locks as well, and these are (obviously) unpickle-able.
You can customize pickling with the __getstate__ method, or __reduce__ (documented in the same place).
I using the wx library to build a GUI. I have initialized a panel and intialized some push buttons and have binded a function that will execute when pushed. The function takes a list of arguments, one which is a callback function. What I am trying to do is redefine the callback function during runtime but I am failing to do so.
My attempt so far is:
self.UpdateCurrent = None
self.GetCurrent = None
self.UpdateCellVoltage = None
self.GetCellVoltage = None
self.UpdateCellTemperature = None
self.GetCellTemperature = None
self.battery_control_d = OrderedDict([('Current',[self.UpdateCurrent, self.GetCurrent, None, 1]),
('Cell Voltage',[self.UpdateCellVoltage, self.GetCellVoltage, 0, 24]),
('Cell Temperature',[self.UpdateCellTemperature, self.GetCellTemperature, 0, 24])])
.
.
.
submit_btn_l[-1].Bind(wx.EVT_BUTTON,
lambda evt,
io_name = io_name,
callback_fn = self.battery_control_d[io_name][0],
ctrl_textbox = ctrl_textbox,
dim_combobox = self.dim_combobox_l[-1]:
self._send_control_value(evt,
io_name,
callback_fn,
ctrl_textbox,
dim_combobox))
.
.
.
def init_battery_intf(self):
self.battery_intf = self.simulator_main_panel.battery_intf
self.UpdateCurrent = self.battery_intf.UpdateCurrent
self.GetCurrent = self.battery_intf.GetCurrent
self.UpdateCellVoltage = self.battery_intf.UpdateCellVoltage
self.GetCellVoltage = self.battery_intf.GetCellVoltage
self.UpdateCellTemperature = self.battery_intf.UpdateCellTemperature
self.GetCellTemperature = self.battery_intf.GetCellTemperature
.
.
.
def _send_control_value(self,
evt,
io_name,
callback_fn,
ctrl_textbox,
dim_combobox):
io_value = float(ctrl_textbox.Value)
if ("Temperature" in io_name):
io_value -= self.simulator_main_gui.temperature_unit_converter
callback_fn(io_value, int(dim_combobox.Value))
def update( self,
evt ):
for io_name, io_info in self.battery_control_d.iteritems():
io_value = float(io_info[1](io_info[2]))
self.reading_text_l[self.io_indexer.index(io_name)].SetLabel(" %.4f " % (io_value))
I predefine some update/get objects.
I bind the function to the buttons
During runtime I call init_battery_intf to initialize these objects
The line that errors out is when I try to call the callback function. It seems to be still be set to None.
Traceback (most recent call last):
File "C:\Users\Sam\Desktop\work\simulator\src\simulate.py", line 1185, in updat
self.control_notebook.update(evt)
File "C:\Users\Sam\Desktop\work\simulator\src\simulate.py", line 869, in update
self.battery_control_panel.update(evt)
File "C:\Users\Sam\Desktop\work\simulator\src\simulate.py", line 591, in update
io_value = float(io_info[1](io_info[2]))
TypeError: 'NoneType' object is not callable
I know I could redefine the bind and feed in the values directly, but I wanted to keep my code clean and simple, and I feel like Python has a way to distribute the redefined callback function to all instances of the object.
I'm using Python 2.7.
You have a few options:
A: Have self.UpdateCurrent and the other methods used by self.battery_control_d already initialized. (never initialize to None)
B: when self.UpdateCurrent and/or the others are updated recreate/update self.battery_control_d.
C: Use a name reference and get the value of that attribute when accessed from self.battery_control_d.
The first two are pretty self explanatory but it seems you are interested in option C so I will expand on it:
You can create a class with several property(ies) that retrieve an attribute from your original object when accessed.
class VarReference(object):
def __init__(self, inst, update_name, get_name, value1, value2):
self.inst = inst
self.update_attr_name = update_name
self.getter_attr_name = get_name
self.value1 = value1
self.value2 = value2
#property
def get(self):
return getattr(self.inst, self.getter_attr_name)
#property
def update(self):
return getattr(self.inst, self.update_attr_name)
class Test(object):pass #dummy class for my demo
self = Test()
self.UpdateCurrent = self.GetCurrent = None
ref_obj = VarReference(self,"UpdateCurrent","GetCurrent",None,1)
>>> print(ref_obj.get)
None
>>> self.GetCurrent = lambda:5
>>> ref_obj.get
<function <lambda> at 0x1057d5488>
>>> ref_obj.get()
5
If you are unfamiliar with property basically the function that it decorates is called when the attribute name is accessed on an instance (and therefore retrieving an attribute of the original instance)
so you would in this case write the initialization for self.battery_control_d like this:
self.battery_control_d = OrderedDict([('Current',VarReference(self, "UpdateCurrent", "GetCurrent", None, 1)),
('Cell Voltage',VarReference(self, "UpdateCellVoltage", "GetCellVoltage", 0, 24)),
('Cell Temperature',VarReference(self, "UpdateCellTemperature", "GetCellTemperature", 0, 24))
])
then self.battery_control_d["current"].get would be the result of getattr(self,"GetCurrent") which is equivalent to self.GetCurrent dynamically.
If you really want to use VALUE[0] and VALUE[1] instead of VALUE.update and VALUE.get then you can just override __getitem__ of the class as well, although I'd highly recommend switching to more verbose solutions:
class VarReference(object):
...
def __getitem__(self,item):
if item==0:
return self.update
elif item == 1:
return self.get
elif item ==2:
return self.value1
elif item == 3:
return self.value2
else:
raise IndexError("Can only get indices from 0 to 3")