When we have a reference to an object, how do we know the type of this object and what methods it has?
First, let’s determine the object type using the type() function:
Basic types can all be checked with type():
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
If a variable points to a function or class, it can also be checked with type():
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
But what type does the type() function return? It returns the corresponding Class type. If we want to make judgments in an if statement, we need to compare whether the type of two variables is the same:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
For basic data types, we can directly use int, str, etc. But what if we want to check if an object is a function? We can use the constants defined in the types module:
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
For class inheritance hierarchies, using type() is inconvenient. To check the type of a class, we can use the isinstance() function.
Let’s review the example from last time with this inheritance hierarchy:
object -> Animal -> Dog -> Husky
The isinstance() function can tell us if an object is of a certain type. First, create objects of three types:
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
Then perform the checks:
>>> isinstance(h, Husky)
True
This is correct because the variable h points to a Husky object.
Check again:
>>> isinstance(h, Dog)
True
Although h is a Husky type itself, since Husky inherits from Dog, h is also considered a Dog type. In other words, isinstance() checks if an object is an instance of the specified type or any of its parent classes in the inheritance chain.
Therefore, we can confirm that h is also an Animal type:
>>> isinstance(h, Animal)
True
Similarly, d (which is actually a Dog type) is also an Animal type:
>>> isinstance(d, Dog) and isinstance(d, Animal)
True
However, d is not a Husky type:
>>> isinstance(d, Husky)
False
Basic types that can be checked with type() can also be checked with isinstance():
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
Additionally, we can check if a variable is one of several types (e.g., check if it is a list or tuple):
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
Tip: Always prioritize using
isinstance()to check types, as it covers the specified type and all its subclasses comprehensively.
To get all attributes and methods of an object, use the dir() function, which returns a list of strings. For example, get all attributes and methods of a string object:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
Attributes and methods with the __xxx__ format have special purposes in Python. For example, the __len__ method returns the length. When you call the len() function to get an object’s length, Python internally calls the object’s __len__() method. Thus, the following code is equivalent:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
If we want to use len(myObj) with a custom class, we just need to implement the __len__() method:
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100
The remaining entries are regular attributes/methods (e.g., lower() returns a lowercase string):
>>> 'ABC'.lower()
'abc'
Listing attributes/methods alone is not enough. Combined with getattr(), setattr(), and hasattr(), we can directly manipulate an object’s state:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
Next, test the object’s attributes:
>>> hasattr(obj, 'x') # Does it have attribute 'x'?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # Does it have attribute 'y'?
False
>>> setattr(obj, 'y', 19) # Set attribute 'y'
>>> hasattr(obj, 'y') # Does it have attribute 'y' now?
True
>>> getattr(obj, 'y') # Get attribute 'y'
19
>>> obj.y # Access attribute 'y' directly
19
Attempting to get a non-existent attribute raises an AttributeError:
>>> getattr(obj, 'z') # Get attribute 'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
You can pass a default parameter to return a fallback value if the attribute does not exist:
>>> getattr(obj, 'z', 404) # Get 'z', return 404 if missing
404
We can also retrieve an object’s methods:
>>> hasattr(obj, 'power') # Does it have method 'power'?
True
>>> getattr(obj, 'power') # Get method 'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # Assign method to variable fn
>>> fn # fn points to obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # Calling fn() is equivalent to obj.power()
81
Using Python’s built-in functions, we can inspect any object and access its internal data. Note that we only need to retrieve object information when we don’t know it in advance.
Instead of writing:
sum = getattr(obj, 'x') + getattr(obj, 'y')
We should write (if possible):
sum = obj.x + obj.y
A proper use case example:
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
Suppose we want to read an image from a file stream fp. First, we check if the fp object has a read() method—if it does, fp is a stream; if not, reading is impossible. This is where hasattr() is useful.
Note that in dynamic languages like Python (following the duck typing principle), having a read() method does not guarantee fp is a file stream—it could also be a network stream or in-memory byte stream. As long as the read() method returns valid image data, the image reading functionality remains unaffected.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# type()
print("type(123) =", type(123))
print("type('123') =", type("123"))
print("type(None) =", type(None))
print("type(abs) =", type(abs))
import types
print("type('abc')==str?", type("abc") == str)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
print("hasattr(obj, 'x') =", hasattr(obj, "x"))
print("hasattr(obj, 'y') =", hasattr(obj, "y"))
setattr(obj, "y", 19)
print("hasattr(obj, 'y') =", hasattr(obj, "y"))
print("getattr(obj, 'y') =", getattr(obj, "y"))
print("obj.y =", obj.y)
print("getattr(obj, 'z') =", getattr(obj, "z", 404))
f = getattr(obj, "power")
print(f)
print(f())