A static type analyzer for Python code
Home
Developer guide
Workflow
• Development process
• Python version upgrades
• Supporting new features
Program analysis
• Bytecode
• Directives
• Main loop
• Stack frames
• Typegraph
Data representation
• Abstract values
• Attributes
• Overlays
• Special builtins
• Type annotations
• Type stubs
• TypeVars
Configuration
Style guide
Tools
Documentation debugging
View the Project on GitHub google/pytype
Hosted on GitHub Pages — Theme by orderedlist
The attribute module handles getting and setting attributes on abstract values. It is mostly used to:
LOAD_ATTR and STORE_ATTR opcodes, and__init__ for instance creation and __setitem__ for modifying a dictionary.We will refer to the object on which an attribute is being get or set as the target object.
attribute has two public methods: get_attribute and set_attribute.
We’ll start by looking at set_attribute, since it’s by far the simpler of the
two top-level methods:

set_attribute recursively calls itself as needed to unpack the target
object. For example, when passed a union, set_attribute iterates through
the union’s options and calls itself on each one.set_attribute then calls _set_member, which first calls
_maybe_load_as_instance_attribute to make sure the requested attribute has
been lazily loaded into the object’s members dict, then modifies the
appropriate members entry.get_attribute is considerably more complicated, due to the lengths it goes to
in order to emulate the behavior of the Python interpreter:

set_attribute, get_attribute recursively calls itself to unpack the
target object.Once the object has been unpacked, it is either a module, class instance,
class, or super instance (the result of a super() call). The corresponding
helper method is called.
a. _get_module_attribute forwards to _get_instance_attribute.
b. _get_instance_attribute and _get_class_attribute forward to
_get_attribute.
c. _get_attribute_from_super_instance determines the super class and calls
_lookup_from_mro_and_handle_descriptors on it.
Python classes can define a magic method, __getattribute__, that is called
unconditionally to implement attribute access, as well as a fallback method,
__getattr__, that is only called when normal attribute lookup fails. (See
the Python data model documentation for more
information.) To mimic this, _get_attribute first calls
_get_attribute_computed("__getattribute__"), then calls either
_get_member or _lookup_from_mro_and_handle_descriptors depending on
whether the target object is a class instance or a class, and finally calls
_get_attribute_computed("__getattr__").
Both _get_attribute_computed and _lookup_from_mro_and_handle_descriptors
use _lookup_from_mro to do attribute lookup on a class. The latter walks
the class’s MRO, calling _get_attribute_flat - which in turn calls
_get_member - to check for the attribute on the class and its bases.
_set_member, _get_member uses
_maybe_load_as_instance_attribute to force lazy loading and then checks
the members dict for the requested attribute.Occasionally, an abstract class needs to bypass normal attribute lookup. In such
cases, the class can implement a get_special_attribute method whose return
value will be immediately used. For example, HasSlots returns the
custom method implementations in its slots rather than the underlying
PyTDFunction objects.
attribute.get_attribute takes an optional valself parameter, a binding to an
abstract value related to the target object. When to pass valself and what
value to use can be tricky:
valself is strictly optional and should be a binding to the target
object. When given, valself will be included in the origins of the returned
variable and therefore be taken into account in typegraph solver queries.valself is interpreted roughly as
follows:
If valself is a binding to an instance of the target class, then the
attribute lookup will ignore the class’s metaclass. For example, with:
class Foo(type):
def f(cls):
pass
class Bar(metaclass=Foo):
pass
get_attribute(node, Bar, 'f', valself=Binding(Instance(Bar))) will return
None despite f existing on Foo. The reasoning is that if the original
query were for an attribute f on an instance of Bar, taking the
metaclass into account would be unexpected.
valself is a binding to the class, then the attribute lookup will use
the metaclass. So get_attribute(node, Bar, 'f', valself=Binding(Bar))
will return Foo.f, as we’re treating Bar as not only a class but also
an instance of its metaclass.valself is None and we are looking up __getitem__, then the class
is treated as a type annotation.valself is None, then attribute lookup will behave the
same as in case (1), except that methods will be returned unbound, since
there is no instance of the class to bind to.