Python function getting extra argument from somewhere

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Typhaceae
Newbie
Posts: 13
Joined: Thu Apr 23, 2015 6:41 pm
Contact:

Python function getting extra argument from somewhere

#1 Post by Typhaceae »

I'm trying to set up setters for my character data class, so I never have to change data from outside the class itself. As an example:

Code: Select all

        class CharacterClass:
            name = "No name";
            approval = 0;
            
            def ChangeApproval(reactionAmount):
                approval += reactionAmount;
                return
This looks right to me, but trying to run it from a label produces an error:

Code: Select all

        $randomNPC = CharacterClass();
        $randomNPC.ChangeApproval(1);
        "NPC approval: [randomNPC.approval]"
This results in "Changeapproval() takes exactly 1 argument (2 given)". I thought I was only giving it a single argument, the int (1) that was passed into it. Am I messing up my syntax?
Last edited by Typhaceae on Sat Apr 25, 2015 9:31 am, edited 1 time in total.

User avatar
Zetsubou
Miko-Class Veteran
Posts: 522
Joined: Wed Mar 05, 2014 1:00 am
Completed: See my signature
Github: koroshiya
itch: zetsuboushita
Contact:

Re: Pythin function getting extra argument from somewhere

#2 Post by Zetsubou »

In python, class methods also receive an instance of self (referring to the object upon which the method is called).
So change "def ChangeApproval(reactionAmount):" to "def ChangeApproval(self, reactionAmount):".
Any further methods created in your CharacterClass class will also need to define self as the first argument.
Finished games
-My games: Sickness, Wander No More, Max Massacre, Humanity Must Perish, Tomboys Need Love Too, Sable's Grimoire, My Heart Grows Fonder, Man And Elf, A Dragon's Treasure, An Adventurer's Gallantry
-Commissions: No One But You, Written In The Sky, Diamond Rose, To Libertad, Catch Canvas, Love Ribbon, Happy Campers, Wolf Tails

Working on:
Sable's Grimoire 2

https://zetsubou.games

Typhaceae
Newbie
Posts: 13
Joined: Thu Apr 23, 2015 6:41 pm
Contact:

Re: Pythin function getting extra argument from somewhere

#3 Post by Typhaceae »

Oh hey, that's perfect, I didn't know that about python... thank you! In case anyone else trips over this looking at the same problem, I also had to add self to any variables I wanted ChangeApproval to modify within the class's scope, so

Code: Select all

   def ChangeApproval(reactionAmount):
                approval += reactionAmount;
                return
became

Code: Select all

   def ChangeApproval(reactionAmount):
                self.approval += reactionAmount;
                return
Thank you again! :)

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Pythin function getting extra argument from somewhere

#4 Post by trooper6 »

Also, Python doesn't really use getters/setters in the way that, say, Java does.
Rather than creating a method to add a number to a class's variable, it is more Pythonic to just add to that variable.

Here is how I would make your class and then change variables:

Code: Select all

class CharacterClass():
    def __init__(self, name="No Name", approval=0):
        self.name = name
        self.approval = approval
Then when you want to change the approval rating:

Code: Select all

$randomNPC = CharacterClass()
$randomNPC.approval += 1
If you are coming to Python from another language like Java (which is what I did), I think you would really benefit from taking Codeacademy.com's free Beginner Python course. It won't take you long.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

Typhaceae
Newbie
Posts: 13
Joined: Thu Apr 23, 2015 6:41 pm
Contact:

Re: Pythin function getting extra argument from somewhere

#5 Post by Typhaceae »

I'll definitely take the class when I get some time, I know for a fact that 90% of what I'm doing is unpythonic as all get-out.

I read a bit about how ren'py interacts with __init__, but I still don't entirely understand it: I know if I borrow your example,

Code: Select all

def __init__(self, name="No Name", approval=0):
gets run every time an instance of CharacterClass gets created, but if I start a new game that has a bunch of character creation & initialization blocks in init python:. change those values through gameplay, save the game, then load it, will the __init__ run a second time and wipe out my altered values, or is it safer to think of it as something closer to a class constructor?

