только преимуществом дополнительных методов является удобочитаемость кода. Вот именно.
Дополнительные методы позволяют Вам делать это:
foo.bar();
вместо этого:
Util.bar(foo);
Теперь существует много вещей в C#, которые похожи на это. Другими словами, существует много функций в C#, которые кажутся тривиальными и не обладают большим преимуществом в и себя. Однако, после того как Вы начинаете сочетать эти функции вместе, Вы начинаете видеть что-то просто немного большее, чем сумма его частей. Преимущества LINQ значительно от дополнительных методов как запросы LINQ были бы почти нечитабельны без них. LINQ был бы возможен без дополнительных методов, но не практичный.
Дополнительные методы много похожи на частичные классы C#. Собой они не очень полезны и кажутся тривиальными. Но когда Вы начинаете работать с классом, которому нужен сгенерированный код, частичные классы начинают иметь намного больше смысла.
Я был вдохновлен добротой декоратора в теории потерь, и после экспериментов с он ненадолго придумал это:
def actual_kwargs():
"""
Decorator that provides the wrapped function with an attribute 'actual_kwargs'
containing just those keyword arguments actually passed in to the function.
"""
def decorator(function):
def inner(*args, **kwargs):
inner.actual_kwargs = kwargs
return function(*args, **kwargs)
return inner
return decorator
if __name__ == "__main__":
@actual_kwargs()
def func(msg, a=None, b=False, c='', d=0):
print msg
for arg, val in sorted(func.actual_kwargs.iteritems()):
print ' %s: %s' % (arg, val)
func("I'm only passing a", a='a')
func("Here's b and c", b=True, c='c')
func("All defaults", a=None, b=False, c='', d=0)
func("Nothin'")
try:
func("Invalid kwarg", e="bogon")
except TypeError, err:
print 'Invalid kwarg\n %s' % err
Что напечатало это:
I'm only passing a a: a Here's b and c b: True c: c All defaults a: None b: False c: d: 0 Nothin' Invalid kwarg func() got an unexpected keyword argument 'e'
Я доволен этим. Более гибкий подход - передать имя атрибута, который вы хотите использовать, в декоратор, вместо того, чтобы жестко кодировать его как «actual_kwargs», но это самый простой подход, который иллюстрирует решение.
Ммм, Python вкусный .
Вот самый простой и простой способ:
def func(a=None, b=None, c=None):
args = locals().copy()
print args
func(2, "egg")
Это даст результат: {'a': 2, 'c': None, 'b': 'egg'}
.
Причина, по которой args
должен быть копией словаря locals
, заключается в том, что словари изменяемы, поэтому, если вы создали какие-либо локальные переменные в этой функции, args
будет содержать все локальные переменные и их значения, а не только аргументы.
Дополнительная документация по встроенной функции locals
здесь .
Одна возможность:
def f(**kw):
acceptable_names = set('a', 'b', 'c')
if not (set(kw) <= acceptable_names):
raise WhateverYouWantException(whatever)
...proceed...
IOW, очень легко проверить, что переданные имена находятся в допустимом наборе, и в противном случае повысить все, что вы хотите, чтобы Python поднял (TypeError, я думаю ;-). Между прочим, довольно легко превратить в декоратора.
Другая возможность:
_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
...proceed with checks `is _sentinel`...
путем создания уникального объекта _sentinel
вы устраняете риск того, что вызывающий может случайно передать None
(или другие неуникальные значения по умолчанию, которые вызывающий может передать). Это все, для чего подходит object ()
, кстати: чрезвычайно легкий, уникальный дозорный, который невозможно случайно спутать с каким-либо другим объектом (когда вы проверяете с помощью оператора , это
оператор) .
Любое решение предпочтительнее для немного разных задач.
Как насчет использования декоратора для проверки входящих kwargs?
def validate_kwargs(*keys):
def entangle(f):
def inner(*args, **kwargs):
for key in kwargs:
if not key in keys:
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
return f(*args, **kwargs)
return inner
return entangle
###
@validate_kwargs('a', 'b', 'c')
def func(**kwargs):
for arg,val in kwargs.items():
print arg, "->", val
func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')
Дает следующий результат:
b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
File "kwargs.py", line 20, in <module>
func(d='not gonna work')
File "kwargs.py", line 6, in inner
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
Возможно, есть способы сделать это лучше, но вот мой вариант:
def CompareArgs(argdict, **kwargs):
if not set(argdict.keys()) <= set(kwargs.keys()):
# not <= may seem weird, but comparing sets sometimes gives weird results.
# set1 <= set2 means that all items in set 1 are present in set 2
raise ValueError("invalid args")
def foo(**kwargs):
# we declare foo's "standard" args to be a, b, c
CompareArgs(kwargs, a=None, b=None, c=None)
print "Inside foo"
if __name__ == "__main__":
foo(a=1)
foo(a=1, b=3)
foo(a=1, b=3, c=5)
foo(c=10)
foo(bar=6)
и его вывод:
Inside foo Inside foo Inside foo Inside foo Traceback (most recent call last): File "a.py", line 18, in foo(bar=6) File "a.py", line 9, in foo CompareArgs(kwargs, a=None, b=None, c=None) File "a.py", line 5, in CompareArgs raise ValueError("invalid args") ValueError: invalid args
Это, вероятно, можно было бы преобразовать в декоратор, но мои декораторы нуждаются в доработке. Оставлено читателю в качестве упражнения: P
Возможно, возникнет ошибка, если они передадут какие-либо * args?
def func(*args, **kwargs):
if args:
raise TypeError("no positional args allowed")
arg1 = kwargs.pop("arg1", "default")
if kwargs:
raise TypeError("unknown args " + str(kwargs.keys()))
Было бы просто учесть это в использовании списка имен переменных или общей функции синтаксического анализа. Не было бы слишком сложно превратить это в декоратор (python 3.1):
def OnlyKwargs(func):
allowed = func.__code__.co_varnames
def wrap(*args, **kwargs):
assert not args
# or whatever logic you need wrt required args
assert sorted(allowed) == sorted(kwargs)
return func(**kwargs)
Примечание: я не уверен, насколько хорошо это будет работать с уже обернутыми функциями или функциями, имеющими * args
или ** kwargs
уже.
Magic - это не ответ:
def funky(a=None, b=None, c=None):
for name, value in [('a', a), ('b', b), ('c', c)]:
print name, value