Python Trouble...

Afforess

The White Wizard
Joined
Jul 31, 2007
Messages
12,239
Location
Austin, Texas
I'm busy convert the Dynamic City Development Mod into a gameoption, and I've hit a snag in the python. I created a new python definition is CvUtil.py to check whether or not my new option is enabled. (Code Here:
Spoiler :
Code:
#Afforess Investment Start
        
def isNoInvestment(self):
    if gc.getGame().isOption(GameOptionTypes.GAMEOPTION_NO_INVESTMENT):
        Self.No_investment = True
    else:
        Self.No_Investment = False
#Afforess Investment End
), and then referenced the code several times in CvMainInterface, so that the new icons only show if the game option is not enabled. However, this second code, doesn't seem to work. I get python exceptions. Also, I should note, I am using BUG 4.0 as my python base.

Here's an example of my troubled python code (And despite what it seems like, my code is tabbed, not spaced. However my computer refuses to copy-paste it correctly.):

Code:
        #Lord Olleus DCD / 2nd May 09
 [COLOR=Red]       if CvUtil.isNoInvestment():[/COLOR]
            if Self.No_investment == True:
                screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 105, PanelStyles.PANEL_STYLE_STANDARD ) 
            else:
                screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 130, PanelStyles.PANEL_STYLE_STANDARD ) 
        #Lord Olleus / End
The python exception is for this piece of code (the red text is line 2379), and many others I would assume...

Traceback (most recent call last):

File "CvAppInterface", line 81, in preGameStart

File "CvScreensInterface", line 71, in showMainInterface

File "CvMainInterface", line 2379, in interfaceScreen
And idea what the correct code for this is?
 
I am not an expert in python. But "self" is not a global. CvUtil has a "self", and your first function is inside CvUtil so it is creating the variable No_investment inside CvUtil. Your second function is in some other class, or at any rate it is not inside CvUtil. So it has a "self" but it is not the same "self". I am sure EmperorFool can come up with a good analogy for why each object has its own self.

In general creating a function for what really amounts to a single line of code rarely provides any benefit. In your second function, why don't you just call gc.getGame().isOption(GameOptionTypes.GAMEOPTION_NO_INVESTMENT) directly? This should be a fast call, and caching it as a local variable may not provide much benefit anyway.
 
You try to check, which value CvUtil.isNoInvestment() returns you back, but you do not define any return value in CvUtil.isNoInvestment().
-> Place after Self.No_investment = True a return True, or Return Self.No_investment (same with the else part).

Okay, this would be easier:
PHP:
#Afforess Investment Start
        
def isNoInvestment(self):
    if gc.getGame().isOption(GameOptionTypes.GAMEOPTION_NO_INVESTMENT):
        Return True
    else:
        Return = False
#Afforess Investment End

And

PHP:
        #Lord Olleus DCD / 2nd May 09
        if CvUtil.isNoInvestment():
            screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 105, PanelStyles.PANEL_STYLE_STANDARD ) 
        else:
            screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 130, PanelStyles.PANEL_STYLE_STANDARD ) 
        #Lord Olleus / End


(Intendation is probably not right in the code [but visual])
 
The function called in the if statement is not returning a value. You are doing an "if nothing-happens-here-to-give-me-a-true-or-false". It should return True or False. Currently the function is setting a member variable, but not returning a value. These are two different things - you should probably have one funtion to set the variable and another to get the value (although that is rather redundant - you can just check the variable after it is set).

You already have the Self.No_Investment value defined. That already should tell you what you need to know. You don't need two different ways to find out the same information. Where is Self.No_Investment set? Wherever it is, it should be doing what you are doing in the isNoInvestment function.

In essence, you could aparently do without the function. Wherever self.No_Investment is being initialized, do what the function is doing. Then just use self.No_Investment. The line "if CvUtil.isNoInvestment():" does not seem to be needed.
 
Figures. Ask a question. Get three unique answers.

Anyways, I just tried your method, The_J. I get a new, really strange python error now, in files I never touched.

