1 Invocations in classic Python:
2
3 f(1, 2, 3) # positional
4 f(1, 2) # positional with defaults
5 f(1, 2, c=3) # keywords
6 f(1, c=3) # keywords with defaults
7 f(1, 2, 3, 4) # extra positional arguments
8 f(1, 2, 3, d=4) # extra keyword arguments
9 f(1, 2, *args) # positional bundles (possibly with defaults)
10 f(1, 2, **kw) # keyword bundles (possibly with defaults)
11
12 Note that f is never fixed before run-time in Python.
13
14 Comparison to invocations in C:
15
16 f(1, 2, 3) # positional, f known at compile-time
17 f(1, 2, 3) # positional, f is appropriate function pointer
18 # ie. (*f)(A, B, C)
19
20 Least expensive cases (positional plus defaults):
21
22 f(1, 2, 3) # put arguments in frame
23 # if f is not known, add arguments vs. parameters check
24 f(1, 2) # to handle defaults, introduce default "filling" where
25 # not enough arguments are given
26 # if f is not known, this is obviously done at run-time
27
28 More expensive cases (keywords plus defaults):
29
30 f(1, 2, c=3) # prepare frame using parameter details
31 # (provided c is a known parameter)
32 # if f is not known, this is obviously done at run-time
33 f(1, c=3) # as with the previous case, with default "filling" done
34 # where not enough arguments are given
35 # if f is not known, this is obviously done at run-time
36 # but with all defaults copied in before keywords are
37 # assigned (since their positions and thus the positions
38 # of missing parameters cannot be known)
39
40 Awkward cases (extra arguments):
41
42 f(1, 2, 3, 4) # put arguments in frame
43 # if f is not known, add arguments vs. parameters check;
44 # to handle superfluous arguments, make a suitable object
45 # and fill it with all such arguments
46
47 Very awkward cases:
48
49 f(1, 2, 3, d=4) # extra keyword arguments
50 f(1, 2, *args) # positional bundles (possibly with defaults)
51 f(1, 2, **kw) # keyword bundles (possibly with defaults)
52
53 These cases require additional structures to be created, potentially at
54 run-time.
55
56 Methods vs. functions:
57
58 f(obj, 1, 2) # f known as function at compile-time:
59 # f(obj, 1, 2)
60 # f known as C.m at compile-time:
61 # m(obj "assert isinstance(obj, C)", 1, 2)
62 # f not known at compile-time:
63 # f(<context>, obj, 1, 2) for instance-accessed methods
64 # f(obj, 1, 2) for class-accessed methods
65 # f(obj, 1, 2) for functions
66
67 (Could either have universal context usage even for functions, which would
68 ignore them, or attempt to remove contexts when functions are called.)
69
70 Argument lists for functions:
71
72 f(obj, 1, 2) # f known as function at compile-time
73
74 f -> don't get any context information
75 obj -> argument #1
76 1 -> argument #2
77 2 -> argument #3
78
79 Argument lists for methods:
80
81 f(obj, 1, 2) # f known as C.m at compile-time (context is C)
82
83 f -> C.m - don't get any context information
84 obj -> argument #1
85 1 -> argument #2
86 2 -> argument #3
87
88 Argument lists for methods:
89
90 f(obj, 1, 2) # f known as C.m at compile-time (context is an instance)
91
92 f -> C.m
93 -> context is argument #1
94 obj -> argument #2
95 1 -> argument #3
96 2 -> argument #4
97
98 Argument lists for classes:
99
100 f(obj, 1, 2) # f known as C at compile-time
101
102 f -> C.__init__
103 -> new instance is argument #1
104 obj -> argument #2
105 1 -> argument #3
106 2 -> argument #4
107
108 The new instance must be manually provided as the result after the call.
109
110 Argument lists for unknown callables:
111
112 f(obj, 1, 2) # f not known at compile-time
113
114 f -> f
115 -> load context for argument #1
116 obj -> argument #2
117 1 -> argument #3
118 2 -> argument #4
119
120 Then, check the context and shift the frame if necessary:
121
122 <context> is module or class:
123 (<context>, obj, 1, 2) -> (obj, 1, 2)
124
125 <context> is instance: no change
126
127 Argument lists in instantiators:
128
129 f(obj, 1, 2) # f not known at compile-time
130
131 f -> C.__new__ (known and called at run-time)
132 -> load context for argument #1
133 obj -> argument #2
134 1 -> argument #3
135 2 -> argument #4
136
137 Need to call C.__init__(<instance>, obj, 1, 2), preferably with the existing
138 frame:
139
140 *** -> instance overwrites argument #1
141 obj -> argument #2
142 1 -> argument #3
143 2 -> argument #4
144
145 Then jump without switching frames.
146 It should be possible to replace the old, tentative context information in the
147 frame.
148
149 Defaults for unknown callables:
150
151 f(obj) # f not known at compile-time
152
153 f -> f
154 -> load context for argument #1
155 obj -> argument #2
156
157 Then, check the number of arguments and the availability of defaults against
158 the details provided by the callable's structure.
159
160 Checking defaults for unknown callables:
161
162 Approach #1 - pre-fill defaults, add arguments, check frame
163
164 Approach #2 - add arguments, add defaults while checking frame
165
166 Functions as methods:
167
168 def f(x, y, z): ...
169 class C:
170 m = f
171 c = C()
172 ...
173 f(obj, 1, 2) # no restrictions on obj
174 obj.m(1, 2) # f(obj, 1, 2)
175 C.m(obj, 1, 2) # f(obj "assert isinstance(obj, C)", 1, 2)
176
177 Context propagation:
178
179 fn = C.m # has context C
180 fn(obj, 1, 2) # non-instance context -> explicit context required
181 # must perform isinstance(obj, C)
182 fn = c.m # table entry for m on C -> replace context
183 # gives context c
184 fn(1, 2) # instance context -> no explicit context required
185 # context c inserted in call
186
187 Star parameters are a convenience:
188
189 max(1, 2, 3) # call to max(*args) where args == (1, 2, 3)
190 max((1, 2, 3)) # but why not just do this instead?
191
192 One motivation: avoid explicitly making sequences.
193 Opportunity: avoid expensive dynamic allocation of sequences?
194
195 Star parameters, known callables and sequences:
196
197 g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
198
199 g -> don't get any context information
200 1 -> argument #1
201 2 -> reference to sequence containing arguments #2, #3, #4
202
203 (This according to approach #1 described for unknown callables. With approach
204 #2, normal argument positioning would occur.)
205
206 Star parameters, unknown callables:
207
208 g(1, 2, 3, 4) # g not known at compile-time
209
210 g -> g
211 -> load context for argument #1
212 1 -> argument #2
213 2 -> argument #3
214 3 -> argument #4
215 4 -> argument #5
216
217 Then, check the context and shift the frame if necessary (described above).
218
219 If g has a star parameter - g(a, *args) - then...
220
221 Approach #1 - move arguments #3, #4, #5 (or shifted to #2, #3, #4) into a
222 sequence, adding a reference to the sequence in their place
223
224 Approach #2 - maintain special access rules to arguments #3, #4, #5 (perhaps
225 shifted to #2, #3, #4) as a C-like array
226
227 Tradeoffs for star parameter approaches:
228
229 Approach #1 - potentially costly at run-time as arguments need moving around,
230 but the arguments would behave normally in functions
231
232 Approach #2 - need to track usage of the star parameter and to possibly copy
233 its contents if assigned, as well as providing special access
234 mechanisms, but the invocation procedure would be simpler