How Does Python Interpreter Look For Types?
Solution 1:
Your question seems to be, "How is this a type declaration?" The answer is, it isn't a type declaration. Names in Python have no type associated with them. Names refer to values, and values have a type, determined at runtime.
When Python executes a = float()
, it looks up the name float
, and finds it in the builtins, it's a function. It calls that function with no arguments. The return value is a float object. The name a
is then made to refer to that object. That's all it does. Before it's executed this line of code, Python has no idea what a
will become, and it has no idea that floats will be involved.
Python is dynamic, so your line of code could have been in this program:
def float():
return "I'm not a float!"
a = float()
Now when a = float()
is executed, the builtin has nothing to do with it, and there are no floats anywhere, and a
refers to a string.
For more on names and values, see Facts and Myths about Python Names and Values.
Solution 2:
If a name is to be resolved as a global, and there is no matching global (you don't have a function named float
in your module), then Python looks at the __builtins__
object that is part of the global namespace for every module.
That object is a built-in module, defined in C. See Python/bltinmodule.c
.
In the case of float
, the name is bound to the C PyFloat_Type
structure, in a series of assignments, and the PyFloat_Type
structure is defined in Objects/floatobject.c
Fundamentally, Python doesn't have 'type declarations'; float
and str
, etc., are just C structures that conform to certain behaviour; they produce values that Python can recognize as a certain type when queried. Thus, anything produced by float()
points back to float
as its type.
And because __builtins__
is only consulted when you don't have a global by the same name already, you can easily provide your own implementation of the float()
callable; one that returns something entirely different when called.
If you wanted to enumerate all built-in type objects, loop over the builtins and enumerate anything that is an instance of type()
:
import __builtin__
for name, obj invars(__builtin__).iteritems():
ifisinstance(obj, type) andnotissubclass(obj, BaseException):
print name, obj
How __builtins__
works is an implementation detail of the CPython implementation, but the same objects are exposed as the __builtin__
module used here.
Note that you cannot ever hope to gather all possible types code can produce. Functions can produce new types that are local to the function, and are not listed in the module globals; types do not need to be listed there to work:
defdynamic_types(name, base=object):
classDynamicType(base):
def__repr__(self):
'<{} (DynamicType) at {:#x}>'.format(self.__name__, id(self))
DynamicType.__name__ = name
return DynamicType()
The above function produces objects with a new type (Python classes are types too) each time you call it. You'll not find that type in the module globals, nor in the builtins.
Solution 3:
In your question, you wrote "how does the interpreter build a complete list of all possible types" - but this contains a false assumption. There is no complete list of possible types. There is an effective set of existing types, but they are not arranged in a list.
You have a namespace, which typically looks in a certain order: function (locals), module (globals), builtins. In parsing code the compiler also selects type constructors for literals. Every name at each place could refer to any object, among which a lot are types:
>>> [name for name indir(__builtins__)
ifisinstance(getattr(__builtins__,name), type)]
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError',
'EnvironmentError', 'Exception', 'FloatingPointError', 'FutureWarning',
'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError',
'NameError', 'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning',
'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning',
'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', 'basestring',
'bool', 'buffer', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict',
'enumerate', 'file', 'float', 'frozenset', 'int', 'list', 'long', 'memoryview',
'object', 'property', 'reversed', 'set', 'slice', 'staticmethod', 'str', 'super',
'tuple', 'type', 'unicode', 'xrange']
The interpreter isn't actually interested in how many types there are, only how they each apply to interpreting the code to be run. It finds that by using the type pointer, which every object has:
>>> type(1)
<type'int'>
The result is that if two types don't fit, it's their job to tell you:
>>>1+"foo"
Traceback (most recent calllast):
File "<stdin>", line 1, in<module>
TypeError: unsupported operand type(s) for+: 'int'and'str'>>> "foo"+1
Traceback (most recent calllast):
File "<stdin>", line 1, in<module>
TypeError: cannot concatenate 'str'and'int' objects
>>>
The first +
is actually int.__add__
, and the second is str.__add__
, simply looked up via the object on the left. (It gets a little more complex with alternate attempts like __radd__
, but the basic principle is the same.)
Post a Comment for "How Does Python Interpreter Look For Types?"