Ren'Py Ren'Py doesn't respect static methods?

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,003
This is my first time using renpy this week, and I found something odd.

Now I'm not the biggest python user, I'm a C# kind of programmer, but it seems renpy doesn't respect static methods? Am I doing anything wrong?

I have a class with a static method and class to represent a character's level of dress
Python:
#somewhere
init Python:
    class Lilith_AA:
        hasPants = False
        hasUnderwear = False
        hasShoes = False
        hasShirt = False

        #Through testing I found that renpy does not respect this, ie this function does not work as it should, does not clear variables
        @staticmethod
        def setNude():
            hasPants = False
            hasUnderwear = False
            hasShoes = False
            hasShirt = False
I've notice these are not variables that renpy manages, so if I want 'undo' what the player does, such when going backwards, I have to directly code in this behavior, something simple like
Python:
$Lilith_AA.hasPants = False #having this here allows pants to come off when going backwards through dialog
"Some dialog, character puts on pants!"
$Lilith_AA.hasPants = True
I was having issues where renpy would still remember clothes when going to main menu and starting again, so obviously I need to reset everything at the start of the game. Hence the static method to clear the variables, which I call out at the start
Python:
label Day_1: #Main Logic
    call Day_1_Prolog

    $Lilith_AA.setNude() #considerer this part of initilization so that the variable data doesn't cary over, at least not too badly

    jump Day_1_Bathroom # WARNING NON LINEAR #
However, It doesn't appear to work. I had to make some sanity check code
Python:
label Day_1_Bathroom_viewMirror:
    scene Apartment Bathroom with dissolve

    # testing something here
    $Lilith_AA.setNude() # testing something here
    if Lilith_AA.hasPants == True:
        "pants"
    else:
        "no pants" # This should always display, but after putting on pants, the test fails and prints "pants"

    #looks like renpy does not respect static function behavior? this is wierd
It's fine if I have directly reset each property, I've already started getting used to having to make tedious lengthy code with renpy, I just had hoped this was the one place I could make concise simple code, but I guess even fundamental python behavior can't be expected to work. I've already noticed some other inconsistencies in code behavior, but I had caulked it up to ren'py quirks, such as the pain in the butt ATL, but this is the one case I just found python is just plain not working. works just fine when I run the code in a dedicated anaconda interpreter, I just thought I would ask if any more experienced python/ren'py dev's notice code inconsistencies with renpy. like, is it running some older version of python that doesn't handle static methods/variables correctly?
 

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
703
2,722
I know nothing about ren'py, but I think that what you need is a class method not a static method. In python static methods don't have access to the class state, so you can't change class variables from within a static method.
Try:
Code:
    @classmethod
    def setNude(cls):
        cls.hasPants = False
        cls.hasUnderwear = False
        cls.hasShoes = False
        cls.hasShirt = False
 
  • Like
Reactions: Saki_Sliz

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,003
In python static methods don't have access to the class state
I want to say, 'that's not how it works' because the variables are also static... technically?... However, I was a big Java programmer for a few years before I converted to C#, and the two programming languages handle static and Final/Sealed different compared to each other, especially in terms of inheritance, one of the reasons I converted to C# is I found out I was trying to force Java to behave more like C#, and it would be easier to use C# since it better aligns with how I believe code should work. So it may just be an issue with python having a different philosophy on the subject.

I tried @classmethod with no luck...

But then I noticed I didn't have the cls. token on each variable, that fixed the problem, Thanks!

I'm also really hating this SoftType interpretive language BS, its really triggering my Safe Code OCD flags, but I can't do much about it, but I will say it is really nice and refreshing to program a project with such a high level code, its fast... it just feels dirty and unsafe.
 

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
703
2,722
Nothing in Python is static/private in the Java sense. Even if you make something a private attribute with a leading double underscore (__foo = 'bar') you can still access it, the interpreter just mangles the bound name. Also everything is a first class object so you can override anything with everything - for good or bad print = 42 .
 
  • Like
Reactions: Saki_Sliz

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
I want to say, 'that's not how it works' because the variables are also static... technically?...
Nothing is really static in script languages, and even less in Python. Between the unmutability and the ...attr core functions, it would be really difficult. The only thing available is protected attributes/methods (leading double underscore) ; yet there's way to access them if you really want.

But anyway the name of the decorator should have warned you, it's "staticmethod". And the lack of self in the arguments list should have been a hint that it don't have access to the object. It's the method that is static, and therefore independent from the class. Such methods are generally used to group some external code that is associated with the class.

By example if you have a class that need to install some dependencies, you would have:
Python:
class MyClass( object ):
    def __init__( self, [...] ):
        [...]

    [...]

    @staticmethod
    def install():
        [...]

MyClass.install()