I know I've already noticed similar issues with rollback: picking a menu choice that runs ChangeApproval, mousewheeling back, then making the same choice results in approval being added to twice, which obviously isn't desireable from a balance standpoint.

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Pythin function getting extra argument from somewhere

#6 Post by trooper6 »

That init is just a class constructor and it is only run once, when you create the object.

But what you are really talking about it sketchiness with rollback and saving. As for rollback and saving, there is a funny thing that happens with Ren'py regarding that.
This page here talks about saving and rollback: http://www.renpy.org/doc/html/save_load_rollback.html
The important quote is this:
"The python state consists of the variables in the store that have changed since the game began, and all objects reachable from those variables. Note that it's the change to the variables that matters - changes to fields in objects will not cause those objects to be saved."
But what all this really means--as I have been told--is that objects and variables should be created within the save block rather than before it to solve all those problems.

So not this:

Code: Select all

init python:
    randomNPC = CharacterClass()
but this:

Code: Select all

label start:
    $ randomNPC = CharacterClass() 
Also, just so you know, look at this class:

Code: Select all

class CharacterClass():
    isAlive = True
    groupInv = []
    def __init__(self, name="No Name", approval = 0):
        self.name = name
        self.approval = approval
isAlive and groupInv are class variables while name and approval are instance variables. They aren't exactly the same.
This is a good discussion of the differences: http://stackoverflow.com/questions/8959 ... -in-python
But note:

Code: Select all

$ tim = CharacterClass("Tim")
$ jane = CharacterClass("Jane")
$ tim.groupInv.append("Sword")
$ jane.groupInv.append("Shield")
So what happens if you print tim's groupInv? You get "Sword" and "Shield" because all CharacterClass's share the same groupInv. This only becomes a thing for mutable types like lists and sets. If Tim dies and you:

Code: Select all

$ tim.isAlive = False
tim gets a tim-specific version of isAlive which is now False. everybody else's isAlive is still True.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

Typhaceae
Newbie
Posts: 13
Joined: Thu Apr 23, 2015 6:41 pm
Contact:

Re: Pythin function getting extra argument from somewhere

#7 Post by Typhaceae »

Okay wow holy cow, I had no clue about the class versus instance variable distinction, that could've sneaked up on me and delivered an unpleasant surprise... are mutable types instanced if they're declared in the __init__ block? Like, if I wanted to guaranteed that everyone had their own, personal version of groupInv, could I get away with just shifting it two lines and saying:

Code: Select all

class CharacterClass():
    isAlive = True
   
    def __init__(self, name="No Name", approval = 0):
        groupInv = []
        self.name = name
        self.approval = approval
, and then using manual appends (or whatever) to populate the list with content?

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Python function getting extra argument from somewhere

#8 Post by trooper6 »

Yep, by putting the groupInv list in the init method, it becomes an instance variable and each specific instance of CharacterClass would have its own, personal version of groupInv and then you'd have no problems there at all.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

Typhaceae
Newbie
Posts: 13
Joined: Thu Apr 23, 2015 6:41 pm
Contact:

Re: Python function getting extra argument from somewhere

#9 Post by Typhaceae »

Outstanding, thank you so much for your incredibly detailed responses- I'm learning a lot about python this morning. :)

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Python function getting extra argument from somewhere

#10 Post by trooper6 »

No probs! Ever since I learned about the situation with class mutable variables, I've been trying to figure out some cool way to use it in a program.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

User avatar
PyTom
Ren'Py Creator
Posts: 16096
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Python function getting extra argument from somewhere

#11 Post by PyTom »

Note that static fields (those on the class) generally aren't saved in Ren'Py. In Python, classes themselves are objects, and saved according to the same rules - except you can't actually save the class, only the instances of the class, due to limitations in pickle.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Python function getting extra argument from somewhere

#12 Post by trooper6 »

That is very good information to know! Thanks PyTom!
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

