Making calls to objects
Key 4: All objects can be made callable.
To reuse and group code for some task, we group it in the functions classes, and then call it with different inputs. The objects that have a __call__
attribute are callable and __call__
is the entry point. For the C class, tp_call
is checked in its structure:
>>> def func(): # a function ... print("adf") ... >>> func() adf >>> func.__call__() #implicit call method adf >>> func.__class__.__call__(func) adf >>> func.__call__ <method-wrapper '__call__' of function object at 0x7ff7d9f24ea0> >>> class C: #a callable class ... def __call__(self): ... print("adf") ... >>> c = C() >>> c() adf >>> c.__call__() #implicit passing of self adf >>> c.__class__.__call__(c) #explicit passing of self adf >>> callable(lambda x:x+1) #testing whether object is callable or not True >>> isinstance(lambda x:x+1, collections.Callable) #testing whether object is callable or not True
Methods in classes are similar to functions, except that they are called with an implicit instance as a first argument. The functions are exposed as methods when they are accessed from the instance. The function is wrapped in a method class and returned. The method class stores instances in __self__
and function in __func__
, and its __call__
method calls __func__
with first argument as __self__
:
>>> class D: ... pass ... >>> class C: ... def do(self,): ... print("do run",self) ... >>> def doo(obj): ... print("doo run",obj) ... >>> c = C() >>> d = D() >>> doo(c) doo run <__main__.C object at 0x7fcf543625c0> >>> doo(d) doo run <__main__.D object at 0x7fcf54362400> >>> # we do not need to pass object in case of C class do method ... >>> c.do() #implicit pass of c object to do method do run <__main__.C object at 0x7fcf543625c0> >>> C.doo = doo >>> c.doo() doo run <__main__.C object at 0x7fcf543625c0> >>> C.doo() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: doo() missing 1 required positional argument: 'obj' >>> C.do() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: do() missing 1 required positional argument: 'self' >>> C.do(c) do run <__main__.C object at 0x7fcf543625c0> >>> C.do(d) do run <__main__.D object at 0x7fcf54362400> >>> c.do.__func__(d) #we called real function this way do run <__main__.D object at 0x7fcf54362400>
Using this logic, we can also collect methods that are needed from other classes in the current class, like the following code, instead of multiple inheritances if data attributes do not clash. This will result in two dictionary lookups for an attribute search: one for instance, and one for class.
>>> #in library ... class PrintVals: ... def __init__(self, endl): ... self.endl = endl ... ... def print_D8(self, data): ... print("{0} {1} {2}".format(data[0],data[1],self.endl)) ... >>> class PrintKVals: #from in2 library ... def __init__(self, knm): ... self.knm = knm ... ... def print_D8(self, data): ... print("{2}:{0} {1}".format(data[0],data[1],self.knm)) ... >>> class CollectPrint: ... ... def __init__(self, endl): ... self.endl = endl ... self.knm = "[k]" ... ... print_D8 = PrintVals.print_D8 ... print_D8K = PrintKVals.print_D8 ... >>> c = CollectPrint("}") >>> c.print_D8([1,2]) 1 2 } >>> c.print_D8K([1,2]) [k]:1 2
When we call classes, we are calling its type, that is metaclass
, with class as a first argument to give us a new instance:
>>> class Meta(type): ... def __call__(*args): ... print("meta call",args) ... >>> class C(metaclass=Meta): ... pass ... >>> >>> c = C() meta call (<class '__main__.C'>,) >>> c = C.__class__.__call__(C) meta call (<class '__main__.C'>,)
Similarly, when we call instances, we are calling their type, that is class, with instance as first argument:
>>> class C: ... def __call__(*args): ... print("C call",args) ... >>> c = C() >>> c() C call (<__main__.C object at 0x7f5d70c2bb38>,) >>> c.__class__.__call__(c) C call (<__main__.C object at 0x7f5d70c2bb38>,)