Traceback (most recent call last):
File "BugInit", line 98, in callInits
File "CvScreensInterface", line 1188, in init
File "CvScreensInterface", line 159, in createMilitaryAdvisor
File "CvBUGMilitaryAdvisor", line 82, in __init__
File "UnitGrouper", line 292, in __init__
File "UnitGrouper", line 109, in __init__
File "UnitGrouper", line 88, in __init__
File "BugUtil", line 141, in getPlainText
File "BugUtil", line 153, in getText
ArgumentError: Python argument types in
CyTranslator.getText(CyTranslator, unicode, tuple)
did not match C++ signature:
getText(class CyTranslator {lvalue}, char const *, class boost::python::tuple {lvalue})

God-Emperor, I also tried your method, but had no more success than my original way. In fact, I got the same error...

Davidallen,

The reason I didn't do this is that I didn't know how to write, in code, "If the gameoption NO_INVESTMENT is NOT selected, do this." I had a few times where I needed some code to work one way, and other code, the other way. A function seemed the easiest way to do this. Plus, I'm a python newbie (although, syntactically [if that's a word. Firefox didn't underline it anyways]; python makes more sense than Java and C++.)
 
The reason I didn't do this is that I didn't know how to write, in code, "If the gameoption NO_INVESTMENT is NOT selected, do this."

I don't understand. You are using if ... else successfully inside your function. If you are trying to write something like "only do the else part", then I think what you want is "if not":
Code:
   if not x == 2:
      do something
 
I don't understand. You are using if ... else successfully inside your function. If you are trying to write something like "only do the else part", then I think what you want is "if not":
Code:
   if not x == 2:
      do something

See, I'm a newbie to python. Didn't even know that existed. :lol:
 
Hehe, this is a funny thread. :) Let me see if I can shed some light on the unaddressed issues.

First, I gave Afforess some sample code for checking a game option using a function, but all I do is return the game option. I use a function because the first one I did this for was No Espionage, and the function had to first verify that the game was 3.17+. Plus

Code:
if GameUtil.isNoEspionage()

reads so much nicer than

Code:
if CyGame().getOption(GameOptionTypes.GAMEOPTION_NO_ESPIONAGE)

So, self. "Self" is just the conventional name for the object on which you have called a function. You can call it anything you want. It is like "this" in C++ except that you have to explicitly declare it in the parameter list.

However, when you call a module function there is no object passed to the function, so you need to remove the "self" from that function. The error you were probably getting is that the isNoInvestment() function takes 1 parameter but got none. You didn't paste the actual error, just the line number. ;)

As for caching the result, I'm with davidallen here: it's not worth the trouble. Here's how the code should look:

Code:
[B]# CvUtil.py[/B]

if isNoInvestment():
    return CyGame().getOption(GameOptionTypes.GAMEOPTION_NO_INVESTMENT)

[B]# CvMainInterface.py[/B]

import CvUtil

if CvUtil.isNoInvestment():
    screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 105, PanelStyles.PANEL_STYLE_STANDARD ) 
else:
    screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 130, PanelStyles.PANEL_STYLE_STANDARD )

Finally, I highly recommend putting your function into a new module. Any time you modify one of the files BUG ships you make more work for yourself when you upgrade to the latest release. I think long and hard before I ever include a module from BTS in BUG for this very reason, and BTS doesn't patch as often. Modules are a great way to organize code in Python; use them to your advantage. Compare how many modules I have in BUG with how many ship with BTS.

Oh, one more thing. "Not" merely inverts any boolean expression. If the expression after it isn't a boolean, it is first converted to a boolean.

Code:
not False -> True
not True -> False
not 5 -> False
not 0.0 -> True
not "" -> True
not None -> True

You combine boolean expressions using "and" and "or" and parentheses to group.

Code:
if (x > xMax or y > yMax) and not CvUtil.isNoInvestment():

Very important: Python uses short-circuit evaluation which means that it stops evaluating the expression as soon as the result is determined. For example, "False and <anything>" is always False so it won't evaluate the <anything> part. Same with "True or <anything>".
 
First, I gave Afforess some sample code for checking a game option using a function, but all I do is return the game option. I use a function because the first one I did this for was No Espionage, and the function had to first verify that the game was 3.17+.

I did use that kindof as a base.

