def test_unhashable_star_empty(): '''Test `ArgumentsProfile` hashing and handling of `*()`.''' def func(a, b, c=3, d=4, **kwargs): pass a2 = ArgumentsProfile(func, 7, ({ 'a': 'b' }, ), {1, (3, 4)}, meow=[1, 2, { 1: [1, 2] }]) assert a2.args == (7, ({'a': 'b'}, ), {1, (3, 4)}) assert a2.kwargs == OrderedDict((('meow', [1, 2, {1: [1, 2]}]), )) a3 = ArgumentsProfile(func, *(), b=({ 'a': 'b' }, ), c={1, (3, 4)}, a=7, meow=[1, 2, { 1: [1, 2] }]) assert a3.args == (7, ({'a': 'b'}, ), {1, (3, 4)}) assert a3.kwargs == OrderedDict((('meow', [1, 2, {1: [1, 2]}]), )) assert hash(a2) == hash(a3)
def test_many_defaultfuls_some_long_2(): ''' Test `ArgumentsProfile` with many defaultful arguments, some of them long. ''' def func(a, b, c=3, dragon=4, e=5, f=6, glide=7, human=8, iris=9): pass a1 = ArgumentsProfile(func, 1, 2, glide='boom') assert a1.args == (1, 2) assert a1.kwargs == OrderedDict((('glide', 'boom'), )) a2 = ArgumentsProfile(func, 1, 2, 3, 4, 5, 6, 'boom') a3 = ArgumentsProfile(func, 1, 2, 3, glide='boom') assert a1 == a2 == a3 a4 = ArgumentsProfile(func, 1, 2, glide='boom', human='pow', iris='badabang') a5 = ArgumentsProfile(func, 1, 2, 3, 4, 5, 6, 'boom', 'pow', 'badabang') assert a4 == a5 assert a4.args == (1, 2, 3, 4, 5, 6, 'boom', 'pow', 'badabang') assert not a4.kwargs
def test_many_defaultfuls_and_star_args(): '''Test `ArgumentsProfile` with many defaultful arguments and `*args`.''' def func(a, b, c='three', d='four', e='five', f='six', *args): pass a1 = ArgumentsProfile(func, 'one', 'two', f='roar') assert a1.args == ('one', 'two') assert a1.kwargs == OrderedDict((('f', 'roar'), )) a2 = ArgumentsProfile(func, 'one', 'two', 'three', 'four', 'five', 'roar') assert a1 == a2 # Specifying `*args`, so can't specify pre-`*args` arguments by keyword: a3 = ArgumentsProfile(func, 'one', 'two', 'three', 'four', 'five', 'roar', 'meow_frr') assert a3.args == ('one', 'two', 'three', 'four', 'five', 'roar', 'meow_frr') assert not a3.kwargs a4 = ArgumentsProfile(func, 'one', 'two', 'three', 'four', 'five', 'six', 3, 1, 4, 1, 5, 9, 2) assert a4.args == ('one', 'two', 'three', 'four', 'five', 'six', 3, 1, 4, 1, 5, 9, 2) assert not a4.kwargs assert a4['*'] == (3, 1, 4, 1, 5, 9, 2)
def get_default_args_dict(function): ''' Get ordered dict from arguments which have a default to their default. Example: >>> def f(a, b, c=1, d='meow'): pass >>> get_default_args_dict(f) OrderedDict([('c', 1), ('d', 'meow')]) ''' arg_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = arg_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more consistent # `()`. We fix that here: if s_defaults is None: s_defaults = () # The number of args which have default values: n_defaultful_args = len(s_defaults) defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] return OrderedDict(zip(defaultful_args, s_defaults))
def test_empty(): '''Test `get_default_args_dict` on a function with no defaultful args.''' def f(a, b, c, *args, **kwargs): pass assert get_default_args_dict(f) == \ OrderedDict()
def test_generator(): '''Test `get_default_args_dict` on a generator function.''' def f(a, meow='frr', d={}): yield None assert get_default_args_dict(f) == \ OrderedDict((('meow', 'frr'), ('d', {})))
def test(): '''Test the basic workings of `get_default_args_dict`.''' def f(a, b, c=3, d=4): pass assert get_default_args_dict(f) == \ OrderedDict((('c', 3), ('d', 4)))
def test_many_defaultfuls_some_long(): ''' Test `ArgumentsProfile` with many defaultful arguments, some of them long. ''' def func(a, b, c=3, dragon=4, e=5, f=6, glide=7, human=8): pass a1 = ArgumentsProfile(func, 1, 2, glide='boom') assert a1.args == (1, 2) assert a1.kwargs == OrderedDict((('glide', 'boom'), )) a2 = ArgumentsProfile(func, 1, 2, 3, 4, 5, 6, 'boom') a3 = ArgumentsProfile(func, 1, 2, 3, glide='boom') assert a1 == a2 == a3 a4 = ArgumentsProfile(func, 1, 2, glide='boom', human='pow') a5 = ArgumentsProfile(func, 1, 2, 3, 4, 5, 6, 'boom', 'pow') # edge case, second priority assert a4.args == (1, 2) assert a4.kwargs == OrderedDict((('glide', 'boom'), ('human', 'pow'))) assert a4 == a5
def test_only_defaultless(): ''' Test `ArgumentsProfile` on a function with defaultless arguments only. ''' def func(a, b, c): pass a1 = ArgumentsProfile(func, 1, 2, 3) assert a1.args == (1, 2, 3) assert not a1.kwargs a2 = ArgumentsProfile(func, 1, c=3, b=2) a3 = ArgumentsProfile(func, c=3, a=1, b=2) a4 = ArgumentsProfile(func, 1, **{'c': 3, 'b': 2}) a5 = ArgumentsProfile(func, **OrderedDict((('c', 3), ('b', 2), ('a', 1)))) assert a1 == a2 == a3 == a4 == a5 for arg_prof in [a1, a2, a3, a4, a5]: ### Testing `.iteritems`: ############################################# # # assert dict(arg_prof) == {'a': 1, 'b': 2, 'c': 3} assert OrderedDict(arg_prof) == \ OrderedDict((('a', 1), ('b', 2), ('c', 3))) # # ### Finished testing `.iteritems`. #################################### ### Testing `.__getitem__`: ########################################### # # assert (arg_prof['a'], arg_prof['b'], arg_prof['c']) == (1, 2, 3) with cute_testing.RaiseAssertor(KeyError): arg_prof['non_existing_key'] # # ### Finished testing `.__getitem__`. ################################## ### Testing `.get`: ################################################### # # assert arg_prof.get('a') == arg_prof.get('a', 'asdfasdf') == 1 assert arg_prof.get('non_existing_key', 7) == 7 assert arg_prof.get('non_existing_key') is None
def __init__(self, parameters=None, return_annotation=_empty, __validate_parameters__=True): '''Constructs Signature from the given list of Parameter objects and 'return_annotation'. All arguments are optional. ''' if parameters is None: params = OrderedDict() else: if __validate_parameters__: params = OrderedDict() top_kind = _POSITIONAL_ONLY for idx, param in enumerate(parameters): kind = param.kind if kind < top_kind: msg = 'wrong parameter order: {0} before {1}' msg = msg.format(top_kind, param.kind) raise ValueError(msg) else: top_kind = kind name = param.name if name is None: name = str(idx) param = param.replace(name=name) if name in params: msg = 'duplicate parameter name: {0!r}'.format(name) raise ValueError(msg) params[name] = param else: params = OrderedDict( ((param.name, param) for param in parameters)) self._parameters = params self._return_annotation = return_annotation
def test_only_positive_ints_or_zero(self): assert self.bag_type( OrderedDict([('a', 0), ('b', 0.0), ('c', 1), ('d', 2.0), ('e', decimal_module.Decimal('3.0'))])) == \ self.bag_type('cddeee') with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': 1.1, }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': -2, }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': -3, }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': decimal_module.Decimal('-3'), }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': infinity, }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': -infinity, }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': 'whatever', }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': b'whateva', }) with cute_testing.RaiseAssertor(TypeError): self.bag_type({ 'a': ('still', 'nope'), })
def test_simplest_defaultful(): ''' Test `ArgumentsProfile` on a function with defaultful arguments. ''' def func(a, b, c='three', d='four'): pass a1 = ArgumentsProfile(func, 'one', 'two') assert a1.args == ('one', 'two') assert not a1.kwargs a2 = ArgumentsProfile(func, 'one', 'two', 'three') a3 = ArgumentsProfile(func, 'one', 'two', 'three', 'four') assert a1 == a2 == a3 a4 = ArgumentsProfile(func, 'one', 'two', 'dynamite') assert a1 != a4 assert a4.args == ('one', 'two', 'dynamite') assert not a4.kwargs a5 = ArgumentsProfile(func, 'one', 'two', c='dynamite') a6 = ArgumentsProfile(func, 'one', 'two', 'dynamite', 'four') a7 = ArgumentsProfile(func, 'one', 'two', c='dynamite', d='four') a8 = ArgumentsProfile(func, 'one', 'two', 'dynamite', d='four') a9 = ArgumentsProfile(func, a='one', b='two', c='dynamite', d='four') a10 = ArgumentsProfile(func, d='four', c='dynamite', b='two', a='one') a11 = ArgumentsProfile(func, 'one', c='dynamite', d='four', b='two') assert a4 == a5 == a6 == a7 == a8 == a9 == a10 == a11 a12 = ArgumentsProfile(func, 'one', 'two', d='bang') assert a12.args == ('one', 'two') assert a12.kwargs == OrderedDict((('d', 'bang'), )) a13 = ArgumentsProfile(func, 'one', 'two', 'three', d='bang') a14 = ArgumentsProfile(func, 'one', 'two', c='three', d='bang') a15 = ArgumentsProfile(func, 'one', 'two', 'three', 'bang') a16 = ArgumentsProfile(func, a='one', b='two', c='three', d='bang') a17 = ArgumentsProfile(func, b='two', c='three', d='bang', a='one') assert a13 == a14 == a15 == a16 == a17
def test_unhashable(): '''Test hashing of `ArgumentsProfile` that has unhashable arguments.''' def func(a, b, c=3, d=4, **kwargs): pass a1 = ArgumentsProfile(func, 7, {1: 2}) assert a1.args == (7, {1: 2}) assert not a1.kwargs hash(a1) a2 = ArgumentsProfile(func, 7, ({ 'a': 'b' }, ), {1, (3, 4)}, meow=[1, 2, { 1: [1, 2] }]) assert a2.args == (7, ({'a': 'b'}, ), {1, (3, 4)}) assert a2.kwargs == OrderedDict((('meow', [1, 2, {1: [1, 2]}]), )) d = {a1: 1, a2: 2} assert d[a1] == 1 assert d[a2] == 2
def test_defaultful_long_first(): ''' Test `ArgumentsProfile` on function with long first defaultful argument. ''' def func(a, b, creativity=3, d=4): pass a1 = ArgumentsProfile(func, 1, 2) assert a1.args == (1, 2) assert not a1.kwargs a2 = ArgumentsProfile(func, 1, 2, 3, 4) a3 = ArgumentsProfile(func, a=1, b=2, creativity=3, d=4) a4 = ArgumentsProfile(func, creativity=3, d=4, a=1, b=2) a5 = ArgumentsProfile(func, 1, 2, creativity=3, d=4) assert a1 == a2 == a3 == a4 == a5 a6 = ArgumentsProfile(func, 1, 2, d='booyeah') assert a6.args == (1, 2) assert a6.kwargs == OrderedDict((('d', 'booyeah'), )) a7 = ArgumentsProfile(func, 1, 2, 3, 'booyeah') a8 = ArgumentsProfile(func, 1, 2, creativity=3, d='booyeah') assert a6 == a7 == a8
def test_operations(self): bag_0 = self.bag_type('abbccc') bag_1 = self.bag_type('bcc') bag_2 = self.bag_type('cddddd') assert bag_0 + bag_1 == self.bag_type('abbccc' + 'bcc') assert bag_1 + bag_0 == self.bag_type('bcc' + 'abbccc') assert bag_0 + bag_2 == self.bag_type('abbccc' + 'cddddd') assert bag_2 + bag_0 == self.bag_type('cddddd' + 'abbccc') assert bag_1 + bag_2 == self.bag_type('bcc' + 'cddddd') assert bag_2 + bag_1 == self.bag_type('cddddd' + 'bcc') assert bag_0 - bag_1 == self.bag_type('abc') assert bag_1 - bag_0 == self.bag_type() assert bag_0 - bag_2 == self.bag_type('abbcc') assert bag_2 - bag_0 == self.bag_type('ddddd') assert bag_1 - bag_2 == self.bag_type('bc') assert bag_2 - bag_1 == self.bag_type('ddddd') assert bag_0 * 2 == self.bag_type('abbccc' * 2) assert bag_1 * 2 == self.bag_type('bcc' * 2) assert bag_2 * 2 == self.bag_type('cddddd' * 2) assert 3 * bag_0 == self.bag_type('abbccc' * 3) assert 3 * bag_1 == self.bag_type('bcc' * 3) assert 3 * bag_2 == self.bag_type('cddddd' * 3) # We only allow floor division on bags, not regular divison, because a # decimal bag is unheard of. with cute_testing.RaiseAssertor(TypeError): bag_0 / 2 with cute_testing.RaiseAssertor(TypeError): bag_1 / 2 with cute_testing.RaiseAssertor(TypeError): bag_2 / 2 with cute_testing.RaiseAssertor(TypeError): bag_0 / self.bag_type('ab') with cute_testing.RaiseAssertor(TypeError): bag_1 / self.bag_type('ab') with cute_testing.RaiseAssertor(TypeError): bag_2 / self.bag_type('ab') assert bag_0 // 2 == self.bag_type('bc') assert bag_1 // 2 == self.bag_type('c') assert bag_2 // 2 == self.bag_type('dd') assert bag_0 // self.bag_type('ab') == 1 assert bag_1 // self.bag_type('ab') == 0 assert bag_2 // self.bag_type('ab') == 0 with cute_testing.RaiseAssertor(ZeroDivisionError): bag_0 // 0 with cute_testing.RaiseAssertor(ZeroDivisionError): bag_0 // self.bag_type() assert bag_0 % 2 == self.bag_type('ac') == bag_0 - ((bag_0 // 2) * 2) \ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_0.items())) assert bag_1 % 2 == self.bag_type('b') == bag_1 - ((bag_1 // 2) * 2) \ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_1.items())) assert bag_2 % 2 == self.bag_type('cd') == bag_2 - ((bag_2 // 2) * 2)\ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_2.items())) assert bag_0 % self.bag_type('ac') == self.bag_type('bbcc') assert bag_1 % self.bag_type('b') == self.bag_type('cc') assert bag_2 % self.bag_type('cd') == self.bag_type('dddd') assert bag_0**2 == pow(bag_0, 2) == self.bag_type('abbbbccccccccc') assert bag_1**2 == pow(bag_1, 2) == self.bag_type('bcccc') assert bag_2 ** 2 == pow(bag_2, 2) == \ self.bag_type('cddddddddddddddddddddddddd') assert pow(bag_0, 2, 3) == self.bag_type('ab') assert pow(bag_1, 2, 3) == self.bag_type('bc') assert pow(bag_2, 2, 3) == self.bag_type('cd') assert divmod(bag_0, 3) == (bag_0 // 3, bag_0 % 3) assert divmod(bag_1, 3) == (bag_1 // 3, bag_1 % 3) assert divmod(bag_2, 3) == (bag_2 // 3, bag_2 % 3) assert divmod(bag_0, self.bag_type('cd')) == \ (bag_0 // self.bag_type('cd'), bag_0 % self.bag_type('cd')) assert divmod(bag_1, self.bag_type('cd')) == \ (bag_1 // self.bag_type('cd'), bag_1 % self.bag_type('cd')) assert divmod(bag_2, self.bag_type('cd')) == \ (bag_2 // self.bag_type('cd'), bag_2 % self.bag_type('cd'))
def _bind(self, args, kwargs, partial=False): '''Private method. Don't use directly.''' arguments = OrderedDict() parameters = iter(self.parameters.values()) parameters_ex = () arg_vals = iter(args) if partial: # Support for binding arguments to 'functools.partial' objects. # See 'functools.partial' case in 'signature()' implementation # for details. for param_name, param in self.parameters.items(): if (param._partial_kwarg and param_name not in kwargs): # Simulating 'functools.partial' behavior kwargs[param_name] = param.default while True: # Let's iterate through the positional arguments and corresponding # parameters try: arg_val = next(arg_vals) except StopIteration: # No more positional arguments try: param = next(parameters) except StopIteration: # No more parameters. That's it. Just need to check that # we have no `kwargs` after this while loop break else: if param.kind == _VAR_POSITIONAL: # That's OK, just empty *args. Let's start parsing # kwargs break elif param.name in kwargs: if param.kind == _POSITIONAL_ONLY: msg = '{arg!r} parameter is positional only, ' \ 'but was passed as a keyword' msg = msg.format(arg=param.name) raise TypeError(msg) parameters_ex = (param, ) break elif (param.kind == _VAR_KEYWORD or param.default is not _empty): # That's fine too - we have a default value for this # parameter. So, lets start parsing `kwargs`, starting # with the current parameter parameters_ex = (param, ) break else: if partial: parameters_ex = (param, ) break else: msg = '{arg!r} parameter lacking default value' msg = msg.format(arg=param.name) raise TypeError(msg) else: # We have a positional argument to process try: param = next(parameters) except StopIteration: raise TypeError('too many positional arguments') else: if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): # Looks like we have no parameter for this positional # argument raise TypeError('too many positional arguments') if param.kind == _VAR_POSITIONAL: # We have an '*args'-like argument, let's fill it with # all positional arguments we have left and move on to # the next phase values = [arg_val] values.extend(arg_vals) arguments[param.name] = tuple(values) break if param.name in kwargs: raise TypeError('multiple values for argument ' '{arg!r}'.format(arg=param.name)) arguments[param.name] = arg_val # Now, we iterate through the remaining parameters to process # keyword arguments kwargs_param = None for param in itertools.chain(parameters_ex, parameters): if param.kind == _POSITIONAL_ONLY: # This should never happen in case of a properly built # Signature object (but let's have this check here # to ensure correct behaviour just in case) raise TypeError('{arg!r} parameter is positional only, ' 'but was passed as a keyword'. \ format(arg=param.name)) if param.kind == _VAR_KEYWORD: # Memorize that we have a '**kwargs'-like parameter kwargs_param = param continue param_name = param.name try: arg_val = kwargs.pop(param_name) except KeyError: # We have no value for this parameter. It's fine though, # if it has a default value, or it is an '*args'-like # parameter, left alone by the processing of positional # arguments. if (not partial and param.kind != _VAR_POSITIONAL and param.default is _empty): raise TypeError('{arg!r} parameter lacking default value'. \ format(arg=param_name)) else: arguments[param_name] = arg_val if kwargs: if kwargs_param is not None: # Process our '**kwargs'-like parameter arguments[kwargs_param.name] = kwargs else: raise TypeError('too many keyword arguments') return self._bound_arguments_cls(self, arguments)
def parameters(self): try: return types.MappingProxyType(self._parameters) except AttributeError: return OrderedDict(self._parameters.items())
def test_many_defaultfuls_and_star_args_and_star_kwargs(): ''' Test `ArgumentsProfile` with defaultful arguments, `*args` and `**kwargs`. ''' def func(a, b, c='three', d='four', e='five', f='six', *args, **kwargs): pass func(None, None) a1 = ArgumentsProfile(func, 'one', 'two', f='boomboomboom', __awesome=True, big=True) assert a1.args == ('one', 'two') assert a1.kwargs == OrderedDict( (('f', 'boomboomboom'), ('big', True), ('__awesome', True))) a2 = ArgumentsProfile(func, 'one', 'two', 'three', 'four', 'five', 'bombastic', 'meow_frr', __funky=None, zany=True, _wet=False, blue=True) assert a2.args == ('one', 'two', 'three', 'four', 'five', 'bombastic', 'meow_frr') assert a2.kwargs == OrderedDict( (('blue', True), ('zany', True), ('_wet', False), ('__funky', None))) a3 = ArgumentsProfile(func, 'one', 'two', 'three', 'four', 'five', 'bombastic', 'meow_frr', zany=True, __funky=None, blue=True, _wet=False, **OrderedDict()) assert a2 == a3 for arg_prof in [a2, a3]: # Testing `.iteritems`: assert OrderedDict(arg_prof) == OrderedDict( (('a', 'one'), ('b', 'two'), ('c', 'three'), ('d', 'four'), ('e', 'five'), ('f', 'bombastic'), ('*', ('meow_frr', )), ('blue', True), ('zany', True), ('_wet', False), ('__funky', None))) ### Testing `.__getitem__`: ########################################### # # assert (arg_prof['a'], arg_prof['b'], arg_prof['c'], arg_prof['d'], arg_prof['e'], arg_prof['f'], arg_prof['*'], arg_prof['blue'], arg_prof['zany'], arg_prof['_wet'], arg_prof['__funky']) == \ ('one', 'two', 'three', 'four', 'five', 'bombastic', ('meow_frr',), True, True, False, None) with cute_testing.RaiseAssertor(KeyError): arg_prof['non_existing_key'] # # ### Finished testing `.__getitem__`. ################################## ### Testing `.get`: ################################################### # # assert arg_prof.get('d') == arg_prof.get('d', 7) == 'four' assert arg_prof.get('non_existing_key', 7) == 7 assert arg_prof.get('non_existing_key') is None # # ### Finished testing `.get`. ########################################## ### Testing `.iterkeys`, `.keys` and `__iter__`: ###################### # # assert list(arg_prof.iterkeys()) == list(arg_prof.keys()) == \ list(arg_prof) == \ ['a', 'b', 'c', 'd', 'e', 'f', '*', 'blue', 'zany', '_wet', '__funky'] # # ### Finished testing `.iterkeys`, `.keys` and `__iter__`. ############# ### Testing `.itervalues` and `.values`: ############################## # # assert list(arg_prof.itervalues()) == list(arg_prof.values()) == \ ['one', 'two', 'three', 'four', 'five', 'bombastic', ('meow_frr',), True, True, False, None] # # ### Finished testing `.itervalues` and `.values`. ##################### ### Testing `.iteritems` and `.items`: ################################ # # items_1 = list(arg_prof.iteritems()) items_2 = arg_prof.items() assert items_1 == items_2 == zip(arg_prof.keys(), arg_prof.values()) # # ### Finished testing `.iteritems` and `.items`. ####################### ### Testing `.__contains__`: ########################################## # # for key in arg_prof: assert key in arg_prof
def __init__(self, function, *args, **kwargs): ''' Construct the arguments profile. `*args` and `**kwargs` are the arguments that go into the `function`. ''' if not callable(function): raise Exception('%s is not a callable object.' % function) self.function = function raw_args = args raw_kwargs = kwargs del args, kwargs self.args = () '''Tuple of positional arguments.''' self.kwargs = OrderedDict() '''Ordered dict of keyword arguments.''' args_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = args_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more # consistent `()`. We fix that here: if s_defaults is None: s_defaults = () getcallargs_result = cute_inspect.getcallargs(function, *raw_args, **raw_kwargs) self.getcallargs_result = getcallargs_result # The number of args which have default values: n_defaultful_args = len(s_defaults) # The word "defaultful" means "something which has a default." ####################################################################### ####################################################################### # Now we'll create the arguments profile, using a 4-phases algorithm. # # # ####################################################################### # Phase 1: We specify all the args that don't have a default as # positional args: defaultless_args = s_args[:-n_defaultful_args] if n_defaultful_args \ else s_args[:] self.args += tuple( dict_tools.get_list(getcallargs_result, defaultless_args)) ####################################################################### # Phase 2: We now have to deal with args that have a default. Some of # them, possibly none and possibly all of them, should be given # positionally. Some of them, possibly none, should be given by # keyword. And some of them, possibly none and possibly all of them, # should not be given at all. It is our job to figure out in which way # each argument should be given. # In this variable: n_defaultful_args_to_specify_positionally = None # We will put the number of defaultful arguments that should be # specified positionally. defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] # `dict` that maps from argument name to default value: defaults = OrderedDict(zip(defaultful_args, s_defaults)) defaultful_args_differing_from_defaults = { defaultful_arg for defaultful_arg in defaultful_args if defaults[defaultful_arg] != getcallargs_result[defaultful_arg] } if s_star_args and getcallargs_result[s_star_args]: # We have some arguments that go into `*args`! This means that we # don't even need to think hard, we can already be sure that we're # going to have to specify *all* of the defaultful arguments # positionally, otherwise it will be impossible to put arguments in # `*args`. n_defaultful_args_to_specify_positionally = n_defaultful_args else: # `dict` mapping from each defaultful arg to the "price" of # specifying its value: prices_of_values = OrderedDict({ defaultful_arg: len(repr(getcallargs_result[defaultful_arg])) for defaultful_arg in defaultful_args }) # The price is simply the string length of the value's `repr`. # `dict` mapping from each defaultful arg to the "price" of # specifying it as a keyword (not including the length of the # value): prices_of_keyword_prefixes = OrderedDict({ defaultful_arg: len(defaultful_arg) + 1 for defaultful_arg in defaultful_args }) # For example, if we have a defaultful arg "gravity_strength", then # specifiying it by keyword will require using the string # "gravity_strength=", which is 17 characters long, therefore the # price is 17. # Now we need to decide just how many defaultful args we are going # to specify positionally. The options are anything from `0` to # `n_defaultful_args`. We're going to go one by one, and calcluate # the price for each candidate, and put it in this dict: total_price_for_n_dasp_candidate = OrderedDict() # (The `n_dasp` here is an abbreivation of the # `n_defaultful_args_to_specify_positionally` variable defined # before.) # # After we have the price for each option, we'll select the one # with the lowest price. # One thing to do before iterating on the candidates is to find out # whether the "lonely comma discount" is in effect. # # The "lonely comma discount" is given when there's nothing but # defaultful arguments to this function, and therefore the number # of ", " strings needed here is not `candidate`, but `candidate - # 1`, unless of course candidate is zero. if not defaultless_args and \ (not s_star_args or not getcallargs_result[s_star_args]) and \ (not s_star_kwargs or not getcallargs_result[s_star_kwargs]): lonely_comma_discount_may_be_given = True else: lonely_comma_discount_may_be_given = False # Now we iterate on the candidates to find out which one has the # lowest price: for candidate in range(n_defaultful_args + 1): defaultful_args_to_specify_positionally = \ defaultful_args[:candidate] price_for_positionally_specified_defaultful_args = \ 2 * candidate + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_positionally ) ) # The `2 * candidate` addend is to account for the ", " parts # between the arguments. defaultful_args_to_specify_by_keyword = list( filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[candidate:])) price_for_defaultful_args_specified_by_keyword = \ 2 * len(defaultful_args_to_specify_by_keyword) + \ sum( dict_tools.get_list( prices_of_keyword_prefixes, defaultful_args_to_specify_by_keyword ) ) + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_by_keyword ) ) # The `2 * len(...)` addend is to account for the ", " parts # between the arguments. # Now we need to figure out if this candidate gets the "lonely # comma discount". if lonely_comma_discount_may_be_given and \ (defaultful_args_to_specify_by_keyword or \ defaultful_args_to_specify_positionally): lonely_comma_discount = -2 else: lonely_comma_discount = 0 price = price_for_positionally_specified_defaultful_args + \ price_for_defaultful_args_specified_by_keyword + \ lonely_comma_discount total_price_for_n_dasp_candidate[candidate] = price # Finished iterating on candidates! Time to pick our winner. minimum_price = min(total_price_for_n_dasp_candidate.values()) leading_candidates = [ candidate for candidate in total_price_for_n_dasp_candidate.keys() if total_price_for_n_dasp_candidate[candidate] == minimum_price ] if len(leading_candidates) == 1: # We finished with one candidate which has the minimum price. # This is our winner. (winner, ) = leading_candidates n_defaultful_args_to_specify_positionally = winner else: # We have a draw! We're gonna have to settle it by picking the # lowest candidate, because in our definition of "canonical # arguments profile", our second priority after "as few # characters as possible" is "as many keyword arguments as # possible". winner = leading_candidates[0] n_defaultful_args_to_specify_positionally = winner # We have a winner! Now we know exactly which defaultful args should # be specified positionally and which should be specified by # keyword. # First we add the positionally specified: defaultful_args_to_specify_positionally = \ defaultful_args[:n_defaultful_args_to_specify_positionally] self.args += tuple( (getcallargs_result[defaultful_arg] for defaultful_arg in defaultful_args_to_specify_positionally)) # Now we add those specified by keyword: defaultful_args_to_specify_by_keyword = list( filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[n_defaultful_args_to_specify_positionally:])) for defaultful_arg in defaultful_args_to_specify_by_keyword: self.kwargs[defaultful_arg] = getcallargs_result[defaultful_arg] ####################################################################### # Phase 3: Add the star args: if s_star_args and getcallargs_result[s_star_args]: assert not self.kwargs # Just making sure that no non-star args were specified by keyword, # which would make it impossible for us to put stuff in `*args`. self.args += getcallargs_result[s_star_args] ####################################################################### # Phase 4: Add the star kwargs: if s_star_kwargs and getcallargs_result[s_star_kwargs]: # We can't just add the `**kwargs` as is; we need to add them # according to canonical ordering. So we need to sort them first. unsorted_star_kwargs_names = \ list(getcallargs_result[s_star_kwargs].keys()) sorted_star_kwargs_names = sorted( unsorted_star_kwargs_names, key=comparison_tools.underscore_hating_key) sorted_star_kwargs = OrderedDict( zip( sorted_star_kwargs_names, dict_tools.get_list(getcallargs_result[s_star_kwargs], sorted_star_kwargs_names))) self.kwargs.update(sorted_star_kwargs) # Our 4-phases algorithm is done! The argument profile is canonical. # ####################################################################### ####################################################################### ####################################################################### # Now a bit of post-processing: _arguments = OrderedDict() dict_of_positional_arguments = OrderedDict( dict_tools.filter_items( getcallargs_result, lambda key, value: ((key not in self.kwargs) and \ (key != s_star_args) and \ (key != s_star_kwargs)) ) ) dict_of_positional_arguments.sort(key=s_args.index) _arguments.update(dict_of_positional_arguments) if s_star_args: _arguments['*'] = getcallargs_result[s_star_args] _arguments.update(self.kwargs) self._arguments = _arguments '''Ordered dict of arguments, both positional- and keyword-.''' # Caching the hash, since its computation can take a long time: self._hash = cheat_hashing.cheat_hash( (self.function, self.args, tuple(self.kwargs)))
def test_defaultfuls_and_star_kwargs(): '''Test `ArgumentsProfile` with defaultful arguments and `**kwargs`.''' def func(a, b, c=3, d=4, **kwargs): pass a1 = ArgumentsProfile(func, 1, 2) assert a1.args == (1, 2) assert not a1.kwargs # Alphabetic ordering among the `**kwargs`, but `d` is first because it's a # non-star: a2 = ArgumentsProfile(func, 1, 2, d='bombastic', zany=True, blue=True) assert a2.args == (1, 2) assert a2.kwargs == OrderedDict( (('d', 'bombastic'), ('blue', True), ('zany', True))) a3 = ArgumentsProfile(func, 1, b=2, blue=True, d='bombastic', zany=True) a4 = ArgumentsProfile(func, zany=True, a=1, b=2, blue=True, d='bombastic') a5 = ArgumentsProfile(func, 1, 2, 3, 'bombastic', zany=True, blue=True) assert a2 == a3 == a4 == a5 for arg_prof in [a2, a3, a4, a5]: # Testing `.iteritems`: assert OrderedDict(arg_prof) == OrderedDict( (('a', 1), ('b', 2), ('c', 3), ('d', 'bombastic'), ('blue', True), ('zany', True))) ### Testing `.__getitem__`: ########################################### # # assert (arg_prof['a'], arg_prof['b'], arg_prof['c'], arg_prof['d'], arg_prof['blue'], arg_prof['zany']) == \ (1, 2, 3, 'bombastic', True, True) with cute_testing.RaiseAssertor(KeyError): arg_prof['non_existing_key'] # # ### Finished testing `.__getitem__`. ################################## ### Testing `.get`: ################################################### # # assert arg_prof.get('d') == arg_prof.get('d', 7) == 'bombastic' assert arg_prof.get('non_existing_key', 7) == 7 assert arg_prof.get('non_existing_key') is None # # ### Finished testing `.get`. ########################################## ### Testing `.iterkeys`, `.keys` and `__iter__`: ###################### # # assert list(arg_prof.iterkeys()) == list(arg_prof.keys()) == \ list(arg_prof) == ['a', 'b', 'c', 'd', 'blue', 'zany'] # # ### Finished testing `.iterkeys`, `.keys` and `__iter__`. ############# ### Testing `.itervalues` and `.values`: ############################## # # assert list(arg_prof.itervalues()) == list(arg_prof.values()) == \ [1, 2, 3, 'bombastic', True, True] # # ### Finished testing `.itervalues` and `.values`. ##################### ### Testing `.__contains__`: ########################################## # # for key in arg_prof: assert key in arg_prof assert 'agaofgnafgadf' not in arg_prof assert '**' not in arg_prof
def test_operations(self): bag_0 = self.bag_type('abbccc') bag_1 = self.bag_type('bcc') bag_2 = self.bag_type('cddddd') assert bag_0 + bag_1 == self.bag_type('abbccc' + 'bcc') assert bag_1 + bag_0 == self.bag_type('bcc' + 'abbccc') assert bag_0 + bag_2 == self.bag_type('abbccc' + 'cddddd') assert bag_2 + bag_0 == self.bag_type('cddddd' + 'abbccc') assert bag_1 + bag_2 == self.bag_type('bcc' + 'cddddd') assert bag_2 + bag_1 == self.bag_type('cddddd' + 'bcc') assert bag_0 - bag_1 == self.bag_type('abc') assert bag_1 - bag_0 == self.bag_type() assert bag_0 - bag_2 == self.bag_type('abbcc') assert bag_2 - bag_0 == self.bag_type('ddddd') assert bag_1 - bag_2 == self.bag_type('bc') assert bag_2 - bag_1 == self.bag_type('ddddd') assert bag_0 * 2 == self.bag_type('abbccc' * 2) assert bag_1 * 2 == self.bag_type('bcc' * 2) assert bag_2 * 2 == self.bag_type('cddddd' * 2) assert 3 * bag_0 == self.bag_type('abbccc' * 3) assert 3 * bag_1 == self.bag_type('bcc' * 3) assert 3 * bag_2 == self.bag_type('cddddd' * 3) assert bag_0 // 2 == self.bag_type('bc') assert bag_1 // 2 == self.bag_type('c') assert bag_2 // 2 == self.bag_type('dd') assert bag_0 // self.bag_type('ab') == 1 assert bag_1 // self.bag_type('ab') == 0 assert bag_2 // self.bag_type('ab') == 0 with cute_testing.RaiseAssertor(ZeroDivisionError): bag_0 // 0 with cute_testing.RaiseAssertor(ZeroDivisionError): bag_0 // self.bag_type() assert bag_0 % 2 == self.bag_type('ac') == bag_0 - ((bag_0 // 2) * 2) \ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_0.items())) assert bag_1 % 2 == self.bag_type('b') == bag_1 - ((bag_1 // 2) * 2) \ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_1.items())) assert bag_2 % 2 == self.bag_type('cd') == bag_2 - ((bag_2 // 2) * 2)\ == self.bag_type(OrderedDict((key, count % 2) for (key, count) in bag_2.items())) assert bag_0 % self.bag_type('ac') == self.bag_type('bbcc') assert bag_1 % self.bag_type('b') == self.bag_type('cc') assert bag_2 % self.bag_type('cd') == self.bag_type('dddd') assert bag_0**2 == pow(bag_0, 2) == self.bag_type('abbbbccccccccc') assert bag_1**2 == pow(bag_1, 2) == self.bag_type('bcccc') assert bag_2 ** 2 == pow(bag_2, 2) == \ self.bag_type('cddddddddddddddddddddddddd') assert pow(bag_0, 2, 3) == self.bag_type('ab') assert pow(bag_1, 2, 3) == self.bag_type('bc') assert pow(bag_2, 2, 3) == self.bag_type('cd') assert divmod(bag_0, 3) == (bag_0 // 3, bag_0 % 3) assert divmod(bag_1, 3) == (bag_1 // 3, bag_1 % 3) assert divmod(bag_2, 3) == (bag_2 // 3, bag_2 % 3) assert divmod(bag_0, self.bag_type('cd')) == \ (bag_0 // self.bag_type('cd'), bag_0 % self.bag_type('cd')) assert divmod(bag_1, self.bag_type('cd')) == \ (bag_1 // self.bag_type('cd'), bag_1 % self.bag_type('cd')) assert divmod(bag_2, self.bag_type('cd')) == \ (bag_2 // self.bag_type('cd'), bag_2 % self.bag_type('cd'))
def _asdict(self): ''' Return a new `OrderedDict` which maps field names to their values. ''' from python_toolbox.nifty_collections import OrderedDict return OrderedDict(zip(self._fields, self))
def signature(obj): '''Get a signature object for the passed callable.''' if not callable(obj): raise TypeError('{0!r} is not a callable object'.format(obj)) if isinstance(obj, types.MethodType): sig = signature(obj.__func__) if obj.__self__ is None: # Unbound method: the first parameter becomes positional-only if sig.parameters: first = sig.parameters.values()[0].replace( kind=_POSITIONAL_ONLY) return sig.replace(parameters=(first, ) + tuple(sig.parameters.values())[1:]) else: return sig else: # In this case we skip the first parameter of the underlying # function (usually `self` or `cls`). return sig.replace(parameters=tuple(sig.parameters.values())[1:]) try: sig = obj.__signature__ except AttributeError: pass else: if sig is not None: return sig try: # Was this function wrapped by a decorator? wrapped = obj.__wrapped__ except AttributeError: pass else: return signature(wrapped) if isinstance(obj, types.FunctionType): return Signature.from_function(obj) if isinstance(obj, functools.partial): sig = signature(obj.func) new_params = OrderedDict(sig.parameters.items()) partial_args = obj.args or () partial_keywords = obj.keywords or {} try: ba = sig.bind_partial(*partial_args, **partial_keywords) except TypeError as ex: msg = 'partial object {0!r} has incorrect arguments'.format(obj) raise ValueError(msg) for arg_name, arg_value in ba.arguments.items(): param = new_params[arg_name] if arg_name in partial_keywords: # We set a new default value, because the following code # is correct: # # >>> def foo(a): print(a) # >>> print(partial(partial(foo, a=10), a=20)()) # 20 # >>> print(partial(partial(foo, a=10), a=20)(a=30)) # 30 # # So, with 'partial' objects, passing a keyword argument is # like setting a new default value for the corresponding # parameter # # We also mark this parameter with '_partial_kwarg' # flag. Later, in '_bind', the 'default' value of this # parameter will be added to 'kwargs', to simulate # the 'functools.partial' real call. new_params[arg_name] = param.replace(default=arg_value, _partial_kwarg=True) elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and not param._partial_kwarg): new_params.pop(arg_name) return sig.replace(parameters=new_params.values()) sig = None if isinstance(obj, type): # obj is a class or a metaclass # First, let's see if it has an overloaded __call__ defined # in its metaclass call = _get_user_defined_method(type(obj), '__call__') if call is not None: sig = signature(call) else: # Now we check if the 'obj' class has a '__new__' method new = _get_user_defined_method(obj, '__new__') if new is not None: sig = signature(new) else: # Finally, we should have at least __init__ implemented init = _get_user_defined_method(obj, '__init__') if init is not None: sig = signature(init) elif not isinstance(obj, _NonUserDefinedCallables): # An object with __call__ # We also check that the 'obj' is not an instance of # _WrapperDescriptor or _MethodWrapper to avoid # infinite recursion (and even potential segfault) call = _get_user_defined_method(type(obj), '__call__', 'im_func') if call is not None: sig = signature(call) if sig is not None: # For classes and objects we skip the first parameter of their # __call__, __new__, or __init__ methods return sig.replace(parameters=tuple(sig.parameters.values())[1:]) if isinstance(obj, types.BuiltinFunctionType): # Raise a nicer error message for builtins msg = 'no signature found for builtin function {0!r}'.format(obj) raise ValueError(msg) raise ValueError( 'callable {0!r} is not supported by signature'.format(obj))
def decorator(function): # In case we're being given a function that is already cached: if getattr(function, 'is_cached', False): return function if max_size == infinity: if time_to_keep: sorting_key_function = lambda sleek_call_args: \ cached._cache[sleek_call_args][1] def remove_expired_entries(): almost_cutting_point = \ binary_search.binary_search_by_index( list(cached._cache.keys()), _get_now(), sorting_key_function, rounding=binary_search.LOW ) if almost_cutting_point is not None: cutting_point = almost_cutting_point + 1 for key in cached._cache.keys()[:cutting_point]: del cached._cache[key] @misc_tools.set_attributes(_cache=OrderedDict()) def cached(function, *args, **kwargs): remove_expired_entries() sleek_call_args = \ SleekCallArgs(cached._cache, function, *args, **kwargs) try: return cached._cache[sleek_call_args][0] except KeyError: value = function(*args, **kwargs) cached._cache[sleek_call_args] = (value, _get_now() + time_to_keep) cached._cache.sort(key=sorting_key_function) return value else: # not time_to_keep @misc_tools.set_attributes(_cache={}) def cached(function, *args, **kwargs): sleek_call_args = \ SleekCallArgs(cached._cache, function, *args, **kwargs) try: return cached._cache[sleek_call_args] except KeyError: cached._cache[sleek_call_args] = value = \ function(*args, **kwargs) return value else: # max_size < infinity @misc_tools.set_attributes(_cache=OrderedDict()) def cached(function, *args, **kwargs): sleek_call_args = \ SleekCallArgs(cached._cache, function, *args, **kwargs) try: result = cached._cache[sleek_call_args] cached._cache.move_to_end(sleek_call_args) return result except KeyError: cached._cache[sleek_call_args] = value = \ function(*args, **kwargs) if len(cached._cache) > max_size: cached._cache.popitem(last=False) return value result = decorator_tools.decorator(cached, function) def cache_clear(key=CLEAR_ENTIRE_CACHE): if key is CLEAR_ENTIRE_CACHE: cached._cache.clear() else: try: del cached._cache[key] except KeyError: pass result.cache_clear = cache_clear result.is_cached = True return result