Is it possible to write a function signature that behaves like getattr ()?

According to help(getattr)

two or three arguments are accepted:

getattr(...)
    getattr(object, name[, default]) -> value

      

By doing some simple tests, we can confirm this:

>>> obj = {}
>>> getattr(obj, 'get')
<built-in method get of dict object at 0x7f6d4beaf168>
>>> getattr(obj, 'bad', 'with default')
'with default'

      

Too few / too many arguments also behave as expected:

>>> getattr()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr expected at least 2 arguments, got 0
>>> getattr(obj, 'get', 'with default', 'extra')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr expected at most 3 arguments, got 4

      

The argument names given in the help text do not appear to be accepted as keyword arguments:

>>> getattr(object=obj, name='get')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr() takes no keyword arguments

      

The module inspect

doesn't help here:

>>> import inspect
>>> inspect.getargspec(getattr)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/inspect.py", line 816, in getargspec
    raise TypeError('{!r} is not a Python function'.format(func))
TypeError: <built-in function getattr> is not a Python function

(messaging is a little different in python3, but the gist is the same)

      

Now the question is, is there an easy way to write my own Python function with a signature that behaves exactly like a signature getattr

? That is, the keyword arguments are invalid and the min / max number of arguments is enforced? The closest I've come to is the following:

def myfunc(*args):
    len_args = len(args)
    if len_args < 2:
        raise TypeError('expected at least 2 arguments, got %d' % len_args)
    elif len_args > 3:
        raise TypeError('expected at most 3 arguments, got %d' % len_args)
    ...

      

But instead of meaningful argument names like object

and name

, we get args[0]

and args[1]

. It's also a lot of patterns, and seems downright frustrating. I know that being inline getattr

should have a significantly different implementation than typical Python code, and there may be no way to perfectly emulate how it behaves. But this is a curiosity that I have had for a while.

+3


source to share


2 answers


These types of function signatures are special functions written in C using the function family PyArg_Parse*

, and the Argument Clinic preprocessor. There is no built-in way to write such a signature in Python. The closest you can get is what you have already come up with using *args

.



(As an aside, a syntax has already been chosen if they decide to implement this functionality as described in PEP 457 , but at the moment this syntax is only used in the documentation, and a small variation is used in Argument Clinic.)

+3


source


This code indicates most of your requirements:

def anonymise_args(fn):
    @functools.wraps(fn)
    def wrap(*args):
        return fn(*args)
    return wrap


@anonymise_args
def myfunc(obj, name, default=None):
    print obj, name, default

      



  • keyword arguments are not allowed

    x.myfunc(obj=1, name=2)
    TypeError: wrap() got an unexpected keyword argument 'obj'
    
          

  • Given the minimum / maximum number of arguments

    x.myfunc(1,2,3,4)
    TypeError: myfunc() takes at most 3 arguments (4 given)
    
          

  • meaningful argument names

  • not many templates

+3


source







All Articles