Finally, I highly recommend putting your function into a new module. Any time you modify one of the files BUG ships you make more work for yourself when you upgrade to the latest release. I think long and hard before I ever include a module from BTS in BUG for this very reason, and BTS doesn't patch as often. Modules are a great way to organize code in Python; use them to your advantage. Compare how many modules I have in BUG with how many ship with BTS.

I thought about it, but my lack of experiance in these matters, coupled with laziness, and that It would have been a lot of work for a few lines...

Although, at one point, I had it in the RoMCvUtil.py, but I found that the game didn't check that file for errors, as it overwrites the CvUtil only after a map has loaded, so it wasn't terribly useful as far as debugging went. (And knowing me, I need lots of debugging.)
Oh, one more thing. "Not" merely inverts any boolean expression. If the expression after it isn't a boolean, it is first converted to a boolean.

Code:
not False -> True
not True -> False
not 5 -> False
not 0.0 -> True
not "" -> True
not None -> True

You combine boolean expressions using "and" and "or" and parentheses to group.

Code:
if (x > xMax or y > yMax) and not CvUtil.isNoInvestment():

I was sort of hoping there was a "not" expression of some kind, but I didn't know it. I knew in C++, there was the !, which could serve as a not, but never noticed the not.

Very important: Python uses short-circuit evaluation which means that it stops evaluating the expression as soon as the result is determined. For example, "False and <anything>" is always False so it won't evaluate the <anything> part. Same with "True or <anything>".
I noticed. I tried to pass a result (even just plain old "pass" once,) and got loads of errors, because it was ignoring half the function then.

As for this being a funny, post, at least it's all at my expense. :lol:
 
The funny part for me was that everyone seemed to have different answers for each thing. :)

You can use Python's "not" just like C's "!" in the same way that "and" and "or" are equivalent to "&&" and "||".

I'm not quite sure I follow you about one module not being checked for errors by Civ. BTS starts by loading the CvAppInterface module which loads other modules, and so on. But as soon as a module is loaded, it is parsed. Also, I assumed you'd have more to this feature than a single option-checking function and that code would go into a module.
 
I'm not quite sure I follow you about one module not being checked for errors by Civ. BTS starts by loading the CvAppInterface module which loads other modules, and so on. But as soon as a module is loaded, it is parsed. Also, I assumed you'd have more to this feature than a single option-checking function and that code would go into a module.

Yes, true, But the RoMUtils.py will parse fine at first, and throw exceptions only once a map has been loaded. This is probably because this file is loaded in XML, to overwrite the CvUtil, and the XML isnt checked until your loading a mapscript or scenario.
 
Okay, EmporerFool, I just tried your method, exactly like you have it, and the first time the game hit my code, it started an infinite loop, and finally broke once it hit the limit for loops (something like 1000 loops)...

I would post the whole thing, but it would exceed the char limit.

RuntimeError: maximum recursion depth exceeded
ERR: Python function preGameStart failed, module CvAppInterface

I love all the error messages, each one unique...
 
Going back to the first post, there is another point that I hadn't noticed:

"Self" and "self" are not the same variable. Python is case sensitive. Be careful about that.
 
Hmm, I just noticed my code is semantically the same as The_J's ignoring that "Return" is not "return" and "return = False" is a typo. I don't see how it could go into recursion at all, let alone infinite. Please post what your code looks like now.

BTW, whenever you have

Code:
if <boolean-test>:
    return True
else:
    return False

you can replace it with

Code:
return <boolean-test>

If the test isn't a boolean already, you can use

Code:
return bool(<boolean-test>)
 
Okay, here's my latest code, in CvUtils (Hey look, the tabs magically started working. Go figure.)
Code:
#Afforess Investment Start 
def isNoInvestment():
	if gc.getGame().isOption(GameOptionTypes.GAMEOPTION_INVESTMENT):
		return True
	else:
		return False
#Afforess Investment End
and an example of some of the code in Cvmaininterface
Code:
		if CvUtil.isNoInvestment(): 
			screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 105, PanelStyles.PANEL_STYLE_STANDARD ) 
		else:
			screen.addPanel( "CityScreenAdjustPanel", u"", u"", True, False, 10, 44, 238, 130, PanelStyles.PANEL_STYLE_STANDARD )

