问题
I experimented with the next code:
>>> f = object()
# It's obvious behavior:
>>> f.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'
# However, the next one is surprising me!
>>> getattr(f, 'foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'
# And this one returns None as expected:
>>> getattr(f, 'foo', None)
Then I found this pseudo-signature of getattr()
in the PyCharm IDE:
def getattr(object, name, default=None): # known special case of getattr
"""
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.
"""
pass
My question is how does python distinguish this two scenarios of using getattr()
(and maybe other functions) internally? And is it possible to do something similar entirely in the client side code?
回答1:
As @scytale said, the pseudo-signature of getattr
doesn't quite correspond to its implementations. I've seen attempts to replicate the behaviour in pure Python that look something like this:
class MyObject(object):
__marker = object()
def getvalue(key, default=__marker):
...
if key is __marker:
# no value supplied for default
....
In other words, use a marker value that a caller cannot easily supply to check if no value was given as the default rather than None
.
回答2:
getattr
is implemented in C so how it's done differ a bit from how it's done in python. In C there are a couple of calling conventions, getattr
is using what's called METH_VARARG
which means that it expects unspecified number positional arguments passed as a tuple, the function then checks that it's either a tuple of length 2 or 3 (via PyArg_UnpackTuple
) and acts accordingly (when unpacked the default argument will when omitted be a NULL
pointer which is different from any python object).
It's similar of what could be done in python:
def mygetattr(*args):
if len(args) != 2 and len(args) != 3:
raise Exception
try:
return getattr(args[0], args[1])
except AttributeError:
if len(args) == 3:
return args[2]
raise
But normally in python one would actually explicitely state the parameters that's mandatory and then use *args
to handle the optional parameters (ie def mygetattr(obj, key, *args):...
)
回答3:
getattr
is a built-in - therefore it is implemented in C - the pseudo-signature is not a precise guide to how it works.
回答4:
I would go in the same direction as @skyking but also allow using named arguments like this:
def getattr(object, name, *args, **kwargs):
if len(args) == 1:
# third positional argument is used as default
default = args[1]
elif 'default' in kwargs and len(kwargs) == 1:
# provided as named argument
default = kwargs['default']
elif len(kwargs) > 0 or len(args) > 0:
# unknown arguments
else:
# no 'default' was supplied
In the above code, you can insert the processing code and exceptions to be raised wherever it is appropriate!
回答5:
getattr docs says
getattr(object, name[, default])
Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
If you want to implement same try this
def mygetattr(obj, attr, default):
ret_val = defaule
try:
ret_val = getattr(obj, attr)
except AttributeError:
pass
return ret_val
来源:https://stackoverflow.com/questions/32499863/how-does-python-distinguish-explicitly-passed-none-as-argument-in-built-ins