あなたがしたいことは不可能です。これを Python で書くことはできません。
shoppingcart(item='laptop', 100,200,300)
2.x または 3.x のいずれかで、エラーが発生します。
SyntaxError: non-keyword arg after keyword arg
まず、少し背景を説明します。
「キーワード」引数はitem='laptop'
、名前と値を指定する のようなものです。「位置」または「非キーワード」引数は100
、値を指定するだけのようなものです。関数を呼び出すたびに、すべての位置引数の後にキーワード args を配置する必要があります。つまり、 と書くことはできますがshoppingcart(100, 200, 300, item='laptop')
、 と書くことはできませんshoppingcart(item='laptop', 100, 200, 300)
。
キーワード引数の処理方法を理解すると、これはもう少し理にかなっています。すべてを簡単にするために、一連のパラメーターが固定され、デフォルト値がない非常に単純なケースを考えてみましょう。
def shoppingcart(item, price):
print item, price
shoppingcart('laptop', 200)
、shoppingcart(price=200, item='laptop')
、またはその他の可能性でこれを呼び出すかどうかに関係なく、引数が関数本体にitem
どのように到着するかを理解できます。price
を除いてshoppingcart(price=200, 'laptop')
、Python は何laptop
が想定されているかを判断できません — それは最初の引数ではなく、明示的なキーワード が与えられてitem
いないため、item
. price
しかし、私はすでにそれを持っているので、それもできません。よく考えてみると、位置引数の前にキーワード引数を渡そうとするたびに、同じ問題が発生します。より複雑なケースでは、その理由を理解するのが難しくなりますが、最も単純なケースでも機能しません。
そのため、Python はSyntaxError: non-keyword arg after keyword arg
、関数がどのように定義されているかを確認することさえせずに , を発生させます。したがって、これを機能させるために関数定義を変更する方法はありません。それは不可能です。
しかし、パラメーター リストの最後に移動した場合はitem
、呼び出しshoppingcart(100, 200, 300, item='computer')
を行うことができ、すべて問題ありませんよね? 残念ながら、いいえ。*args
を吸収するために を使用しており、 .100, 200, 300
の後に明示的なパラメーターを配置することはできないため*args
です。結局のところ、この制限には原則的な理由がないため、Python 3 で廃止されましたが、まだ 2.7 を使用している場合は、それを受け入れなければなりません。しかし、少なくとも回避策があります。
これを回避するには、 を使用**kwargs
してすべてのキーワード引数を吸収し、それらを自分で解析します*args
。これは、すべての位置引数を吸収するために使用したのと同じ方法です。位置引数で を取得*args
するのと同じように、各キーワード引数のキーワードをその値にマップして を取得します。list
**kwargs
dict
そう:
>>> def shoppingcart(*args, **kwargs):
... print args, kwargs
>>> shoppingcart(100, 200, 300, item='laptop')
[100, 200, 300], {'item': 'laptop'}
item
必須にしたい場合は、次のようにアクセスするだけです:
item = kwargs['item']
デフォルト値を使用してオプションにしたい場合は、次のようにします。
item = kwargs.get('item', 'computer')
ただし、ここで問題があります。オプションのitem
引数のみを受け入れたかったのですが、実際には任意のキーワード引数を受け入れています。単純な関数でこれを試みると、エラーが発生します。
>>> def simplefunc(a, b, c):
... print a, b, c
>>> simplefunc(1, 2, 3, 4)
TypeError: simplefunc() takes exactly 3 arguments (4 given)
>>> simplefunc(a=1, b=2, c=3, d=4, e=5)
TypeError: simplefunc() got an unexpected keyword argument 'd'
でこれをシミュレートする非常に簡単な方法があり**kwargs
ます。これは通常のdict
. それが望ましくない場合は、エラーを発生させることができます。次のように記述できます。
def shoppingcart(*prices, **kwargs):
for key in kwargs:
if key != 'item':
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
item = kwargs.get('item', 'computer')
print item, prices
ただし、便利なショートカットがあります。このdict.pop
メソッドは と同じようdict.get
に機能しますが、値を返すだけでなく、辞書から削除します。削除した後item
(存在する場合)、何か残っている場合はエラーです。したがって、これを行うことができます:
def shoppingcart(*args, **kwargs):
item = kwargs.pop('item', 'computer')
if kwargs: # Something else was in there besides item!
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
再利用可能なライブラリを構築している場合を除き、その全体よりも単純なもので逃げることができif
ます。の代わりにが取得されても、誰も気にしません。それが、lqc のソリューションが行っていることです。raise
assert not kwargs
AssertionError
TypeError
要約すると、関数をどのように記述するかは問題ではありません。思い通りに呼び出すことはできません。しかしitem
、最後まで進んでいくのであれば、(Python 3 バージョンほど単純でも読みやすくなくても) 合理的な方法でそれを作成し、呼び出すことができます。
いくつかのまれなケースでは、他の方法があります。たとえば、価格が常に数値である必要があり、アイテムが常に文字列である場合、コードを次のように変更できます。
def shoppingcart(*prices):
if prices and isinstance(prices[0], basestring):
item = prices.pop(0)
else:
item = 'computer'
print item
for price in prices:
print price
これで、次のことができます。
>>> shoppingcart(100, 200, 300)
computer
100
200
300
>>> shoppingcart('laptop', 100, 200, 300) # notice no `item=` here
laptop
100
200
300
これは基本的に、Python が を実装するために使用する種類のトリックslice(start=None, stop, step=None)
であり、コードで非常に役立つ場合があります。ただし、実装がはるかに壊れやすくなり、関数の動作が読者にとってわかりにくくなり、一般的に Pythonic とは感じられなくなります。したがって、ほとんどの場合、これを回避し、必要に応じて呼び出し元に実際のキーワード引数を使用させ、最後に来なければならないという制限を設けることが最善です。