1

When an instance of a class is created, for each function in the class definition, the instance will have an attribute in its directory with the same name as the function. This “function attribute”, assuming the function is either a class method or an instance/bound method, will have another directory as its value which includes all of the attributes found in function objects in addition to __func__ and __self__. __func__ contains yet another directory—the directory of the function as found in the class itself—which of course contains all the attributes found in function objects (e.g. all attributes in __func__ point to the same object as the identically named attributes in the class’ function directory). Presumably, the attributes in the instance’s functions’ directory defer to the the identical attribute found in the __func__ directory when the function is called from the instance (and when the function is called from the instance, the instance's method's __call__ attribute defers to __func__.__call__). Why then do instances create this __func__ attribute anyway? Why not just have the immediate instance function directory attributes point directly to the class function directory attributes just as static methods operate? It seems that instances are using more memory than they need.

To make this discrepancy more clear:

class Solution:
    def maxArea(self, height):
        pass

    @staticmethod
    def i_am_static():
        pass

    @classmethod
    def i_am_a_class_method():
        pass

s = Solution()

print("\n\nDirectory of Instance Method:")
print(dir(s.maxArea))

print("\n\nDirectory of Instance Method __func__ attribute:")
print(dir(s.maxArea.__func__))

print("\n\nEqualities:")
print("s.maxArea.__func__.__call__== Solution.maxArea.__call__?  ",
s.maxArea.__func__.__call__ == Solution.maxArea.__call__)
print("s.maxArea.__call__ == Solution.maxArea.__call__?  ",
s.maxArea.__call__ == Solution.maxArea.__call__)



print("\n\nDirectory of Static Method:")
print(dir(s.i_am_static))
print("\nInstance Method has these other methods:")
for i in dir(s.maxArea):
    if i not in dir(s.i_am_static):
        print(i)
print("\nStatic Method has these other methods:")
for i in dir(s.i_am_static):
    if i not in dir(s.maxArea):
        print(i)

Prints:

Directory of Instance Method:
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']


Directory of Instance Method __func__ attribute:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']


Equalities:
s.maxArea.__func__.__call__ == Solution.maxArea.__call__?   True
s.maxArea.__call__ == Solution.maxArea.__call__?   False


Directory of Static Method:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Instance Method has these other methods:
__func__
__self__

Static Method has these other methods:
__annotations__
__closure__
__code__
__defaults__
__dict__
__globals__
__kwdefaults__
__module__
__name__
__qualname__

Looking at the equalities, we can see that the attributes in __func__ reference the same object created by the Class itself. However, the attributes not in __func__ (for instance __call__) do not reference the same object, yet they presumably call that object. Why go through the trouble of recreating these attributes outside of __func__ simply to call those in __func__? Why not dump the attributes in __func__ in the dir(s.maxArea) and the __self__ attribute will express that the method should be called as an instance method?

soporific312
  • 117
  • 9

1 Answers1

0

It's used to hold where a function is located in memory.

See: https://github.com/python/cpython/blob/24bba8cf5b8db25c19bcd1d94e8e356874d1c723/Objects/funcobject.c

An example class:

>>> class Test(object):
...     a = 'world'
...     def test_method(self, b='hello'):
...             print(b, self.a)
... 

Working example:

>>> c = Test()
>>> c.test_method()
hello world

There is code related to setting, various internal variables for, arguments, key word arguments ( __defaults__, __kwdefaults__, etc) so when the method is called it has information about how it is to be called. ( see bound method example, also note there are caveats here for python2/3, see linked method-wrapper explanation below )

Example of default kwarg b:

>>> print(c.test_method.__defaults__)
('hello',)

Where is Test and Test.test_method located:

>>> print(c)
<__main__.Test object at 0x7fd274115550>
>>> print(c.test_method)
<bound method Test.test_method of <__main__.Test object at 0x7fd274115550>>

Where is the __func__ located:

>>> print(c.test_method.__func__)
<function Test.test_method at 0x7fd274113598>

>>> c.test_method.__func__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test_method() missing 1 required positional argument: 'self'

How does the Test.test_method look like when it's called, more or less it looks like:

>>> c.test_method.__func__(c)
hello world

What about __call__? It's a method-wrapper for __func__:

>>> print(c.test_method.__func__.__call__)
<method-wrapper '__call__' of function object at 0x7fd274113598>

>>> c.test_method.__func__.__call__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test_method() missing 1 required positional argument: 'self'
>>> c.test_method.__func__.__call__(c)
hello world

So the purpose of __func__ is to setup how modules, and classes will look for the function, if it is a method inside of a class, it has some extra stuff working behind the scenes to set the context, so it can access self.a, or other class methods within the class.

An example where the function is outside of a class:

>>> def test_method(cls, b='hello'):
...     print(b, cls.a)
... 

>>> class Test(object):
...     a = 'world'
... 

>>> c = Test()
>>> test_method(c)
hello world

>>> print(test_method)
<function test_method at 0x7fd274113400>

>>> print(test_method.__func__)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '__func__'

>>> print(test_method.__defaults__)
('hello',)

>>> test_method.__call__(c)
hello world

Also see:

jmunsch
  • 16,405
  • 6
  • 74
  • 87
  • Your answer has good information in it and has helped me understand class methods better. However, I think my main question is still unanswered. I understand that `__func__` is used to hold the directory of the method to help set the context for the call. But why does an instance method directory re-define the exact same attributes as found in `__func__` if all they will do is delegate to the methods in `__func__`? Static methods do not define `__func__` because they just shallow-copy the original dictionary. Why do instance and class methods not do the same and simply include `__self__`? – soporific312 Mar 28 '20 at 01:07