Last Updated on

什么是自省(introspection)?

自省就是面向对象的语言缩写的程序在运行时,能够知道对象的类型。

例如,Python,Java,C++等都有自省的能力,像Python不单可以知道是什么类型,还能知道有什么属性和方法。

这个也是python彪悍的特性.


在Python中,与自省相关的方法有很多,常见的有help(),dir(),type(),hasattr(),getattr(),setattr(),isinstance(),issubclass(),id(),callable()等,大致作用如下:

help() 打印查看函数或模块用途的详细说明。
type() 返回对象的类型。
dir() 返回对象所有属性。
hasattr() 判断对象是否有特定属性或方法。
getattr() 返回对象的特定属性。
setattr() 设置对象的属性为指定值,若属性没有,则会新增属性并赋值。
isinstance() 判断一个对象是否是一个已知的类型。
issubclass() 判断一个类是不是另一个类的子类。
id() 返回对象的内存地址。
callable() 用于检查一个对象是否是可调用的。

下面将依次详细的说明一下这些方法的使用。

help()

打印查看函数或模块用途的详细说明,用途较少,常用于IDE中编写代码时,查看以下模块或方法的使用说明。

import requests
help(requests.get)

# 输出结果:
Help on function get in module requests.api:

get(url, params=None, **kwargs)
    Sends a GET request.
    
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response

如上,就可以查看到此方法的使用说明,此方法使用的较少,平时可能我们更多会直接点进去查看源码中的说明。

type()

返回对象的类型,此方法不会考虑继承关系。

看示例:

class A(object):
    name = 'amos'

class B(A):
    pass

b = B()
print(type(b))
print(type(B))


# 输出结果:
<class '__main__.B'>
<class 'type'>

如上所示,使用type()可以直接得到示例b的类型,为类B,而不是类A,而类的类型则是默认的元类type。而如果我们在类B中,使用自定义的元类,那么type返回的就会是我们自定义的元类,而不是默认的元类type。如下:

class A(type):
    pass

class B(metaclass=A):
    pass

b = B()
print(type(b))
print(type(B))

# 输出结果:
<class '__main__.B'>
<class '__main__.A'>

关于Python的元类,不了解的请查看我的另一篇博文:

《Python中的元类(metaclass)》

dir()

返回对象所有属性。

dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。

print(dir())
print(dir([1,2,3]))


# 输出结果:
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

可以看到上面输出了list列表的各种方法如append,clear等,还有各种默认的魔法方法。

hasattr()

判断对象是否有特定属性或方法,有则返回True,没有则返回False。

只要是对象能够引用的属性和方法,只要有就会返回True,可以来自于继承或者动态绑定等。以下的getattr,setattr等都是涉及到对象的属性,关于Python的类属性和实例属性,不了解的可以查看我另一篇博文:

《Python 类变量和实例变量》

class A(object):
    name = 'Amos'
    age = 24

    def work(self):
        pass

    @classmethod
    def play(cls):
        pass

class B(A):

    def __init__(self):
        self.sex = 'male'

b = B()
print(hasattr(b, 'sex'))
print(hasattr(b, 'name'))
print(hasattr(B, 'play'))


# 输出结果:
True
True
True

如上所示,不管是否是通过继承,只要对象有的属性和方法,就会返回True。这在程序中判断对象是否能具有某些属性和方法非常好用。

查看hasattr函数的源码解释,如下:

返回对象是否具有给定名称的属性
这是通过调用getattr(obj,name)并捕获AttributeError来完成的.

getattr()

返回对象的特定属性。

与hasatter类似,获取对象的属性或方法,同样是可以来自继承或动态定义。

先开看一下此方法的源码解释:

从对象中获取指定名称的属性;getattr(x,‘y’)等同于X.Y。 如果给定了默认参数,则未找到该属性时将返回该参数。如果未指定,则会引发异常。

使用示例:

class B(object):

    def __init__(self):
        self.sex = 'male'

    def work(self):
        print("I'm working")

b = B()
print(getattr(b, 'sex'))
print(getattr(b, 'work'))
getattr(b, 'work')()
print(getattr(b, 'name', None))
print(getattr(b, 'name'))


# 输出结果:
male
<bound method B.work of <__main__.B object at 0x10b4bee80>>
I'm working
None
AttributeError: 'B' object has no attribute 'name'

如上所示,getattr方法可以直接返回对象的方法或属性,如源码中说明的一样,可以自定义默认返回参数,否则,若找不到属性则报错AttributeError。