obj1 = MyClass( [...] )
obj2 = MyClass( [...] )
This is a bit cleaner than having a totally independent function to install the dependency, and a bit easier than having a test in "__init__" to see if you need to install them or if they are already installed.

This being said, objects are really relative in Python ; "self" is just the pointer to an object, therefore anything can be parts of a object, even a static method or a purely external function:
Python:
init python:
    class MyClass( object ):

        def __init__( self ): self.value = "boo"

        @property
        def boo( self ): return self.value

        @staticmethod
        def baa ( self ): self.value = "bee"

    def notEvenMethod( self ): self.value = "baa"

define obj = MyClass()

label start:

    "before: [obj.boo]"
    $ MyClass.baa( obj )
    "after static: [obj.boo]"
    $ notEvenMethod( obj )
    "after function: [obj.boo]"
[/quote]

But it would defeat the interest to have a static method.


[QUOTE="Saki_Sliz, post: 9666765, member: 591430"]
I'm also really hating this SoftType interpretive language BS,
[/quote]

Python have a strong typing... It's just that the anything being unmutable, a value exist only until you replace it by something else:
[code=python]
init python:

    def yes(): return u"abc" + u"def"

    #  Both are string, but an Unicode string and a Byte string are different types
    # and you do NOT addition different types.
    def boom(): return u"abc" + b"def"

label start:

    $ a = 1
    $ aID = id( a )
    "The variable 'a' have '[aID]' as ID."
    $ a += 1
    $ aID = id( a )
    "The variable 'a' is now a totally different variable, that have '[aID]' as ID."
    $ t = yes()
    "Addition works when types are similar: '[t]'"
    "And now Ren'Py 8.x will insult you."
    $ boom()

its really triggering my Safe Code OCD flags,
Duck typing is not for people with OCD. As long as it quack it's a duck, even if it's 2 meters tall, fly and throw flames. And Python's "Easier to Ask for Forgiveness than Permission" tendency make it worse ; by default it's your responsibility to ensure that all ducks will quacks. But in the end it's just a habit to take.


it just feels dirty and unsafe.
If it's what you think about Python, for the sake of your sanity, never come close to Perl ;)
Code:
sub normal{ return "Maybe"; }
sub crazy{ $_[0] = "Yes"; }
sub insane{ return $_[0] = "Totally"; }

my $a = "No !";
print "$a\n";   # Will print 'No !'
$a = normal();
print "$a\n";   # Will print 'Maybe'
crazy( $a );
print "$a\n";   # Will print 'Yes'
my $b = insane( $a );
print "$b, I said $a\n";   # Will print 'Totally, I said Totally'
 

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,003
the name of the decorator should have warned you, it's "staticmethod". And the lack of self in the arguments list should have been a hint that it don't have access to the object
yep, its just that C# which I'm more familiar with has different way of thinking of these things, they also don't have access to any instantiated object, but it can still work with other static variables because they are static, meaning it belongs to the class, not the object. C# is able to distinguish between the 'description' of a class vs the 'instantiated' version of a class. But given how you describe python and how it took the class method to get my code to work, python handles static differently than C#, treating the static parent class as if its its own instantiation, where as with C# this is more of an virtual and override function type behavior.

Python's "Easier to Ask for Forgiveness than Permission" tendency make it worse
XD

default it's your responsibility to ensure that all ducks will quacks.
which is why I guess I don't see this in high level production code, cuz we design code around the fact that we can't trust the programmer to be competent, or at least in my field of programming experience.

Isn't perl one of those functional or declarative programming languages? Those are technically pretty safe... just the syntaxt isn't very human friendly at first glance. I'm actually developing my own declarative language, as a sort of hybrid between programing and note taking, but that project is on pause for now.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
yep, its just that C# which I'm more familiar with has different way of thinking of these things, they also don't have access to any instantiated object, but it can still work with other static variables because they are static, meaning it belongs to the class, not the object.
It's still possible with Python, it's just that you need to explicitly reference the class:
Python:
init Python:
    class Lilith_AA:
        hasPants = False
        hasUnderwear = False
        hasShoes = False
        hasShirt = False

        @staticmethod
        def setNude():
            Lilith_AA.hasPants = False
            Lilith_AA.hasUnderwear = False
            Lilith_AA.hasShoes = False
            Lilith_AA.hasShirt = False
And here having a static method make sense since it clearly point that it's not a reference to the instance.


C# is able to distinguish between the 'description' of a class vs the 'instantiated' version of a class. But given how you describe python and how it took the class method to get my code to work, python handles static differently than C#, treating the static parent class as if its its own instantiation, where as with C# this is more of an virtual and override function type behavior.
You should keep in mind that compiled and interpreted languages have mandatory differences regarding their dynamism ; see it as the compilation freezing everything you defined. Script languages not being compiled, they will always end being more dynamics, either by side effect or, for Python, directly by design.
The way Python handle "static" as you imagine it, is in fact pure dynamism ; a class is an attribute like any others and can be extended/changed like any other attributes.
Python:
init python:
    class MyClass( object ):
        def __init__( self, name ): self.name = name

    def show( self ): return self.staticVar + " [from {}]".format( self.name )

