Gusev Slava
Gusev Slava

Reputation: 2204

Inner class function without self

Peace, everyone! I'm using Python 3.6.3 and I find strange that such construction is possible:

class TestClass(object):
    def __init__(self):
        self.arg = "arg"

    def test():
        print("Hey test")

And using:

>>> TestClass.test()
"Hey test"

I know that in Python there are standard methods with self as parameter (don't know how to call them properly), static methods, class methods, abstract methods.

But what kind of method the test() is? Is it static method?


Edited:

Are there any useful usecases of such method of determining a function inside a class?

Upvotes: 12

Views: 12797

Answers (5)

Aran-Fey
Aran-Fey

Reputation: 43156

In python 3, there is no difference between a function and a function defined in a class:

def test():
    print("Hey test")

class TestClass:
    def test():
        print("Hey test")

test() # works
TestClass.test() # also works

Both of these are normal functions.

The magic of the implicit self argument happens when you access a function through an instance of the class, like this:

obj = TestClass()
obj.test() # throws an error because the test function doesn't accept arguments

This is when the function test is turned into the (bound) method test. You can see the difference if you print them:

print(TestClass.test) 
print(instance.test)
# output:
# <function TestClass.test at 0xaaaaaa>
# <bound method TestClass.test of <__main__.TestClass object at 0xbbbbbb>>

To sum it up:

  • Accessing a function through the class gives you the original function.
  • Accessing a function through an instance gives you a method with a bound self argument.

For details about how exactly this conversion from function to bound method works, see the descriptor how-to, and specifically the section about functions.

Upvotes: 18

developer_hatch
developer_hatch

Reputation: 16224

Let me explain with an example:

class TestClass(object):
  def __init__(self):
    self.arg = "arg"

  def test1():
    print("class method test1, Hey test")

  @classmethod
  def test2(cls):
    print("class method test2, Hey test")

  def test3(self):
    print("instance method test3, Hey test")

Look what happens when you call test1 with the class or with the instance:

First:

  TestClass.test1() #called from class
class method test1, Hey test
   TestClass().test1() #created an instance TestClass()
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: test1() takes 0 positional arguments but 1 was given

that's because when you create an instance, the self parameter is used, but here, the method has not the self parameter, that's why it brakes.

next one!

   TestClass.test2()
class method test2, Hey test
   TestClass().test2()
class method test2, Hey test

That worked for instance and for class, why? well, as you can see test2(cls) take an argument, cls, here, I'm not using it, so, it's ok that it works.

bring me the next subject, muajaja

  TestClass().test3()
instance method test3, Hey test
   TestClass.test3()
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: test3() missing 1 required positional argument: 'self'

That's easy to see, when you call it as class, you haven't provided the self parameter

Upvotes: 9

chepner
chepner

Reputation: 531125

Python 3 dispensed with the distinction between a bound and unbound method that existed in Python 2. What was previously an unbound method is now just a regular function.

class A(object):
    def test():
        pass

In Python 2:

>>> A.test
<unbound method A.test>

whereas in Python 3:

>>> A.test
<function A.test at 0x101cbbae8>

(the address may differ)

test here is a descriptor; you don't (necessarily) get back the original function object when you access A.test; instead you get the return value of that object's (i.e. the function's) __get__ method, which is called with two arguments. Which arguments depends on whether you access it via the class or via an instance of a class.

  • A.test => A.test.__get__(None, A)
  • a = A(); a.test => A.test.__get__(a, A)

In Python 2, A.test.__get__(None, A) returns an method object, which is a wrapper around the original function. As an unbound method, the wrapper still expects an instance of A as its first argument, even though the function itself was not defined with any parameters.

In Python 3, however, A.test.__get(None, A) simply returns a reference to the original function, not a method object. As a result, you can use it exactly as you defined it in the first place.

You can confirm this by examining id(A.__dict__['test']) and id(A.test) in Python 2 and Python 3. In Python 2, you'll get two different values; in Python 3, you'll get the same value for each.

Upvotes: 1

Moses Koledoye
Moses Koledoye

Reputation: 78556

In Python 3, (unlike Python 2) a function accessed and called from the class is just another function; nothing special:

Note that the transformation from function object to instance method object happens each time the attribute is retrieved from the instance.

[Emphasis mine]

You just happened to be calling the function with the right set of parameters albeit accessed via the class object. Same as calling the underlying function object for the method via an instance:

TestClass().test.__func__() # "Hey test"

A quick test explains it further:

print(TestClass().test is TestClass.test)
# False
print(TestClass().test.__func__ is TestClass.test)
# True

However, in Python 2, the behaviour is different as the transformation from function object to method object happens when the attribute is accessed via both the class or instance:

Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance.

[Emphasis mine]

Upvotes: 6

Chen A.
Chen A.

Reputation: 11280

TestClass.test() call actually executes the test() on the class object. This is similar to a @staticmethod (a method that can be executed on the class object, without creating an object first).

ins = TestClass()
ins.test()

will throw an exception, since instance methods pass self as the first argument, and test() takes no args.

When an object is created, the methods defined in the clas are bound to it. They are actually different objects, hence they have different ids:

   id(TestClass.test)
=> 140288592901800
   obj = TestClass()
   id(obj.test)
=> 140288605765960

In Python 2.7, your code throws an exception which is self explantory:

Traceback (most recent call last):
  File "<pyshell#404>", line 1, in <module>
    TestClass.test()
TypeError: unbound method test() must be called with TestClass instance as 
first argument (got nothing instead)

Upvotes: 0

Related Questions