setattr()

设置对象的属性为指定值,若指定的属性没有,则会新增属性并赋值。

还是先来看下源码中的说明:

将给定对象的命名属性设置为指定值
setattr(x,‘y’,v)等价于“x.y=v”

看下面示例:

class B(object):

    def __init__(self):
        self.sex = 'male'

    def work(self):
        print("I'm working")

b = B()
setattr(b, 'name', 'Amos')
setattr(B, 'name', 'class_name')
setattr(b, 'sex', 'unkown')
print(b.name)
print(B.name)
print(b.sex)


# 数据结果:
Amos
class_name
unkown

如上所示,setattr设置对象的属性,如果对象是实例,则设置实例属性,对象是类,则设置类属性。

isinstance()

判断一个对象是否是一个已知的类型,是则返回True,否则返回False。

此方法类似于type(),但是此方法考虑继承关系,type()不考虑继承关系。

先来看下其源码中的说明:

返回对象是一个类的实例还是其子类的实例。
可以将isinstance(x,(A,B,…))中的元组作为目标检查。 这等效于isinstance(x,A)或isinstance(x,B) 或...

看下面示例:

class A(type):
    pass

class B(metaclass=A):
    pass

class C(B):
    pass

c = C()
print(isinstance(c, C))
print(isinstance(c, B))
print(isinstance(C, type))


# 输出结果:
True
True
True

如上所示,isinstance()方法会考虑继承关系,只要对象属于此类的实例或者此类子类的实例,都会返回True,即使是自定义的元类。由于自定义的元类继承自type元类,所以也是会返回True,所以所有类通过isinstance(classs_name, type)都会返回True。

当然,除此之外,isinstance方法还可以用于判断Python中所有的数据类型,即number,string,tuple,list,set,dict。

因为在Python中,这些所有的数据类型都是类,比如定义一个string字符串,就相当于用str类创建了一个实例。

print(isinstance(123, int))
print(isinstance(12.3, float))
print(isinstance(False, bool))
print(isinstance(1+2j, complex))
print(isinstance('da', str))
print(isinstance([], list))
print(isinstance((), tuple))
print(isinstance({}, dict))
print(isinstance(set(), set))


# 输出结果都为True:
True
True
True
True
True
True
True
True
True

issubclass()

判断一个类是不是另一个类的子类,是则返回True,否则返回False。

先来看以下源码中都说明:

返回“ cls”是从另一个类派生还是相同的类。
像issubclass(x,(A,B,…))中的元组可以作为目标检查。 这等效于issubclass(x,A)或issubclass(x,B 或...

看下面示例:

class A(object):
    pass

class B(A):
    pass

class C(B):
    pass

class D(object):
    pass

print(issubclass(B, A))
print(issubclass(C, A))
print(issubclass(C, (D, A)))


# 输出结果:
True
True
True

如上所示,此方法用法也是比较简单。跟isinstance()类似,第二个参数都可以跟元组。

id()

返回对象的内存地址

id()方法比较简单,就是返回对象的内存地址,通过内存地址可以判断变量是否相同。

看下面示例:

a = 1
b = 1
print(id(a))
print(id(b))
print(a is b)
print(a == b)

c = []
d = []
print(id(c))
print(id(d))
print(c is d)
print(c == d)


# 输出结果:
4437579712
4437579712
True
True

4441502088
4441501960
False
True

如上所示,你会发现有趣的一点,对于不可变对象,相同值的内存地址是一样的,通过is方法比较内存地址会发挥True。对于可变对象,即使值是相同的,但是实际上的内存地址是不同的,所以is方法判断返回False,但是通过==来判断值则是相同的,所以返回True。

callable()

用于检查一个对象是否是可调用的。如果返回 True,object 仍然可能调用失败;但如果返回 False,调用对象 object 绝对不会成功。

这个方法平时可能不是很常见,我们先来看下源码的说明:

返回对象是否可调用(比如某种函数)。
请注意,类是可调用的,带有__call__()方法。

看下面示例:

def a():
    pass

class B(object):
    pass

class C(object):
    def __call__(self, *args, **kwargs):
        pass

print(callable(a))
print(callable(B))
print(callable(B()))
print(callable(C()))


# 输出结果:
True
True
False
True

如上所示,类是可调用的,用于生成实例,而如果定义了实例的__call__方法,则实例也是可调用的,也会返回True。 而字符串,数字等对象不可调用,同样是因为str,int这些类中没有定义__call__方法。