paul@69 | 1 | Invocations in classic Python:
|
paul@69 | 2 |
|
paul@69 | 3 | f(1, 2, 3) # positional
|
paul@69 | 4 | f(1, 2) # positional with defaults
|
paul@69 | 5 | f(1, 2, c=3) # keywords
|
paul@69 | 6 | f(1, c=3) # keywords with defaults
|
paul@69 | 7 | f(1, 2, 3, 4) # extra positional arguments
|
paul@69 | 8 | f(1, 2, 3, d=4) # extra keyword arguments
|
paul@69 | 9 | f(1, 2, *args) # positional bundles (possibly with defaults)
|
paul@69 | 10 | f(1, 2, **kw) # keyword bundles (possibly with defaults)
|
paul@69 | 11 |
|
paul@69 | 12 | Note that f is never fixed before run-time in Python.
|
paul@69 | 13 |
|
paul@92 | 14 | Comparison to invocations in C:
|
paul@69 | 15 |
|
paul@69 | 16 | f(1, 2, 3) # positional, f known at compile-time
|
paul@69 | 17 | f(1, 2, 3) # positional, f is appropriate function pointer
|
paul@69 | 18 | # ie. (*f)(A, B, C)
|
paul@69 | 19 |
|
paul@213 | 20 | Least expensive cases (positional plus defaults):
|
paul@69 | 21 |
|
paul@109 | 22 | f(1, 2, 3) # put arguments in frame
|
paul@69 | 23 | # if f is not known, add arguments vs. parameters check
|
paul@69 | 24 | f(1, 2) # to handle defaults, introduce default "filling" where
|
paul@69 | 25 | # not enough arguments are given
|
paul@69 | 26 | # if f is not known, this is obviously done at run-time
|
paul@69 | 27 |
|
paul@213 | 28 | More expensive cases (keywords plus defaults):
|
paul@69 | 29 |
|
paul@109 | 30 | f(1, 2, c=3) # prepare frame using parameter details
|
paul@69 | 31 | # (provided c is a known parameter)
|
paul@69 | 32 | # if f is not known, this is obviously done at run-time
|
paul@69 | 33 | f(1, c=3) # as with the previous case, with default "filling" done
|
paul@69 | 34 | # where not enough arguments are given
|
paul@69 | 35 | # if f is not known, this is obviously done at run-time
|
paul@69 | 36 | # but with all defaults copied in before keywords are
|
paul@69 | 37 | # assigned (since their positions and thus the positions
|
paul@69 | 38 | # of missing parameters cannot be known)
|
paul@69 | 39 |
|
paul@213 | 40 | Awkward cases (extra arguments):
|
paul@69 | 41 |
|
paul@213 | 42 | f(1, 2, 3, 4) # put arguments in frame
|
paul@214 | 43 | # if f is not known, add arguments vs. parameters check;
|
paul@214 | 44 | # to handle superfluous arguments, make a suitable object
|
paul@214 | 45 | # and fill it with all such arguments
|
paul@213 | 46 |
|
paul@213 | 47 | Very awkward cases:
|
paul@213 | 48 |
|
paul@69 | 49 | f(1, 2, 3, d=4) # extra keyword arguments
|
paul@69 | 50 | f(1, 2, *args) # positional bundles (possibly with defaults)
|
paul@69 | 51 | f(1, 2, **kw) # keyword bundles (possibly with defaults)
|
paul@69 | 52 |
|
paul@69 | 53 | These cases require additional structures to be created, potentially at
|
paul@69 | 54 | run-time.
|
paul@92 | 55 |
|
paul@763 | 56 | Invocations and argument lists:
|
paul@763 | 57 |
|
paul@763 | 58 | General procedure:
|
paul@763 | 59 |
|
paul@763 | 60 | 1. Load target.
|
paul@763 | 61 | 2. Put target in list (if appropriate).
|
paul@763 | 62 | 3. Put context in list (if appropriate).
|
paul@763 | 63 | 4. Put arguments in list.
|
paul@763 | 64 | 5. Jump to target.
|
paul@763 | 65 |
|
paul@763 | 66 | The target is needed for dynamic functions and methods, but is external to
|
paul@763 | 67 | any notion of arguments or locals.
|
paul@763 | 68 |
|
paul@92 | 69 | Methods vs. functions:
|
paul@92 | 70 |
|
paul@92 | 71 | f(obj, 1, 2) # f known as function at compile-time:
|
paul@92 | 72 | # f(obj, 1, 2)
|
paul@92 | 73 | # f known as C.m at compile-time:
|
paul@92 | 74 | # m(obj "assert isinstance(obj, C)", 1, 2)
|
paul@98 | 75 | # f not known at compile-time:
|
paul@92 | 76 | # f(<context>, obj, 1, 2) for instance-accessed methods
|
paul@92 | 77 | # f(obj, 1, 2) for class-accessed methods
|
paul@92 | 78 | # f(obj, 1, 2) for functions
|
paul@92 | 79 |
|
paul@92 | 80 | (Could either have universal context usage even for functions, which would
|
paul@92 | 81 | ignore them, or attempt to remove contexts when functions are called.)
|
paul@92 | 82 |
|
paul@98 | 83 | Argument lists for functions:
|
paul@98 | 84 |
|
paul@98 | 85 | f(obj, 1, 2) # f known as function at compile-time
|
paul@98 | 86 |
|
paul@234 | 87 | f -> f (context is null)
|
paul@98 | 88 | obj -> argument #1
|
paul@98 | 89 | 1 -> argument #2
|
paul@98 | 90 | 2 -> argument #3
|
paul@98 | 91 |
|
paul@98 | 92 | Argument lists for methods:
|
paul@98 | 93 |
|
paul@98 | 94 | f(obj, 1, 2) # f known as C.m at compile-time (context is C)
|
paul@98 | 95 |
|
paul@234 | 96 | f -> C.m (context is class C)
|
paul@234 | 97 | obj -> argument #1 (must be tested against the context)
|
paul@98 | 98 | 1 -> argument #2
|
paul@98 | 99 | 2 -> argument #3
|
paul@98 | 100 |
|
paul@98 | 101 | Argument lists for methods:
|
paul@98 | 102 |
|
paul@98 | 103 | f(obj, 1, 2) # f known as C.m at compile-time (context is an instance)
|
paul@98 | 104 |
|
paul@98 | 105 | f -> C.m
|
paul@98 | 106 | -> context is argument #1
|
paul@98 | 107 | obj -> argument #2
|
paul@98 | 108 | 1 -> argument #3
|
paul@98 | 109 | 2 -> argument #4
|
paul@98 | 110 |
|
paul@109 | 111 | Argument lists for classes:
|
paul@109 | 112 |
|
paul@109 | 113 | f(obj, 1, 2) # f known as C at compile-time
|
paul@109 | 114 |
|
paul@234 | 115 | f -> instantiator of C
|
paul@234 | 116 | -> (argument #1 reserved for a new instance made by the instantiator)
|
paul@137 | 117 | obj -> argument #2
|
paul@137 | 118 | 1 -> argument #3
|
paul@137 | 119 | 2 -> argument #4
|
paul@137 | 120 |
|
paul@234 | 121 | The new instance must be provided as the result of the call.
|
paul@109 | 122 |
|
paul@98 | 123 | Argument lists for unknown callables:
|
paul@98 | 124 |
|
paul@98 | 125 | f(obj, 1, 2) # f not known at compile-time
|
paul@98 | 126 |
|
paul@98 | 127 | f -> f
|
paul@98 | 128 | -> load context for argument #1
|
paul@98 | 129 | obj -> argument #2
|
paul@98 | 130 | 1 -> argument #3
|
paul@98 | 131 | 2 -> argument #4
|
paul@98 | 132 |
|
paul@98 | 133 | Then, check the context and shift the frame if necessary:
|
paul@98 | 134 |
|
paul@234 | 135 | f is class: no change
|
paul@234 | 136 |
|
paul@234 | 137 | <context> is class:
|
paul@98 | 138 | (<context>, obj, 1, 2) -> (obj, 1, 2)
|
paul@98 | 139 |
|
paul@98 | 140 | <context> is instance: no change
|
paul@98 | 141 |
|
paul@137 | 142 | Argument lists in instantiators:
|
paul@137 | 143 |
|
paul@137 | 144 | f(obj, 1, 2) # f not known at compile-time
|
paul@137 | 145 |
|
paul@137 | 146 | f -> C.__new__ (known and called at run-time)
|
paul@230 | 147 | -> load context for argument #1
|
paul@230 | 148 | obj -> argument #2
|
paul@230 | 149 | 1 -> argument #3
|
paul@230 | 150 | 2 -> argument #4
|
paul@137 | 151 |
|
paul@426 | 152 | f(obj, 1, 2) # f known at compile-time
|
paul@426 | 153 |
|
paul@426 | 154 | f -> C.__new__ (known and called at run-time)
|
paul@426 | 155 | -> argument #1 left blank
|
paul@426 | 156 | obj -> argument #2
|
paul@426 | 157 | 1 -> argument #3
|
paul@426 | 158 | 2 -> argument #4
|
paul@426 | 159 |
|
paul@426 | 160 | Frame re-use in instantiators:
|
paul@426 | 161 |
|
paul@137 | 162 | Need to call C.__init__(<instance>, obj, 1, 2), preferably with the existing
|
paul@137 | 163 | frame:
|
paul@137 | 164 |
|
paul@230 | 165 | *** -> instance overwrites argument #1
|
paul@230 | 166 | obj -> argument #2
|
paul@230 | 167 | 1 -> argument #3
|
paul@230 | 168 | 2 -> argument #4
|
paul@137 | 169 |
|
paul@137 | 170 | Then jump without switching frames.
|
paul@137 | 171 |
|
paul@426 | 172 | If no context argument (or blank argument) were provided, a new frame would
|
paul@426 | 173 | need to be allocated and filled with a new instance and all remaining
|
paul@426 | 174 | arguments from the current frame.
|
paul@426 | 175 |
|
paul@110 | 176 | Defaults for unknown callables:
|
paul@110 | 177 |
|
paul@110 | 178 | f(obj) # f not known at compile-time
|
paul@110 | 179 |
|
paul@110 | 180 | f -> f
|
paul@110 | 181 | -> load context for argument #1
|
paul@110 | 182 | obj -> argument #2
|
paul@110 | 183 |
|
paul@110 | 184 | Then, check the number of arguments and the availability of defaults against
|
paul@110 | 185 | the details provided by the callable's structure.
|
paul@110 | 186 |
|
paul@111 | 187 | Checking defaults for unknown callables:
|
paul@111 | 188 |
|
paul@111 | 189 | Approach #1 - pre-fill defaults, add arguments, check frame
|
paul@111 | 190 |
|
paul@111 | 191 | Approach #2 - add arguments, add defaults while checking frame
|
paul@111 | 192 |
|
paul@331 | 193 | Dynamic functions:
|
paul@233 | 194 |
|
paul@331 | 195 | def f(x):
|
paul@331 | 196 | def g(y=x): # dynamic: y depends on non-constant value
|
paul@331 | 197 | ...
|
paul@331 | 198 | def h(y=2): # static: y depends on constant value
|
paul@331 | 199 | ...
|
paul@331 | 200 |
|
paul@331 | 201 | def f(x):
|
paul@331 | 202 | g = lambda y=x: ... # dynamic: y depends on non-constant value
|
paul@331 | 203 | h = lambda y=2: ... # static: y depends on constant value
|
paul@233 | 204 |
|
paul@331 | 205 | Representation of dynamic functions:
|
paul@331 | 206 |
|
paul@331 | 207 | f = lambda x, y=nonconst: ...
|
paul@331 | 208 |
|
paul@331 | 209 | def f(x, y=nonconst):
|
paul@331 | 210 | ...
|
paul@233 | 211 |
|
paul@763 | 212 | Defines object referencing function:
|
paul@233 | 213 |
|
paul@763 | 214 | def <lambda>(x, y=nonconst): # references lambda x, ...
|
paul@763 | 215 | # Obtain nonconst from <lambda> for y, if necessary
|
paul@233 | 216 |
|
paul@763 | 217 | def <f>(x, y=nonconst): # references f
|
paul@763 | 218 | # Obtain nonconst from <f> for y, if necessary
|
paul@233 | 219 |
|
paul@760 | 220 | Dynamic default information and methods:
|
paul@760 | 221 |
|
paul@760 | 222 | class C:
|
paul@760 | 223 | def f(self, y=nonconst):
|
paul@760 | 224 | ...
|
paul@760 | 225 |
|
paul@763 | 226 | Defines additional state for the method:
|
paul@760 | 227 |
|
paul@760 | 228 | class C:
|
paul@763 | 229 | def <f>(self, y=nonconst): # references C.f
|
paul@763 | 230 | # Obtain nonconst from <f> for y, if necessary
|
paul@763 | 231 |
|
paul@763 | 232 | Non-constant information is only likely to be introduced to method defaults
|
paul@763 | 233 | within loops or if classes are permitted within functions.
|
paul@760 | 234 |
|
paul@92 | 235 | Functions as methods:
|
paul@92 | 236 |
|
paul@92 | 237 | def f(x, y, z): ...
|
paul@92 | 238 | class C:
|
paul@92 | 239 | m = f
|
paul@92 | 240 | c = C()
|
paul@92 | 241 | ...
|
paul@92 | 242 | f(obj, 1, 2) # no restrictions on obj
|
paul@92 | 243 | obj.m(1, 2) # f(obj, 1, 2)
|
paul@92 | 244 | C.m(obj, 1, 2) # f(obj "assert isinstance(obj, C)", 1, 2)
|
paul@123 | 245 |
|
paul@123 | 246 | Context propagation:
|
paul@123 | 247 |
|
paul@123 | 248 | fn = C.m # has context C
|
paul@123 | 249 | fn(obj, 1, 2) # non-instance context -> explicit context required
|
paul@123 | 250 | # must perform isinstance(obj, C)
|
paul@123 | 251 | fn = c.m # table entry for m on C -> replace context
|
paul@123 | 252 | # gives context c
|
paul@123 | 253 | fn(1, 2) # instance context -> no explicit context required
|
paul@123 | 254 | # context c inserted in call
|
paul@214 | 255 |
|
paul@214 | 256 | Star parameters are a convenience:
|
paul@214 | 257 |
|
paul@214 | 258 | max(1, 2, 3) # call to max(*args) where args == (1, 2, 3)
|
paul@214 | 259 | max((1, 2, 3)) # but why not just do this instead?
|
paul@214 | 260 |
|
paul@214 | 261 | One motivation: avoid explicitly making sequences.
|
paul@214 | 262 | Opportunity: avoid expensive dynamic allocation of sequences?
|
paul@214 | 263 |
|
paul@255 | 264 | Star parameters, approach #1:
|
paul@255 | 265 |
|
paul@255 | 266 | Make a sequence to hold the extra arguments, either in the caller for known
|
paul@255 | 267 | callables or in the function itself.
|
paul@255 | 268 |
|
paul@255 | 269 | Such a sequence would need allocating and its contents copying from the
|
paul@255 | 270 | stack.
|
paul@255 | 271 |
|
paul@255 | 272 | Star parameters, approach #2:
|
paul@255 | 273 |
|
paul@255 | 274 | Keep the extra arguments in the stack.
|
paul@255 | 275 |
|
paul@255 | 276 | Access to the star parameter would need to consider assignment to other
|
paul@255 | 277 | things and "escape situations" for the parameter:
|
paul@255 | 278 |
|
paul@255 | 279 | def f(*args):
|
paul@255 | 280 | return args # need to allocate and return the sequence
|
paul@255 | 281 |
|
paul@255 | 282 | Access to elements of the extra argument sequence would behave slightly
|
paul@255 | 283 | differently to normal sequences, but this could be identified at
|
paul@255 | 284 | compile-time.
|
paul@255 | 285 |
|
paul@255 | 286 | Star parameters, known callables and sequences, approach #1:
|
paul@214 | 287 |
|
paul@214 | 288 | g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
|
paul@214 | 289 |
|
paul@214 | 290 | g -> don't get any context information
|
paul@214 | 291 | 1 -> argument #1
|
paul@214 | 292 | 2 -> reference to sequence containing arguments #2, #3, #4
|
paul@214 | 293 |
|
paul@255 | 294 | Star parameters, known callables and sequences, approach #2:
|
paul@255 | 295 |
|
paul@255 | 296 | g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
|
paul@214 | 297 |
|
paul@255 | 298 | g -> don't get any context information
|
paul@255 | 299 | 1 -> argument #1
|
paul@255 | 300 | 2 -> argument #2
|
paul@255 | 301 | 3 -> argument #3
|
paul@255 | 302 | 4 -> argument #4
|
paul@255 | 303 |
|
paul@255 | 304 | Star parameters, unknown callables, both approach #1 and #2:
|
paul@214 | 305 |
|
paul@214 | 306 | g(1, 2, 3, 4) # g not known at compile-time
|
paul@214 | 307 |
|
paul@214 | 308 | g -> g
|
paul@214 | 309 | -> load context for argument #1
|
paul@214 | 310 | 1 -> argument #2
|
paul@214 | 311 | 2 -> argument #3
|
paul@214 | 312 | 3 -> argument #4
|
paul@214 | 313 | 4 -> argument #5
|
paul@214 | 314 |
|
paul@214 | 315 | Then, check the context and shift the frame if necessary (described above).
|
paul@214 | 316 |
|
paul@214 | 317 | If g has a star parameter - g(a, *args) - then...
|
paul@214 | 318 |
|
paul@214 | 319 | Approach #1 - move arguments #3, #4, #5 (or shifted to #2, #3, #4) into a
|
paul@214 | 320 | sequence, adding a reference to the sequence in their place
|
paul@214 | 321 |
|
paul@214 | 322 | Approach #2 - maintain special access rules to arguments #3, #4, #5 (perhaps
|
paul@214 | 323 | shifted to #2, #3, #4) as a C-like array
|
paul@214 | 324 |
|
paul@214 | 325 | Tradeoffs for star parameter approaches:
|
paul@214 | 326 |
|
paul@214 | 327 | Approach #1 - potentially costly at run-time as arguments need moving around,
|
paul@214 | 328 | but the arguments would behave normally in functions
|
paul@214 | 329 |
|
paul@214 | 330 | Approach #2 - need to track usage of the star parameter and to possibly copy
|
paul@214 | 331 | its contents if assigned, as well as providing special access
|
paul@214 | 332 | mechanisms, but the invocation procedure would be simpler
|