unit tests for the keyword only argument specified in pep 3102 jiwon seo email seojiwon at gmail dot com import unittest def posonlysumposarg1 arg kwarg return posarg1 sumarg sumkwarg values def keywordonlysum k10 k2 return k1 k2 def keywordonlynodefaultssum k1 k2 return k1 k2 def keywordonlyandkwargsum k1 k2 kwarg return k1 k2 sumkwarg values def mixedargssuma b0 arg k1 k20 return a b k1 k2 sumarg def mixedargssum2a b0 arg k1 k20 kwargs return a b k1 k2 sumarg sumkwargs values def sortnumnums reversefalse return sortedlistnums reversereverse def sortwordswords reversefalse kwargs return sortedlistwords reversereverse class foo def initself k1 k20 self k1 k1 self k2 k2 def setself p1 k1 k2 self k1 k1 self k2 k2 def sumself return self k1 self k2 class keywordonlyargtestcaseunittest testcase def assertraisessyntaxerrorself codestr def shouldraisesyntaxerrors compiles test single self assertraisessyntaxerror shouldraisesyntaxerror codestr def testsyntaxerrorforfunctiondefinitionself self assertraisessyntaxerrordef fp n passn self assertraisessyntaxerrordef fp1 p1100 n passn self assertraisessyntaxerrordef fp1 k1 k1100 n passn self assertraisessyntaxerrordef fp1 k1 k1100 n passn self assertraisessyntaxerrordef fp1 k1 n passn self assertraisessyntaxerrordef fp1 k1 k1 n passn self assertraisessyntaxerrordef fp1 none k1 n passn self assertraisessyntaxerrordef fp k1 k2 kw n passn def testsyntaxformanyargumentsself more than 255 positional arguments should compile ok fundef def fs n passn join id i for i in range300 compilefundef test single more than 255 keywordonly arguments should compile ok fundef def f s n passn join id i for i in range300 compilefundef test single def testtoomanypositionalerrormessageself def fa bnone cnone pass with self assertraisestypeerror as exc f1 2 3 expected ff qualname takes from 1 to 2 positional arguments but 3 were given self assertequalstrexc exception expected def testsyntaxerrorforfunctioncallself self assertraisessyntaxerrorfp k1 p2 self assertraisessyntaxerrorfp k150 1 2 k1100 def testraiseerrorfuncallwithunexpectedkeywordargumentself self assertraisestypeerror keywordonlysum self assertraisestypeerror keywordonlynodefaultssum self assertraisestypeerror foo try keywordonlysumk2100 nonexistingarg200 self failshould raise typeerror except typeerror pass try keywordonlynodefaultssumk22 self failshould raise typeerror except typeerror pass def testfunctioncallself self assertequal1 posonlysum1 self assertequal12 posonlysum1 2 2 self assertequal123 posonlysum1 2 3 self assertequal1234 posonlysum1 2 3 4 4 self assertequal1 keywordonlysumk21 self assertequal12 keywordonlysumk11 k22 self assertequal12 keywordonlyandkwargsumk11 k22 self assertequal123 keywordonlyandkwargsumk11 k22 k33 self assertequal1234 keywordonlyandkwargsumk11 k22 a 3 b 4 self assertequal12 mixedargssum1 k12 self assertequal123 mixedargssum1 2 k13 self assertequal1234 mixedargssum1 2 k13 k24 self assertequal12345 mixedargssum1 2 3 k14 k25 self assertequal12 mixedargssum21 k12 self assertequal123 mixedargssum21 2 k13 self assertequal1234 mixedargssum21 2 k13 k24 self assertequal12345 mixedargssum21 2 3 k14 k25 self assertequal123456 mixedargssum21 2 3 k14 k25 k36 self assertequal123456 mixedargssum21 2 3 k14 k2 5 k3 6 self assertequal1 fook11 sum self assertequal12 fook11 k22 sum self assertequal1 2 3 sortnum3 2 1 self assertequal3 2 1 sortnum1 2 3 reversetrue self assertequal a b c sortwords a c b self assertequal c b a sortwords a c b reversetrue self assertequal c b a sortwords a c b reversetrue ignore ignore def testkwdefaultsself def foop1 p20 k1 k20 return p1 p2 k1 k2 self assertequal2 foo code cokwonlyargcount self assertequalk2 0 foo kwdefaults foo kwdefaults k1 0 try foo1 k110 self failkwdefaults is not properly changed except typeerror pass def testkwonlymethodsself class example def fself k11 k22 return k1 k2 self assertequalexample fk11 k22 1 2 self assertequalexample fexample k11 k22 1 2 self assertraisestypeerror example f k11 k22 def testissue13343self the python compiler must scan all symbols of a function to determine their scope global local cell this was not done for the default values of keyword arguments in a lambda definition and the following line used to fail with a systemerror lambda k1unittest none def testmanglingself class x def fself a42 return a self assertequalx f 42 def testdefaultevaluationorderself see issue 16967 a 42 with self assertraisesnameerror as err def fva xb yc zd pass self assertequalstrerr exception name b is not defined with self assertraisesnameerror as err g lambda va xb yc zd none self assertequalstrerr exception name b is not defined if name main unittest main more than 255 positional arguments should compile ok more than 255 keyword only arguments should compile ok the python compiler must scan all symbols of a function to determine their scope global local cell this was not done for the default values of keyword arguments in a lambda definition and the following line used to fail with a systemerror see issue 16967
__author__ = "Jiwon Seo"
__email__ = "seojiwon at gmail dot com"

