0

I can't figure out what exactly is happening or why:

For whatever reason when I try to call methods on an instance of a custom class I created something keeps passing the instance itself as argument in to the methods, where as it didn't before.

I don't know exactly when it started but it was recently and may have started after I updated to the latest version of 3.9 And I've spent over an hour trying to figure this out. Anyone who can explain this and tell me how to fix it will have my gratitude.

Also I don't know if this will help, but I'm still in Python 3.9, not 3.10 yet.(I should probably update.)

Example Code:

Test Class

class cat_test_class():
    def __init__(self,hat=None,gear=None,weapon=None,name="Kit"):
            self.hat = hat
            self.gear = gear
            self.weapon = weapon
            self.name = name
    
    def __str__():
        return f"{name}'s equipment:\nWeapon = {weapon}\nGear ={gear}\nHat = {hat}"
    
    def test(*args):
        print(f"# of args: {len(args)} \n {args}")
    
    def try_dis(args):
        return args

Tests and Results

Creating an Instance and test 1

kitteh = cat(hat="Platypus Controller",gear="Book of Wumbology",weapon="Bun Sword")

kitteh.test("banana",26,"Mpaw3 player")

Output

Number of args: 4 
 (<__main__.cat_test_class object at 0x0000014554732130>, 'banana', 26, 'Mpaw3 player')

Test 2

kitteh.test()

Output

Number of args: 1 
 (<__main__.cat_test_class object at 0x0000014554732130>,)

Test 3

caet = kitteh.try_dis()

caet

Output

<__main__.cat_test_class at 0x14554732130>

Test 3.1 - type(caet)##

type(caet)

Output

__main__.cat_test_class

Thank you in advance for all answers.

2e0byo
  • 5,305
  • 1
  • 6
  • 26
  • 5
    Instance methods *always* receive the invoking instance as its first argument. `self` is just the conventional name for the parameter, not a special value that *tells* Python to pass the object as the first argument. – chepner Oct 14 '21 at 17:24
  • I have reformatted this question pretty heavily visually, although I would have preferred to take almost all of the headings and bold out entirely. I just find it impossible to read as it was presented, but I recognise this is largely taste. – 2e0byo Oct 14 '21 at 17:28
  • Please fix the indentation of the class – Mad Physicist Oct 14 '21 at 17:32

1 Answers1

0

For whatever reason when I try to call methods on an instance of a custom class I created something keeps passing the instance itself as argument in to the methods, where as it didn't before.

In python, class methods always receive the instance as an argument unless they are decorated with @staticmethod or @classmethod. This instance will be placed in the first argument, so these are equivalent:

class TestClass:
    def meth1(self):
        print(self)

    def meth2(this):
        print(this)

But by convention we use self. I prefer it to JS's this, but even if I didn't I'd use self, because that's what everyone else does.

If you want a method not to recieve the class object, you do this:

class TestClass2(TestClass):
    @staticmethod
    def meth2(arg=None):
        print(arg)

You would do this if you wanted to keep meth2 wrapped up in the class (because it belonged to the same unit) but it never needed to do anything with class state.

Lastly we have:

class TestClass3(TestClass2):
    X = 7
    def __init__(self):
        self.X = 8

    def meth2(self):
        print(self.X) # prints 8

    @classmethod
    def meth3(cls):
        print(class.X) # prints 7

Classmethods are passed the uninstantiated class. Note that cls is purely by convention: change it to self and it will do the same thing.

Edit Previous versions of this answer referred to memory savings by @staticmethod. Apparently I was quite wrong about that, so I have removed it.

2e0byo
  • 5,305
  • 1
  • 6
  • 26
  • RAM usage has nothing to do with it. `TestClass.meth1` is also a unique value. When you *access* it, the descriptor protocol produces a new value of type `method`, which is what actually gets called when you try to call `foo.meth1()`. – chepner Oct 14 '21 at 17:39
  • `staticmethod` does not save memory. Instances don't get their own dedicated copy of each instance method; as chepner said, method objects are created on access. Such method objects become reclaimable immediately after invocation (unless explicitly saved somewhere), so there is no lasting memory impact. – user2357112 Oct 14 '21 at 17:41
  • but if I instantise N objects and call their staticmethods at once, I have only one method, right? Whereas if I instantise N objects and call N nonstatic methods I have N objects in ram _at that point_. I did *not* know that instantising only happend at call, though, and I will update the answer to say so: thanks! – 2e0byo Oct 14 '21 at 17:44
  • @chepner [SO will only let me mention one person in a comment] I've corrected the answer, thank you for point that out, I wrongly thought instantising happened at with the object. There is still a potention ram saving when multiple instances exist in the main thread, but I have only ever seen that being important in embedded code, so I've added that at the bottom of the answer. – 2e0byo Oct 14 '21 at 17:51
  • There is no persistent memory used by instance methods. All methods are just *class* attributes, of type `function`, `classmethod`, or `staticmethod`. When you access one via an instance of the class, you get back the result of the method's `__get__` method. For instance and class methods, that's a new (and temporary) object of type `method`. For static methods, it's the original function decorated by `staticmethod`. – chepner Oct 14 '21 at 17:51
  • 1
    No, there is no extra memory used by multiple instances. At worse, it is extra memory per active *call*, but if you have multiple threads or processes that would allow that, there's already a lot of memory overhead in managing those threads and processes, making the tiny amount used by a `method` object irrelevent. – chepner Oct 14 '21 at 17:53
  • @chepner: Or recursion, but even then, the memory cost of the stack frames outweighs the extra method objects, before even getting into the memory consumed by any data the method calls are working with. – user2357112 Oct 14 '21 at 17:58
  • @chepner hmm, I'm going to remove all references to RAM usage and ask a question myself. To be clear, I was thinking of async code, where I imagine the functions as 'running' until they have finished awaiting, but I'm not even sure if that is right – 2e0byo Oct 14 '21 at 17:59
  • 1
    Micropython is an entirely separate implementation of Python. What I've been describing is how CPython works (and is what most people are actually using when they talk about Python code). It's possible that methods are implemented differently, or in such a way that the overhead of multiple `method` objects *does* become an issue. – chepner Oct 14 '21 at 18:01
  • @chepner if you have a moment I'd appreciate an answer over [here](https://stackoverflow.com/questions/69575542/does-staticmethod-save-any-ram-in-cpython-or-micropython), but in any case thank you for challenging an assumption I've had for a while without ever checking – 2e0byo Oct 14 '21 at 18:17
  • 2ebyo Thank you for answering my question. This should help now. And thanks to anyone else who contributed. – Zach .N Oct 15 '21 at 17:22