Ei vielä käännetty
Tätä sivua ei ole vielä käännetty suomeksi, joten se näytetään englanniksi. Auta kääntämään
hasattr() Function Complexity¶
The hasattr() function checks whether an object has a named attribute. It is
one getattr() whose AttributeError becomes False, so it costs exactly
what the attribute lookup costs: a cached class-level lookup plus an instance
dict probe, or whatever a user-defined hook does.
Complexity Reference¶
Let d be the length of the object's class MRO and g the cost, in time and
space, of a user-defined hook: __getattr__, __getattribute__, or a
descriptor such as a property getter.
| Operation | Time | Space | Notes |
|---|---|---|---|
hasattr(obj, name) |
O(1) avg | O(1) | The class-level lookup is cached per class and name object, for misses as well as hits, so d does not matter once the cache is warm - provided name is the same str object each time, as a literal in the source or a sys.intern() result is |
hasattr(obj, name) with a name built at run time |
O(d) | O(1) | An f-string, a concatenation or a string read from input is usually a new object on every call, and the cache matches name objects rather than their text, so every call walks the MRO. sys.intern(name) returns one shared object per text and restores the cached cost. A str subclass, or a name longer than 100 characters, is never cached |
hasattr(obj, name), first lookup after the class or a base changed |
O(d) | O(1) | Walks the MRO once and caches the result. From Python 3.13, once a class has been changed and looked up again more than 1,000 times it is never cached again, and every lookup on it pays O(d) |
hasattr(obj, name) through a hook |
O(g) | O(g) | On top of the lookup above. A property getter runs, and __getattr__ runs on every miss. Only AttributeError becomes False; any other exception propagates |
hasattr(obj, name) with a non-str name |
O(1) | O(1) | Raises TypeError before any lookup |
Basic Usage¶
Check Attribute Existence¶
# O(1) avg - one cached lookup
class MyClass:
attr = 42
obj = MyClass()
# Safe check - O(1) avg
if hasattr(obj, 'attr'):
print(obj.attr) # 42
# A miss is cached too - O(1) avg
if hasattr(obj, 'missing'):
print("Has missing")
else:
print("No missing attribute")
Avoid AttributeError¶
# All three forms do the same lookup; they differ in how many times they do it
class User:
name = "Alice"
user = User()
# try/except - one lookup
try:
email = user.email
except AttributeError:
email = "no@email.com"
# hasattr() then access - two lookups when the attribute exists
if hasattr(user, 'email'):
email = user.email
else:
email = "no@email.com"
# getattr() with a default - one lookup either way, and the clearest
email = getattr(user, 'email', "no@email.com")
Complexity Details¶
Inheritance Chain Traversal¶
# The first lookup of a name on a class walks the MRO - O(d)
# Later lookups of that name on that class are cached - O(1) avg
class A:
a_attr = 1
class B(A):
b_attr = 2
class C(B):
c_attr = 3
class D(C):
d_attr = 4
obj = D()
# MRO: [D, C, B, A, object]
has_a = hasattr(obj, 'a_attr') # O(d) once: D, C, B, then found in A
has_a = hasattr(obj, 'a_attr') # O(1) avg: cached for (D, 'a_attr')
has_x = hasattr(obj, 'x_attr') # O(d) once, then the miss is cached too
# Assigning to a class, or to any of its bases, invalidates its cache
C.c_attr = 30
has_a = hasattr(obj, 'a_attr') # O(d) again, once
Names Built at Run Time¶
# The cache matches the name object, not its text
import sys
class Shape:
def draw_circle(self):
pass
def supports(obj, kind):
# The f-string is a new object per call - every call walks the MRO, O(d)
return hasattr(obj, f"draw_{kind}")
def supports_interned(obj, kind):
# One shared object per text - the lookup is O(1) avg once the cache is warm
return hasattr(obj, sys.intern(f"draw_{kind}"))
shape = Shape()
assert supports(shape, "circle") and not supports(shape, "square")
assert supports_interned(shape, "circle") and not supports_interned(shape, "square")
What hasattr() Catches¶
# hasattr() is one getattr() call; only AttributeError means False
def hasattr_simulation(obj, name):
"""What hasattr() does - the cost is the lookup's"""
if not isinstance(name, str):
raise TypeError("attribute name must be string")
try:
getattr(obj, name)
except AttributeError:
return False
return True
class Strict:
@property
def broken(self):
raise KeyError("not an AttributeError")
obj = Strict()
print(hasattr(obj, 'missing')) # False
try:
hasattr(obj, 'broken') # the KeyError propagates
except KeyError:
print("only AttributeError means False")
try:
hasattr(obj, 42)
except TypeError:
print("the name must be a str")
Performance Patterns¶
hasattr vs getattr with Default¶
# hasattr() then getattr() - two lookups
class Config:
host = "localhost"
obj = Config()
if hasattr(obj, 'host'): # lookup one
value = getattr(obj, 'host') # lookup two
else:
value = None
# getattr() with a default - one lookup
value = getattr(obj, 'host', None)
Checking Multiple Attributes¶
# O(n) - one cached lookup per name checked
class Message:
to = "user@example.com"
subject = "Hello"
body = "Content"
msg = Message()
required = ['to', 'subject', 'body']
valid = all(hasattr(msg, attr) for attr in required) # O(n) - n names
# dir() is not a shortcut: it collects and sorts every attribute of the
# object and its whole MRO, so it grows with that attribute count where a
# few hasattr() calls do not
valid = {'to', 'subject', 'body'} <= set(dir(msg)) # at least O(a) in the a names listed
Common Use Cases¶
Optional Feature Detection¶
# O(1) avg - check if feature is available
class DataStore:
def load(self):
pass
def save(self):
pass
# backup() is optional
class DatabaseStore(DataStore):
def backup(self):
"""Advanced feature"""
pass
store = DatabaseStore()
# O(1) avg - check for optional method
if hasattr(store, 'backup'):
store.backup() # Call if available
Protocol Checking¶
# One cached lookup per protocol method
def is_iterable(obj):
"""Check for the iterator protocol - one lookup"""
return hasattr(obj, '__iter__')
def is_context_manager(obj):
"""Check for the context manager protocol - one or two lookups"""
return hasattr(obj, '__enter__') and hasattr(obj, '__exit__')
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
obj = Countdown(3)
if is_iterable(obj):
for item in obj:
print(item) # 3, 2, 1
Safe Method Invocation¶
# hasattr() then the call - two lookups when the method exists
class Handler:
def process(self, data):
return f"Processed: {data}"
class MinimalHandler:
pass
def safe_process(obj, data):
"""Call method if it exists"""
if hasattr(obj, 'process'): # O(1) avg
return obj.process(data)
return None
handler = Handler()
minimal = MinimalHandler()
result1 = safe_process(handler, "data") # Processed: data
result2 = safe_process(minimal, "data") # None
Advanced Usage¶
Dynamic Behavior Routing¶
# Route to a handler by name
class Plugin:
def on_start(self):
pass
def on_stop(self):
pass
class AdvancedPlugin(Plugin):
def on_update(self):
pass
def trigger_event(obj, event_name):
"""Safely trigger an event - one lookup"""
handler = getattr(obj, event_name, None)
if handler is not None:
handler()
plugin = AdvancedPlugin()
trigger_event(plugin, 'on_start') # Works
trigger_event(plugin, 'on_update') # Works
trigger_event(plugin, 'missing') # No error
Attribute Validation¶
# O(n) - one lookup per required name
def validate_object(obj, required_attrs):
"""Return the required names the object lacks - O(n)"""
missing = []
for attr in required_attrs: # n iterations
if not hasattr(obj, attr): # O(1) avg each
missing.append(attr)
return missing if missing else None
class FileWriter:
def write(self):
pass
def close(self):
pass
required = ['write', 'close', 'flush']
missing = validate_object(FileWriter(), required)
# Returns: ['flush'] - missing method
Duck Typing Implementation¶
# Two lookups per capability used: the check and the call
def transform(data):
return data.upper()
def process_data(obj):
"""Work with any object that has the required methods"""
if hasattr(obj, 'read'):
data = obj.read()
else:
raise TypeError("Object doesn't support read")
if hasattr(obj, 'write'):
obj.write(transform(data))
else:
raise TypeError("Object doesn't support write")
# Works with file-like objects
import io
buffer = io.StringIO("content")
process_data(buffer)
print(buffer.getvalue()) # contentCONTENT
Practical Examples¶
Conditional Initialization¶
# Up to three lookups: two checks and one read
class FlexibleConfig:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def get_port(self):
"""Get port with fallback"""
if hasattr(self, 'port'): # O(1) avg
return self.port
elif hasattr(self, 'default_port'): # O(1) avg
return self.default_port
else:
return 8000
config = FlexibleConfig(default_port=9000)
port = config.get_port() # 9000
Plugin System¶
# O(n) - one lookup per feature name
class PluginManager:
def get_capabilities(self, plugin):
"""List plugin capabilities - O(n)"""
capabilities = []
features = ['process', 'validate', 'transform', 'cache']
for feature in features: # n iterations
if hasattr(plugin, feature): # O(1) avg each
capabilities.append(feature)
return capabilities
class SimplePlugin:
def process(self):
pass
def validate(self):
pass
manager = PluginManager()
caps = manager.get_capabilities(SimplePlugin())
# Returns: ['process', 'validate']
Edge Cases¶
Descriptors Run Inside hasattr()¶
# hasattr() finds attributes, methods and properties alike - and runs getters
class Example:
value = 42 # Attribute
def method(self): # Callable
pass
@property
def computed(self): # Property - its getter runs on every hasattr()
print("computing")
return 100
obj = Example()
print(hasattr(obj, 'value')) # True - attribute
print(hasattr(obj, 'method')) # True - callable
print(hasattr(obj, 'computed')) # prints "computing", then True
Side Effects of getattr¶
# __getattr__ runs on every miss - its cost and side effects are hasattr()'s
class WithSideEffect:
def __getattr__(self, name):
print(f"Looking up: {name}")
raise AttributeError(name)
obj = WithSideEffect()
result = hasattr(obj, 'missing') # Prints "Looking up: missing"; result is False
# A __getattr__ that returns instead of raising makes every name exist
class Permissive:
def __getattr__(self, name):
return None
print(hasattr(Permissive(), 'anything')) # True
Best Practices¶
✅ Do:
- Use
hasattr()to test for an attribute before acting on it - Use
getattr()with a default when the value is needed - it is one lookup, not two - Use it for duck typing and protocol checking
- Expect property getters and
__getattr__to run inside it - Pass a name built at run time through
sys.intern()when it is checked repeatedly on a deep hierarchy - a fresh string object misses the lookup cache
❌ Avoid:
hasattr()followed bygetattr()on the same namedir()as a way to check for a few names - it builds and sorts the whole attribute list- Relying on
hasattr()to swallow anything butAttributeError - Reassigning a class's attributes in a hot loop on Python 3.13 or later - after more than 1,000 rounds of change and lookup the class is never cached again
Related Functions¶
- getattr() - Get attribute with default
- setattr() - Set attribute value
- delattr() - Delete attribute
- dir() - List all attributes
- vars() - Get dict
- callable() - Check if callable
Version Notes¶
- Python 2.x: Every
Exceptionraised by the lookup, not onlyAttributeError, is swallowed and reported asFalse - Python 3.2+: Only
AttributeErrorbecomesFalse; every other exception propagates - Python 3.13+: A class that has been changed and looked up again more than 1,000 times stops being cached, so lookups on it are O(d) from then on
- Python 3: A non-
strname,bytesincluded, raisesTypeError