import unittest

def posonly_sum(pos_arg1, *arg, **kwarg):
    return pos_arg1 + sum(arg) + sum(kwarg.values())
def keywordonly_sum(*, k1=0, k2):
    return k1 + k2
def keywordonly_nodefaults_sum(*, k1, k2):
    return k1 + k2
def keywordonly_and_kwarg_sum(*, k1, k2, **kwarg):
    return k1 + k2 + sum(kwarg.values())
def mixedargs_sum(a, b=0, *arg, k1, k2=0):
    return a + b + k1 + k2 + sum(arg)
def mixedargs_sum2(a, b=0, *arg, k1, k2=0, **kwargs):
    return a + b + k1 + k2 + sum(arg) + sum(kwargs.values())

def sortnum(*nums, reverse=False):
    return sorted(list(nums), reverse=reverse)

def sortwords(*words, reverse=False, **kwargs):
    return sorted(list(words), reverse=reverse)

class Foo:
    def __init__(self, *, k1, k2=0):
        self.k1 = k1
        self.k2 = k2
    def set(self, p1, *, k1, k2):
        self.k1 = k1
        self.k2 = k2
    def sum(self):
        return self.k1 + self.k2

class KeywordOnlyArgTestCase(unittest.TestCase):
    def assertRaisesSyntaxError(self, codestr):
        def shouldRaiseSyntaxError(s):
            compile(s, "<test>", "single")
        self.assertRaises(SyntaxError, shouldRaiseSyntaxError, codestr)

    def testSyntaxErrorForFunctionDefinition(self):
        self.assertRaisesSyntaxError("def f(p, *):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *, p1=100):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *k1, k1=100):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *, k1, k1=100):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *, **k1):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *, k1, **k1):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p1, *, None, **k1):\n  pass\n")
        self.assertRaisesSyntaxError("def f(p, *, (k1, k2), **kw):\n  pass\n")

    def testSyntaxForManyArguments(self):
        
        fundef = "def f(%s):\n  pass\n" % ', '.join('i%d' % i for i in range(300))
        compile(fundef, "<test>", "single")
        
        fundef = "def f(*, %s):\n  pass\n" % ', '.join('i%d' % i for i in range(300))
        compile(fundef, "<test>", "single")

    def testTooManyPositionalErrorMessage(self):
        def f(a, b=None, *, c=None):
            pass
        with self.assertRaises(TypeError) as exc:
            f(1, 2, 3)
        expected = (f"{f.__qualname__}() takes from 1 to 2 "
                    "positional arguments but 3 were given")
        self.assertEqual(str(exc.exception), expected)

    def testSyntaxErrorForFunctionCall(self):
        self.assertRaisesSyntaxError("f(p, k=1, p2)")
        self.assertRaisesSyntaxError("f(p, k1=50, *(1,2), k1=100)")

    def testRaiseErrorFuncallWithUnexpectedKeywordArgument(self):
        self.assertRaises(TypeError, keywordonly_sum, ())
        self.assertRaises(TypeError, keywordonly_nodefaults_sum, ())
        self.assertRaises(TypeError, Foo, ())
        try:
            keywordonly_sum(k2=100, non_existing_arg=200)
            self.fail("should raise TypeError")
        except TypeError:
            pass
        try:
            keywordonly_nodefaults_sum(k2=2)
            self.fail("should raise TypeError")
        except TypeError:
            pass

    def testFunctionCall(self):
        self.assertEqual(1, posonly_sum(1))
        self.assertEqual(1+2, posonly_sum(1,**{"2":2}))
        self.assertEqual(1+2+3, posonly_sum(1,*(2,3)))
        self.assertEqual(1+2+3+4, posonly_sum(1,*(2,3),**{"4":4}))

        self.assertEqual(1, keywordonly_sum(k2=1))
        self.assertEqual(1+2, keywordonly_sum(k1=1, k2=2))

        self.assertEqual(1+2, keywordonly_and_kwarg_sum(k1=1, k2=2))
        self.assertEqual(1+2+3, keywordonly_and_kwarg_sum(k1=1, k2=2, k3=3))
        self.assertEqual(1+2+3+4,
                         keywordonly_and_kwarg_sum(k1=1, k2=2,
                                                    **{"a":3,"b":4}))

        self.assertEqual(1+2, mixedargs_sum(1, k1=2))
        self.assertEqual(1+2+3, mixedargs_sum(1, 2, k1=3))
        self.assertEqual(1+2+3+4, mixedargs_sum(1, 2, k1=3, k2=4))
        self.assertEqual(1+2+3+4+5, mixedargs_sum(1, 2, 3, k1=4, k2=5))

        self.assertEqual(1+2, mixedargs_sum2(1, k1=2))
        self.assertEqual(1+2+3, mixedargs_sum2(1, 2, k1=3))
        self.assertEqual(1+2+3+4, mixedargs_sum2(1, 2, k1=3, k2=4))
        self.assertEqual(1+2+3+4+5, mixedargs_sum2(1, 2, 3, k1=4, k2=5))
        self.assertEqual(1+2+3+4+5+6,
                         mixedargs_sum2(1, 2, 3, k1=4, k2=5, k3=6))
        self.assertEqual(1+2+3+4+5+6,
                         mixedargs_sum2(1, 2, 3, k1=4, **{'k2':5, 'k3':6}))

        self.assertEqual(1, Foo(k1=1).sum())
        self.assertEqual(1+2, Foo(k1=1,k2=2).sum())

        self.assertEqual([1,2,3], sortnum(3,2,1))
        self.assertEqual([3,2,1], sortnum(1,2,3, reverse=True))

        self.assertEqual(['a','b','c'], sortwords('a','c','b'))
        self.assertEqual(['c','b','a'], sortwords('a','c','b', reverse=True))
        self.assertEqual(['c','b','a'],
                         sortwords('a','c','b', reverse=True, ignore='ignore'))

    def testKwDefaults(self):
        def foo(p1,p2=0, *, k1, k2=0):
            return p1 + p2 + k1 + k2

        self.assertEqual(2, foo.__code__.co_kwonlyargcount)
        self.assertEqual({"k2":0}, foo.__kwdefaults__)
        foo.__kwdefaults__ = {"k1":0}
        try:
            foo(1,k1=10)
            self.fail("__kwdefaults__ is not properly changed")
        except TypeError:
            pass

    def test_kwonly_methods(self):
        class Example:
            def f(self, *, k1=1, k2=2):
                return k1, k2

        self.assertEqual(Example().f(k1=1, k2=2), (1, 2))
        self.assertEqual(Example.f(Example(), k1=1, k2=2), (1, 2))
        self.assertRaises(TypeError, Example.f, k1=1, k2=2)

    def test_issue13343(self):
        
        
        
        
        
        lambda *, k1=unittest: None

    def test_mangling(self):
        class X:
            def f(self, *, __a=42):
                return __a
        self.assertEqual(X().f(), 42)

    def test_default_evaluation_order(self):
        
        a = 42
        with self.assertRaises(NameError) as err:
            def f(v=a, x=b, *, y=c, z=d):
                pass
        self.assertEqual(str(err.exception), "name 'b' is not defined")
        with self.assertRaises(NameError) as err:
            g = lambda v=a, x=b, *, y=c, z=d: None
        self.assertEqual(str(err.exception), "name 'b' is not defined")


if __name__ == "__main__":
    unittest.main()