Anyways, this still gets a python exception, but one I don't understand at all.
Traceback (most recent call last):
File "BugInit", line 98, in callInits
File "CvScreensInterface", line 1188, in init
File "CvScreensInterface", line 159, in createMilitaryAdvisor
File "CvBUGMilitaryAdvisor", line 82, in __init__
File "UnitGrouper", line 292, in __init__
File "UnitGrouper", line 109, in __init__
File "UnitGrouper", line 88, in __init__
File "BugUtil", line 141, in getPlainText
File "BugUtil", line 153, in getText
ArgumentError: Python argument types in
CyTranslator.getText(CyTranslator, unicode, tuple)
did not match C++ signature:
getText(class CyTranslator {lvalue}, char const *, class boost::python::tuple {lvalue})
 
Okay, EmporerFool, I just tried your method, exactly like you have it, and the first time the game hit my code, it started an infinite loop, and finally broke once it hit the limit for loops (something like 1000 loops)...
RuntimeError: maximum recursion depth exceeded

Sorry if this is obvious, but scaling for a first year programmer ...

Recursion is when one function calls itself, which calls itself, which calls itself. Don't do this; here is an example:
Code:
def myfunc (a):
   return myfunc(a+1)
The python interpreter itself catches this type of infinite recursion after a while, and gives your message above. It is OK if there is a way for the recursion to terminate; here is an OK example:
Code:
def myfunc (a):
   if a < 2: return 1
   return myfunc(a-1)
This does not *infinitely* recurse.

So please check to make sure your function is not calling itself. It is possible, but less likely, that you have two functions which introduce the infinite recursion; here is another bad example:
Code:
def myfunc(a): return myotherfunc(a)
def myotherfunc(a): return myfunc(a)
 
I do know what infinite recursion is. Anyways, my code was EXACTLY the way emperor fool had suggested, above, when I got that. I haven't the slightest clue as to why it was looping, just that it was.
 
Code:
[B]# CvUtil.py[/B]
if isNoInvestment():
    return CyGame().getOption(GameOptionTypes.GAMEOPTION_NO_INVESTMENT)

I am sure this is not exactly what he meant. If you put this inside "def isNoInvestment(self)", then I think you will agree this is recursive. However, you are obviously past this since you are now getting a translator signature message. Since the translator message doesn't seem to relate to the code you changed, I recommend undoing one or two of your last changes to recover to a working point. Sometimes you may have accidentally changed something else as well I have occasionally fumbled the text editor and accidentally saved an unintentional change in an unrelated file.
 
Did you add a new unit type but forget to provide a <Text> entry for its description?

To fix this edit BugUtil.getText():

Code:
def getText(key, values=(), default=None, replaceFontTags=True):
	"""
	Looks up a translated message in XML with a tuple of replacement parameters.
	It is safe to pass in a single value instead of tuple/list.
	If the key isn't found, the default is returned.
	"""
	if values is None:
		values = ()
	elif not isinstance(values, (tuple, list)):
		values = (values,)
[B]	if isinstance(key, unicode):
		warn("getText - received Unicode key %s", key)
		key = str(key)[/B]
	text = localText.getText(key, values)

Edit: Reversed the arguments to isinstance on the first changed line.

I will include this fix in BUG as well.
 
Did you add a new unit type but forget to provide a <Text> entry for its description?

To fix this edit BugUtil.getText():

Code:
def getText(key, values=(), default=None, replaceFontTags=True):
    """
    Looks up a translated message in XML with a tuple of replacement parameters.
    It is safe to pass in a single value instead of tuple/list.
    If the key isn't found, the default is returned.
    """
    if values is None:
        values = ()
    elif not isinstance(values, (tuple, list)):
        values = (values,)
[B]    if isinstance(key, unicode):
        warn("getText - received Unicode key %s", key)
        key = str(key)[/B]
    text = localText.getText(key, values)
Edit: Reversed the arguments to isinstance on the first changed line.

I will include this fix in BUG as well.

I seem to be trading an error for another, more interesting error. I commented out my changes in CvMaininterface and CvUtils, but and made the above change, but now get a plethora of errors. So many that I can't post them here, so I attached them.
View attachment 228095
 
Top Bottom