1"""Unit tests for the positional only argument syntax specified in PEP 570.""" 2 3import dis 4import pickle 5import unittest 6 7from test.support import check_syntax_error 8 9 10def global_pos_only_f(a, b, /): 11 return a, b 12 13def global_pos_only_and_normal(a, /, b): 14 return a, b 15 16def global_pos_only_defaults(a=1, /, b=2): 17 return a, b 18 19class PositionalOnlyTestCase(unittest.TestCase): 20 21 def assertRaisesSyntaxError(self, codestr, regex="invalid syntax"): 22 with self.assertRaisesRegex(SyntaxError, regex): 23 compile(codestr + "\n", "<test>", "single") 24 25 def test_invalid_syntax_errors(self): 26 check_syntax_error(self, "def f(a, b = 5, /, c): pass", "non-default argument follows default argument") 27 check_syntax_error(self, "def f(a = 5, b, /, c): pass", "non-default argument follows default argument") 28 check_syntax_error(self, "def f(a = 5, b=1, /, c, *, d=2): pass", "non-default argument follows default argument") 29 check_syntax_error(self, "def f(a = 5, b, /): pass", "non-default argument follows default argument") 30 check_syntax_error(self, "def f(*args, /): pass") 31 check_syntax_error(self, "def f(*args, a, /): pass") 32 check_syntax_error(self, "def f(**kwargs, /): pass") 33 check_syntax_error(self, "def f(/, a = 1): pass") 34 check_syntax_error(self, "def f(/, a): pass") 35 check_syntax_error(self, "def f(/): pass") 36 check_syntax_error(self, "def f(*, a, /): pass") 37 check_syntax_error(self, "def f(*, /, a): pass") 38 check_syntax_error(self, "def f(a, /, a): pass", "duplicate argument 'a' in function definition") 39 check_syntax_error(self, "def f(a, /, *, a): pass", "duplicate argument 'a' in function definition") 40 check_syntax_error(self, "def f(a, b/2, c): pass") 41 check_syntax_error(self, "def f(a, /, c, /): pass") 42 check_syntax_error(self, "def f(a, /, c, /, d): pass") 43 check_syntax_error(self, "def f(a, /, c, /, d, *, e): pass") 44 check_syntax_error(self, "def f(a, *, c, /, d, e): pass") 45 46 def test_invalid_syntax_errors_async(self): 47 check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "non-default argument follows default argument") 48 check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "non-default argument follows default argument") 49 check_syntax_error(self, "async def f(a = 5, b=1, /, c, d=2): pass", "non-default argument follows default argument") 50 check_syntax_error(self, "async def f(a = 5, b, /): pass", "non-default argument follows default argument") 51 check_syntax_error(self, "async def f(*args, /): pass") 52 check_syntax_error(self, "async def f(*args, a, /): pass") 53 check_syntax_error(self, "async def f(**kwargs, /): pass") 54 check_syntax_error(self, "async def f(/, a = 1): pass") 55 check_syntax_error(self, "async def f(/, a): pass") 56 check_syntax_error(self, "async def f(/): pass") 57 check_syntax_error(self, "async def f(*, a, /): pass") 58 check_syntax_error(self, "async def f(*, /, a): pass") 59 check_syntax_error(self, "async def f(a, /, a): pass", "duplicate argument 'a' in function definition") 60 check_syntax_error(self, "async def f(a, /, *, a): pass", "duplicate argument 'a' in function definition") 61 check_syntax_error(self, "async def f(a, b/2, c): pass") 62 check_syntax_error(self, "async def f(a, /, c, /): pass") 63 check_syntax_error(self, "async def f(a, /, c, /, d): pass") 64 check_syntax_error(self, "async def f(a, /, c, /, d, *, e): pass") 65 check_syntax_error(self, "async def f(a, *, c, /, d, e): pass") 66 67 def test_optional_positional_only_args(self): 68 def f(a, b=10, /, c=100): 69 return a + b + c 70 71 self.assertEqual(f(1, 2, 3), 6) 72 self.assertEqual(f(1, 2, c=3), 6) 73 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'b'"): 74 f(1, b=2, c=3) 75 76 self.assertEqual(f(1, 2), 103) 77 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'b'"): 78 f(1, b=2) 79 self.assertEqual(f(1, c=2), 13) 80 81 def f(a=1, b=10, /, c=100): 82 return a + b + c 83 84 self.assertEqual(f(1, 2, 3), 6) 85 self.assertEqual(f(1, 2, c=3), 6) 86 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'b'"): 87 f(1, b=2, c=3) 88 89 self.assertEqual(f(1, 2), 103) 90 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'b'"): 91 f(1, b=2) 92 self.assertEqual(f(1, c=2), 13) 93 94 def test_syntax_for_many_positional_only(self): 95 # more than 255 positional only arguments, should compile ok 96 fundef = "def f(%s, /):\n pass\n" % ', '.join('i%d' % i for i in range(300)) 97 compile(fundef, "<test>", "single") 98 99 def test_pos_only_definition(self): 100 def f(a, b, c, /, d, e=1, *, f, g=2): 101 pass 102 103 self.assertEqual(5, f.__code__.co_argcount) # 3 posonly + 2 "standard args" 104 self.assertEqual(3, f.__code__.co_posonlyargcount) 105 self.assertEqual((1,), f.__defaults__) 106 107 def f(a, b, c=1, /, d=2, e=3, *, f, g=4): 108 pass 109 110 self.assertEqual(5, f.__code__.co_argcount) # 3 posonly + 2 "standard args" 111 self.assertEqual(3, f.__code__.co_posonlyargcount) 112 self.assertEqual((1, 2, 3), f.__defaults__) 113 114 def test_pos_only_call_via_unpacking(self): 115 def f(a, b, /): 116 return a + b 117 118 self.assertEqual(f(*[1, 2]), 3) 119 120 def test_use_positional_as_keyword(self): 121 def f(a, /): 122 pass 123 expected = r"f\(\) got some positional-only arguments passed as keyword arguments: 'a'" 124 with self.assertRaisesRegex(TypeError, expected): 125 f(a=1) 126 127 def f(a, /, b): 128 pass 129 expected = r"f\(\) got some positional-only arguments passed as keyword arguments: 'a'" 130 with self.assertRaisesRegex(TypeError, expected): 131 f(a=1, b=2) 132 133 def f(a, b, /): 134 pass 135 expected = r"f\(\) got some positional-only arguments passed as keyword arguments: 'a, b'" 136 with self.assertRaisesRegex(TypeError, expected): 137 f(a=1, b=2) 138 139 def test_positional_only_and_arg_invalid_calls(self): 140 def f(a, b, /, c): 141 pass 142 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'c'"): 143 f(1, 2) 144 with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'b' and 'c'"): 145 f(1) 146 with self.assertRaisesRegex(TypeError, r"f\(\) missing 3 required positional arguments: 'a', 'b', and 'c'"): 147 f() 148 with self.assertRaisesRegex(TypeError, r"f\(\) takes 3 positional arguments but 4 were given"): 149 f(1, 2, 3, 4) 150 151 def test_positional_only_and_optional_arg_invalid_calls(self): 152 def f(a, b, /, c=3): 153 pass 154 f(1, 2) # does not raise 155 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'b'"): 156 f(1) 157 with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'a' and 'b'"): 158 f() 159 with self.assertRaisesRegex(TypeError, r"f\(\) takes from 2 to 3 positional arguments but 4 were given"): 160 f(1, 2, 3, 4) 161 162 def test_positional_only_and_kwonlyargs_invalid_calls(self): 163 def f(a, b, /, c, *, d, e): 164 pass 165 f(1, 2, 3, d=1, e=2) # does not raise 166 with self.assertRaisesRegex(TypeError, r"missing 1 required keyword-only argument: 'd'"): 167 f(1, 2, 3, e=2) 168 with self.assertRaisesRegex(TypeError, r"missing 2 required keyword-only arguments: 'd' and 'e'"): 169 f(1, 2, 3) 170 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'c'"): 171 f(1, 2) 172 with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'b' and 'c'"): 173 f(1) 174 with self.assertRaisesRegex(TypeError, r" missing 3 required positional arguments: 'a', 'b', and 'c'"): 175 f() 176 with self.assertRaisesRegex(TypeError, r"f\(\) takes 3 positional arguments but 6 positional arguments " 177 r"\(and 2 keyword-only arguments\) were given"): 178 f(1, 2, 3, 4, 5, 6, d=7, e=8) 179 with self.assertRaisesRegex(TypeError, r"f\(\) got an unexpected keyword argument 'f'"): 180 f(1, 2, 3, d=1, e=4, f=56) 181 182 def test_positional_only_invalid_calls(self): 183 def f(a, b, /): 184 pass 185 f(1, 2) # does not raise 186 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'b'"): 187 f(1) 188 with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'a' and 'b'"): 189 f() 190 with self.assertRaisesRegex(TypeError, r"f\(\) takes 2 positional arguments but 3 were given"): 191 f(1, 2, 3) 192 193 def test_positional_only_with_optional_invalid_calls(self): 194 def f(a, b=2, /): 195 pass 196 f(1) # does not raise 197 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'a'"): 198 f() 199 200 with self.assertRaisesRegex(TypeError, r"f\(\) takes from 1 to 2 positional arguments but 3 were given"): 201 f(1, 2, 3) 202 203 def test_no_standard_args_usage(self): 204 def f(a, b, /, *, c): 205 pass 206 207 f(1, 2, c=3) 208 with self.assertRaises(TypeError): 209 f(1, b=2, c=3) 210 211 def test_change_default_pos_only(self): 212 def f(a, b=2, /, c=3): 213 return a + b + c 214 215 self.assertEqual((2,3), f.__defaults__) 216 f.__defaults__ = (1, 2, 3) 217 self.assertEqual(f(1, 2, 3), 6) 218 219 def test_lambdas(self): 220 x = lambda a, /, b: a + b 221 self.assertEqual(x(1,2), 3) 222 self.assertEqual(x(1,b=2), 3) 223 224 x = lambda a, /, b=2: a + b 225 self.assertEqual(x(1), 3) 226 227 x = lambda a, b, /: a + b 228 self.assertEqual(x(1, 2), 3) 229 230 x = lambda a, b, /, : a + b 231 self.assertEqual(x(1, 2), 3) 232 233 def test_invalid_syntax_lambda(self): 234 check_syntax_error(self, "lambda a, b = 5, /, c: None", "non-default argument follows default argument") 235 check_syntax_error(self, "lambda a = 5, b, /, c: None", "non-default argument follows default argument") 236 check_syntax_error(self, "lambda a = 5, b, /: None", "non-default argument follows default argument") 237 check_syntax_error(self, "lambda *args, /: None") 238 check_syntax_error(self, "lambda *args, a, /: None") 239 check_syntax_error(self, "lambda **kwargs, /: None") 240 check_syntax_error(self, "lambda /, a = 1: None") 241 check_syntax_error(self, "lambda /, a: None") 242 check_syntax_error(self, "lambda /: None") 243 check_syntax_error(self, "lambda *, a, /: None") 244 check_syntax_error(self, "lambda *, /, a: None") 245 check_syntax_error(self, "lambda a, /, a: None", "duplicate argument 'a' in function definition") 246 check_syntax_error(self, "lambda a, /, *, a: None", "duplicate argument 'a' in function definition") 247 check_syntax_error(self, "lambda a, /, b, /: None") 248 check_syntax_error(self, "lambda a, /, b, /, c: None") 249 check_syntax_error(self, "lambda a, /, b, /, c, *, d: None") 250 check_syntax_error(self, "lambda a, *, b, /, c: None") 251 252 def test_posonly_methods(self): 253 class Example: 254 def f(self, a, b, /): 255 return a, b 256 257 self.assertEqual(Example().f(1, 2), (1, 2)) 258 self.assertEqual(Example.f(Example(), 1, 2), (1, 2)) 259 self.assertRaises(TypeError, Example.f, 1, 2) 260 expected = r"f\(\) got some positional-only arguments passed as keyword arguments: 'b'" 261 with self.assertRaisesRegex(TypeError, expected): 262 Example().f(1, b=2) 263 264 def test_module_function(self): 265 with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'a' and 'b'"): 266 global_pos_only_f() 267 268 269 def test_closures(self): 270 def f(x,y): 271 def g(x2,/,y2): 272 return x + y + x2 + y2 273 return g 274 275 self.assertEqual(f(1,2)(3,4), 10) 276 with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required positional argument: 'y2'"): 277 f(1,2)(3) 278 with self.assertRaisesRegex(TypeError, r"g\(\) takes 2 positional arguments but 3 were given"): 279 f(1,2)(3,4,5) 280 281 def f(x,/,y): 282 def g(x2,y2): 283 return x + y + x2 + y2 284 return g 285 286 self.assertEqual(f(1,2)(3,4), 10) 287 288 def f(x,/,y): 289 def g(x2,/,y2): 290 return x + y + x2 + y2 291 return g 292 293 self.assertEqual(f(1,2)(3,4), 10) 294 with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required positional argument: 'y2'"): 295 f(1,2)(3) 296 with self.assertRaisesRegex(TypeError, r"g\(\) takes 2 positional arguments but 3 were given"): 297 f(1,2)(3,4,5) 298 299 def test_annotations_in_closures(self): 300 301 def inner_has_pos_only(): 302 def f(x: int, /): ... 303 return f 304 305 assert inner_has_pos_only().__annotations__ == {'x': int} 306 307 class Something: 308 def method(self): 309 def f(x: int, /): ... 310 return f 311 312 assert Something().method().__annotations__ == {'x': int} 313 314 def multiple_levels(): 315 def inner_has_pos_only(): 316 def f(x: int, /): ... 317 return f 318 return inner_has_pos_only() 319 320 assert multiple_levels().__annotations__ == {'x': int} 321 322 def test_same_keyword_as_positional_with_kwargs(self): 323 def f(something,/,**kwargs): 324 return (something, kwargs) 325 326 self.assertEqual(f(42, something=42), (42, {'something': 42})) 327 328 with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'something'"): 329 f(something=42) 330 331 self.assertEqual(f(42), (42, {})) 332 333 def test_mangling(self): 334 class X: 335 def f(self, __a=42, /): 336 return __a 337 338 def f2(self, __a=42, /, __b=43): 339 return (__a, __b) 340 341 def f3(self, __a=42, /, __b=43, *, __c=44): 342 return (__a, __b, __c) 343 344 self.assertEqual(X().f(), 42) 345 self.assertEqual(X().f2(), (42, 43)) 346 self.assertEqual(X().f3(), (42, 43, 44)) 347 348 def test_too_many_arguments(self): 349 # more than 255 positional-only arguments, should compile ok 350 fundef = "def f(%s, /):\n pass\n" % ', '.join('i%d' % i for i in range(300)) 351 compile(fundef, "<test>", "single") 352 353 def test_serialization(self): 354 pickled_posonly = pickle.dumps(global_pos_only_f) 355 pickled_optional = pickle.dumps(global_pos_only_and_normal) 356 pickled_defaults = pickle.dumps(global_pos_only_defaults) 357 358 unpickled_posonly = pickle.loads(pickled_posonly) 359 unpickled_optional = pickle.loads(pickled_optional) 360 unpickled_defaults = pickle.loads(pickled_defaults) 361 362 self.assertEqual(unpickled_posonly(1,2), (1,2)) 363 expected = r"global_pos_only_f\(\) got some positional-only arguments "\ 364 r"passed as keyword arguments: 'a, b'" 365 with self.assertRaisesRegex(TypeError, expected): 366 unpickled_posonly(a=1,b=2) 367 368 self.assertEqual(unpickled_optional(1,2), (1,2)) 369 expected = r"global_pos_only_and_normal\(\) got some positional-only arguments "\ 370 r"passed as keyword arguments: 'a'" 371 with self.assertRaisesRegex(TypeError, expected): 372 unpickled_optional(a=1,b=2) 373 374 self.assertEqual(unpickled_defaults(), (1,2)) 375 expected = r"global_pos_only_defaults\(\) got some positional-only arguments "\ 376 r"passed as keyword arguments: 'a'" 377 with self.assertRaisesRegex(TypeError, expected): 378 unpickled_defaults(a=1,b=2) 379 380 def test_async(self): 381 382 async def f(a=1, /, b=2): 383 return a, b 384 385 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'a'"): 386 f(a=1, b=2) 387 388 def _check_call(*args, **kwargs): 389 try: 390 coro = f(*args, **kwargs) 391 coro.send(None) 392 except StopIteration as e: 393 result = e.value 394 self.assertEqual(result, (1, 2)) 395 396 _check_call(1, 2) 397 _check_call(1, b=2) 398 _check_call(1) 399 _check_call() 400 401 def test_generator(self): 402 403 def f(a=1, /, b=2): 404 yield a, b 405 406 with self.assertRaisesRegex(TypeError, r"f\(\) got some positional-only arguments passed as keyword arguments: 'a'"): 407 f(a=1, b=2) 408 409 gen = f(1, 2) 410 self.assertEqual(next(gen), (1, 2)) 411 gen = f(1, b=2) 412 self.assertEqual(next(gen), (1, 2)) 413 gen = f(1) 414 self.assertEqual(next(gen), (1, 2)) 415 gen = f() 416 self.assertEqual(next(gen), (1, 2)) 417 418 def test_super(self): 419 420 sentinel = object() 421 422 class A: 423 def method(self): 424 return sentinel 425 426 class C(A): 427 def method(self, /): 428 return super().method() 429 430 self.assertEqual(C().method(), sentinel) 431 432 def test_annotations_constant_fold(self): 433 def g(): 434 def f(x: not (int is int), /): ... 435 436 # without constant folding we end up with 437 # COMPARE_OP(is), IS_OP (0) 438 # with constant folding we should expect a IS_OP (1) 439 codes = [(i.opname, i.argval) for i in dis.get_instructions(g)] 440 self.assertNotIn(('UNARY_NOT', None), codes) 441 self.assertIn(('IS_OP', 1), codes) 442 443 444if __name__ == "__main__": 445 unittest.main() 446