define obj1 = MyClass()
define obj2 = MyClass()

label start:
    $ setattr( MyClass, "staticVar", "I'm a static var" )
    $ setattr( MyClass, "show", property( show ) )
    "[obj1.show] <-> [obj2.show]"
It define a nearly blank class, then add it a static attribute, and a property that display this attribute.

But be careful, because classes are not saved by Ren'Py. And even if you for Ren'Py to save them, it's only the "__dict__" that will be handled by Pickle, not the static part.
As long as it's not code, there's a workaround to this, but it defeat the interest to have static values. Therefore it's preferable that you rely on an external object to handle your static values:
Code:
init python:
    class Static( renpy.python.RevertableObject ):
        def __init__( self ):
            self.__Lilith_AA = { "hasPants": False, "hasUnderwear": False, "hasShoes": False, "hasShirt": False }

        def Lilith_AA( self, *args ):
            if len( args ) == 1: return self.__Lilith_AA[args[0]]
            else: self.__Lilith_AA[args[0]] = args[1]


    class Lilith_AA( renpy.python.RevertableObject ):
        @staticMethod
        def setNude():
            static.Lilith_AA( "hasPants", False )
            static.Lilith_AA( "hasUnderwear", False )
            static.Lilith_AA( "hasShoes", False )
            static.Lilith_AA( "hasShirt", False )

        def isNude():
            return all( [ static.Lilith_AA( "hasPants" ), static.Lilith_AA( "hasUnderwear" ), static.Lilith_AA( "hasShoes" ), static.Lilith_AA( "hasShirt" ) ] )

default static = Static()
default lilith1 = Lilith_AA()
default lilith2 = Lilith_AA()
I haven't tested it, but unless I made a typo it should works.



Isn't perl one of those functional or declarative programming languages? Those are technically pretty safe...
There's nothing safe with Perl.
It have strong typing, yet you can decide that a static will be in fact an array.
It have separated namespaces, yet you can write in any namespace as long as you know it's "reference".
It have objects, yet you can use it's methods as if they were functions and, here it's like Python, as long as "self" is passed as parameter any function can become a method for any object you want.
And, it was my example, parameters being passed by references, you can directly write inside them if you want.

When I was younger and crazier, I even built a library that had all its code build dynamically, and (worse) built directly in the caller namespace. Just a single function, for the caller to provide the base it wanted for the name of the functions, few parameters regarding how the functions should be built and where the used value where stored.
Imagine a lib/DLL for which you don't know the name of the functions, nor what values they'll proceed, until the moment you decide to link to it...
 
  • Red Heart
Reactions: Saki_Sliz

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,003
It's still possible with Python, it's just that you need to explicitly reference the class:
Oh!
Now I never saw that in any of the tutorials or docs...
but given how the class method works, how I've been seeing python, that matches the pattern I'm starting to see.
That worked perfectly! Thank you.

But then your next set of code. Yeah, I probably want to use classes to save my game state, since I'm more familiar with setting up objects and classes. In fact a lot of my writing is based on player contextual data. I like to have the dialog reflect small contextual cues to make the writing a bit more imersive and interesting (or at least I think it feels like this). I have a contextDay1 'static' class that stores information on what the player does, and I use that to influence the game. such as, if while dressing the player leaves the bathroom before putting on everything, the dialog changes according to her state of dress
You don't have permission to view the spoiler content. Log in or register now.

I'll have to convert that class and all future class to this... revertable version instead...

One problem. I don't want to pester you with questions or make you program for me... but when I am looking into this renpy.python.revertableobject thing... I'm not finding anything. Well I did find something, its one of your old posts actually XD. I can't find any resources talking about the technique you showed, I don't know if its an older repy code thing (the system I'm using for making the character is the obsolete liveComposite because that's all I could find videos on, and I lost the page that showed the more up to date technique).

However, I did try to study what your code looked to be doing, and added this to the Lililth_AA class so I could add and remove items in line with the dialog (and I wanted my code calls to be a bit nicer and concise)
Python:
# in class Lilith_AA
    @staticmethod
    def has(*args):
        if len( args ) == 1: return static.Lilith_AA("has"+args[0])
        else: static.Lilith_AA("has"+args[0], args[1])
       
#somewehre in the script
label Example:
    "no pants"
    lilith1.has("pants")
    "yay pants!"

1671751021124.png