User avatar
morte111
Newbie
Posts: 4
Joined: Tue Mar 31, 2015 8:31 pm
Projects: AnimeGen
Location: Australia
Contact:

Re: Python function getting extra argument from somewhere

#13 Post by morte111 »

Thank you!
I found this thread mere minutes after effectively discovering the same thing albeit symptomatically
- this thread at least confirms WHY it was not working :)
- i.e. use of class variables, not being saved due to limitations in pickle, thx PyTom! :)

In my specific case, using Ren'Py v6.99.3, I was having no luck getting game state to correctly re-load two variables
(both 8 element lists of integers)

I should add I also tried other versions 6.17, 6.18 for SDL vs SDL2-related functionality, but this variable save/load is not a version specific issue.

After reading the doco numerous times, specifically
http://www.renpy.org/doc/html/persistent.html
I knew I did not want persistant or multipersistant variables, these had to be save-specific.

and then after
http://www.renpy.org/doc/html/save_load_rollback.html
I gathered that the key was to init any variables I wanted to be saved,
in or after the label start:
NOT in an init: or init python: block

Since I was still not having much success,
I started on a few red herrings:
first related to SetVariable and SetScreenVariable,
which as it turns out are separate from non-screen variables,
and which cannot be set outside of a screen language action anyway

When I saw this thread:
http://lemmasoft.renai.us/forums/viewto ... le#p370214
I tried to set up a basic class even without an __init__ constructor (my bad)
like so:

Code: Select all

init python:
##############################################################################
# bodypart.ix - list - item index for each bodypart
# bodypart.co - list - rgb  index for each bodypart
  class bodypart:
    ix=[1]*8
    co=[0]*8

which I then instantiated from label start:

Code: Select all

label start:
  $bp=bodypart()
Even after I had moved the variable initialisation to label start:
no luck.

I was becoming convinced that my variables (if they were even being saved at all)
were being magically overwritten by some init code at game reload

I then tried to set up some code which used try-except NameError blocks in a label after_load:
such that the class would only be instantiated once on game start, and never on a reload
That was also ineffective in terms of variable reloading,
although debug/tracing showed that the try-except logic was working correctly.

I then became paranoid about exactly was meant by
'When a game is loaded, the state of the game is reset (using the rollback system described below) to the state of the game when the current statement began executing.'
thinking perhaps that python statements were not being treated as save-points
even when they were single $ lines not python: blocks
So I littered my code with unnecessary RenPy narration style text statements to pad out the code between variable assignments.
Still no luck.

I was not clear as to exactly what was meant by this in the save_load_rollback doco.
It appears to contain a typo/redundancy of the word 'is' (or maybe missing 'ring' after 'refer'?)
which obfuscates the meaning somewhat:
Python variables that are not changed before the game begins will not be saved. This can be a major problem if a variable that is saved and one that is refer to the same object. (Alias the object.)
So I then took a leaf from Philat & Trooper6' posts in this thread:
http://lemmasoft.renai.us/forums/viewto ... le#p370309
and went back to basics using a simple var definition after label start, incrementing it after each call screen iteration in the main game loop,
and I found that the simple variable WAS being saved and reloaded correctly every time,
yet my class variables were not.

I tried this:

Code: Select all

renpy.retain_after_load()
which seemed to work only when I played the game, changed some values, then reloaded from within the running game.
If I quit and then reloaded rather than start, it still reloaded the initial values not the updated ones.

Finally, I bit the bullet, dropped the class definition and went with a simple definition after label start:

Code: Select all

label start:
  python:
    bpix=[1]*8
    bpco=[0]*8
Et Voila, perfection. Save & Reload now picked up the correct values.
So for now, lesson learned I think, K.I.S.S.
(or at least, do not rely on class variables being saved correctly)

So anyway, big thanks again,
These forums are a lifeline, I would be absolutely lost without them and contributions from PyTom and the veterans!
- the doco,wiki and tutorial code notwithstanding :)

Post Reply

Who is online

Users browsing this forum: Google [Bot]