Reputation: 2204
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
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:
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
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
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
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
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