It works like a charm! able to undo (roleback) just like normal renpy, and remembers between saving, closing, and relaunching!

I can not thank you enough, you probably saved me all Christmas weekend worth of suffering trying to figure out how to get this to work. Thank you for the early Christmas present!

also, I've never seen such fuckery before, a function that is both an input, and an output?! you can see I copied the idea to make the 'has' function but that's some python-fu if I've ever seen any!

With this I can get back to focusing on art.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
but when I am looking into this renpy.python.revertableobject thing... I'm not finding anything. Well I did find something, its one of your old posts actually XD.
renpy.python.RevertableObject is just a basic child of object with a bit of internal tweaks to ensure that objects will be fully compatible with the rollback.
As long as you create your class in a "rpy" file, Ren'Py will automatically use it (if you look at "object" in the console, you'll see that it point to "RevertableObject"). But I prefer to always be explicit, because there's people who define their classes in "py" files, and there they end with the default object that can lead to some issue when the player rollback.


I can't find any resources talking about the technique you showed,
It's something really basic, it's just that it's wrote by someone who is a bit crazy. I used an object to centralize all the variables "static" to other classes. This way the said "static" variables are saved with this object since they cannot be saved with the class.

A bit of explanation for the readers:
Python:
    # Ensure that it will be rollback compliant by forcing the inheritance.  
    class Static( renpy.python.RevertableObject ):

        def __init__( self ):
            #  The leading "__" make the attribute protected. You cannot access it outside of
            # the object, nor outside of this class (children cannot access it). Note: there's
            # in fact a way to address it, but complicated enough to remind you that you
            # shouldn't do it.
            #  The name is the name of the related class to make it easily understandable.
            #  The value is a dict, "static" attribute's name as key, its value as value.
            self.__Lilith_AA = { "hasPants": False, "hasUnderwear": False, "hasShoes": False, "hasShirt": False }

        #  The interface that let you set/get the "static" attributes.
        # "*args" is Python magic, it groups all regular arguments into a tuple, 
        # what let you have as many as you need and, more interesting here,
        # being able to call the method with one or two arguments.
        #  "**kwargs" would do the same for the keyword arguments, by
        # creating a dict.
        #  What matter is in fact only the leading "*" or "**", the rest is just
        # a variable name. "args" and "kwargs" being nothing more than a
        # convention for readability.
        def Lilith_AA( self, *args ):
            #  If there's only one arguments, it's a "get", return the value
            # corresponding to the "static" attribute.
            if len( args ) == 1: return self.__Lilith_AA[args[0]]
            #  Else, assume that there's two arguments, then it's a "set",
            # first argument is the name of the "static" attribute, second
            # argument is its new value.
            else: self.__Lilith_AA[args[0]] = args[1]
There's also another way to do this, that would need only one magic method and an internal object created on the fly, but it would be a bit too complicate to explain and clearly dirty.


(the system I'm using for making the character is the obsolete liveComposite because that's all I could find videos on, and I lost the page that showed the more up to date technique).
I guess what you are looking for is .


[...] remembers between saving, closing, and relaunching!
You did right for your test.
And like it's something really important, I jump on the occasion to say it again:

Ren'Py initialize the game only once. Therefore when you want to know if something is saved, you need to close Ren'Py then relaunch it.
If you just go to the main menu and load your save, you'll not know if the value is correct because it have been saved, or if it's correct because Ren'Py have it in its memory.


Thank you for the early Christmas present!
You're welcome.


also, I've never seen such fuckery before, a function that is both an input, and an output?! you can see I copied the idea to make the 'has' function but that's some python-fu if I've ever seen any!
It's probably not frequent in Python. I blame my decades of practice with Perl for this, I have the tendency to not use more functions/methods than strictly needed, not in terms of code, but in terms of interface.
I'm slowly loosing it, but sometime it make a comeback ;)
 

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,003
I guess what you are looking for is .
yep, thats it.

I gave it another read because I remember it having the ability to use groups and attribute tags and I wanted to see if that would be better than my class or not. I don't know if I'll draw characters with enough layers (multiple sets of clothing, animated breasts, etc) to really justify needing the full power of such a system, and I wanted to see how neat or not neat it is to check the state of things, in case I wanted some specific data, or if my lilith.has() function is more concise and to the point, but I may just be re inventing the wheel. and I don't even know if its all that important because I don't want to use 'Lilith_AA' as the main state data, its more, the result of other states that do heavy calculations... but I may use the results (aka Lililth_AA) as part of the AI perception input, but that's tbd if I'll be coding anything like that with this first project.

It's probably not frequent in Python. I blame my decades of practice with Perl for this
yep, weirdness because of Perl makes sense. I haven't even touched perl with a 25 and a half foot pole, but what I hear it is wierd.

anyways, spent the evening converting my classes over and getting everything working