Appearance
Getting Object Information
When we have a reference to an object, how can we determine what type it is and what methods it has?
Using type()
First, we can determine the type of an object using the type()
function.
Basic types can be checked with type()
:
python
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
If a variable points to a function or a class, we can also use type()
to check:
python
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
What does the type()
function return? It returns the corresponding class type. If we need to check in an if
statement, we must compare the type of two variables:
python
>>> 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 need to check if an object is a function? We can use constants defined in the types
module:
python
>>> 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
Using isinstance()
For class inheritance relationships, using type()
can be inconvenient. To check a class type, we can use the isinstance()
function.
Let’s revisit the previous example, where the inheritance relationship is:
object -> Animal -> Dog -> Husky
The isinstance()
function can tell us whether an object is of a certain type. First, create objects of the three types:
python
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
Then check:
python
>>> isinstance(h, Husky)
True
This is correct since the variable h
points to a Husky
object.
Now check:
python
>>> isinstance(h, Dog)
True
Although h
is of type Husky
, it is also of type Dog
because Husky
inherits from Dog
. In other words, isinstance()
checks if an object is of that type or exists within the type's inheritance chain.
Thus, we can be confident that h
is still of type Animal
:
python
>>> isinstance(h, Animal)
True
Similarly, the actual type of d
being Dog
also means it is of type Animal
:
python
>>> isinstance(d, Dog) and isinstance(d, Animal)
True
However, d
is not of type Husky
:
python
>>> isinstance(d, Husky)
False
Basic types can also be checked with isinstance()
:
python
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
Additionally, you can check if a variable is one of multiple types, as shown below:
python
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
Tip:
Always prefer using isinstance()
to check types, as it captures both the specified type and its subclasses.
Using dir()
To obtain all attributes and methods of an object, we can use the dir()
function. It returns a list of strings containing all the attributes and methods. For example, to get all attributes and methods of a str
object:
python
>>> dir('ABC')
['__add__', '__class__', ..., '__subclasshook__', 'capitalize', 'casefold', ..., 'zfill']
Attributes and methods that start and end with __
are special in Python. For instance, the __len__
method returns the length. When you call the len()
function to get an object's length, it actually calls the object's __len__()
method:
python
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
If you want to use len(myObj)
for your own class, you must implement the __len__()
method:
python
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100
The remaining attributes or methods are ordinary ones. For example, lower()
returns the string in lowercase:
python
>>> 'ABC'.lower()
'abc'
Simply listing attributes and methods is not enough; combined with getattr()
, setattr()
, and hasattr()
, we can directly manipulate an object's state:
python
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
Next, we can test the object's attributes:
python
>>> 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'?
True
>>> getattr(obj, 'y') # Get attribute 'y'
19
>>> obj.y # Get attribute 'y'
19
If we attempt to access a non-existent attribute, it raises an AttributeError
:
python
>>> getattr(obj, 'z') # Get attribute 'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
We can pass a default
parameter, so if the attribute doesn't exist, it returns the default value:
python
>>> getattr(obj, 'z', 404) # Get attribute 'z', return default 404 if it doesn't exist
404
We can also obtain the methods of the object:
python
>>> hasattr(obj, 'power') # Does it have attribute 'power'?
True
>>> getattr(obj, 'power') # Get attribute 'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # Get attribute 'power' and assign it to variable fn
>>> fn # fn points to obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # Calling fn() is the same as calling obj.power()
81
Summary
Through a series of built-in functions, we can analyze any Python object to access its internal data. It’s important to note that we should only retrieve object information when necessary. If you can write:
python
sum = obj.x + obj.y
You shouldn't write:
python
sum = getattr(obj, 'x') + getattr(obj, 'y')
A proper use case example is:
python
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
Assuming we want to read an image from the file stream fp
, we first check if the fp
object has a read
method. If it does, then the object is a stream; if not, it cannot be read. This is where hasattr()
comes in handy.
Keep in mind that in dynamic languages like Python, the presence of a read()
method does not guarantee that the fp
object is a file stream; it could also be a network stream or an in-memory byte stream. However, as long as the read()
method returns valid image data, it will not affect the image reading functionality.