文章目录
1. 从dir()函数提及
对于dir()这个Python的内置函数,Python进阶群里的小伙伴们必定不陌生。我不止一次地介绍过这个函数。每当想要了解一个类或类实例包含了什么属性和方法时,我都会求助于这个函数。python
>>> a = [3,4,5] >>> type(a) # 返回a的类型,结果是list类 <class 'list'> >>> dir(a) # 返回list类实例对象a包含的属性和方法 ['__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'] >>> dir(list) # 返回list类a包含的属性和方法 ['__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']
对于模块、内置函数,以及自定义的类,dir()一视同仁,照样可用。程序员
>>> import math >>> dir(math) # 返回math模块包含的子项(子模块、类、函数、常量等) ['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc'] >>> dir(max) # 返回内置函数的内建子项 ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
读到这里,必定会有不少小伙伴会说,个人PyCharm(也多是VSCode或者其余什么)也会告诉我,当前的对象有什么属性和方法,仍是自动显示的,不须要我动手。没错,IDE的确为咱们提供了不少便利,可是,你有没有想过IDE是如何实现这些功能的呢?假如你的任务就是设计一款相似的IDE,你真的不要深刻理解Python内在的机制吗?shell
2. 内建属性和方法
下面的代码中,类Player定义了两个属性和一个方法,p是Player的一个实例。调用dir()显示实例p的属性和方法,就会发现,除了代码中定义name,rating和say_hello()外,其余都是以双下划线开头、以双下划线结尾,这些就是传说中的Python对象的内建属性和方法。编程
>>> class Player: """玩家类""" def __init__(self, name, rating=1800): self.name = name self.rating = rating def say_hello(self): """自报姓名和等级分""" print('你们好!我是棋手%s,目前等级分%d分。'%(self.name, self.rating)) >>> p = Player('天元浪子') >>>> p.say_hello() 你们好!我是棋手天元浪子,目前等级分1800分。 >>> for item in dir(p): print(item) __class__ __delattr__ __dict__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __gt__ __hash__ __init__ __init_subclass__ __le__ __lt__ __module__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ name rating say_hello
这些内建属性和方法中,彷佛只有__init__和__new__看起来有点面熟,其余那些都有什么用途呢?下面,我选其中的几个演示一下。app
2.1 _ doc _
__doc__是最经常使用的内建属性,有不少小伙伴并无意识到这一点。一个规范的代码文件,除了代码自己,还会提供不少必要信息,好比类、函数的说明,这些说明,咱们称其为文档字符串(DocString)。__doc__就是对象的文档字符串。ssh
>>> Player.__doc__ '玩家类' >>> p.__doc__ '玩家类' >>> p.say_hello.__doc__ '自报姓名和等级分'
这里显示的文档字符串,就是我在定义Player时写在特定位置的注释(没有注意到这一点的小伙伴,请返回查看前面的Player类定义代码)。编程语言
2.2 _ module _
很容易猜到,内建属性__mudule__表示对象所属的模块。这里,Player类及其实例,都是当前__main__模块。如咱们引入一个模块,更容易说明__module__的含义。函数
>>> Player.__module__ '__main__' >>> p.__module__ '__main__' >>> p.say_hello.__module__ '__main__' >>> import math >>> math.sin.__module__ 'math'
2.3 _ dict _
内建属性__dict__,是一个由对象的属性键值对构成的字典。类的__dict__和类实例的__dict__有不一样的表现。学习
>>> p.__dict__ {'name': '天元浪子', 'rating': 1800} >>> Player.__dict__ mappingproxy({'__module__': '__main__', '__doc__': '玩家类', '__init__': <function Player.__init__ at 0x000002578CF399D8>, 'say_hello': <function Player.say_hello at 0x000002578CF39A68>, '__dict__': <attribute '__dict__' of 'Player' objects>, '__weakref__': <attribute '__weakref__' of 'Player' objects>})
2.4 _ class _
经过类的实例化,能够获得一个类实例。那么如何从一个类实例,逆向获得类呢?实际上,类实例的内建属性__class__就是类。咱们彻底能够用一个实例的__class__去初始化另外一个实例。编码
>>> pp = p.__class__('零下八段', 2100) >>> pp.say_hello() 你们好!我是棋手零下八段,目前等级分2100分。
2.5 _ dir _
dir()函数是Python的内置函数,内建方法__dir__相似于dir()函数。。
>>> p.__dir__() ['name', 'rating', '__module__', '__doc__', '__init__', 'say_hello', '__dict__', '__weakref__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
2.6 _ getattribute _
顾名思义,__getattribute__返回对象的属性——其实是属性或方法。这是一个内建方法,使用的时候其后必须有圆括号,参数是指定的属性或方法的名字。
>>> p.__getattribute__('name') '天元浪子' >>> p.__getattribute__('rating') 1800 >>> p.__getattribute__('say_hello') <bound method Player.say_hello of <__main__.Player object at 0x000002578CF2CA88>> >>> p.__getattribute__('say_hello')() 你们好!我是棋手天元浪子,目前等级分1800分。
3. 动态加载及调用
学习任何一门编程语言的初级阶段,咱们几乎都会遇到一个共同的问题:动态建立一个变量或对象。在这里,“动态”只是强调变量或对象名称不是由程序员决定,而是由另外的参与方(好比交互程序中的操做者,C/S或B/S程序中的客户端)决定。也许不是一个准确的说法,但我想不出一个更好的词汇来表述此种应用需求。
以Python为例:从键盘上读入一个字符串,以该字符串为名建立一个整型对象,令其值等于3。一般,这样的问题咱们使用exec()函数就能够解决。为何不是eval()函数呢?eval()函数仅是计算一个字符串形式的表达式,没法完成赋值操做。
>>> var_name = input('请输入整型对象名:') 请输入整型对象名:x >>> exec('%s=3'%var_name) >>> x 3
理解了“动态”的概念,咱们来看看如何动态加载模块、如何动态调用对象等
3.1 动态加载模块
按照Python编码规范,脚本文件通常会在编码格式声明和文档说明以后统一导入模块。有些状况下,代码须要根据程序运行时的具体状况,临时导入相应的模块——一般,这种状况下,导入的模块命是由一个字符串指定的。下面的代码给出了动态加载模块的实例。
>>> os.getcwd() # 此时没有导入os模块,因此抛出异常 Traceback (most recent call last): File "<pyshell#158>", line 1, in <module> os.getcwd() NameError: name 'os' is not defined >>> os = __import__('os') # 动态导入'os'模块 >>> os.getcwd() 'C:\\Users\\xufive\\AppData\\Local\\Programs\\Python\\Python37'
3.2 经过对象名取得对象
这个需求听起来有点奇怪,但也有不少人会遇到。Player类实例p为例,若是咱们只有字符串’p’,怎样才能获得p实例呢?咱们知道内置函数globals()返回全局的对象字典,locals()返回所处层次的对象字典,这两个字典的键就是对象名的字符串。有了这个思路,就很容易经过对象名取得对象了。
>>> obj = globals().get('p', None) >>> obj <__main__.Player object at 0x000002578CF2CA88> >>> obj.say_hello() 你们好!我是棋手天元浪子,目前等级分1800分。
3.3 动态调用对象
动态调用对象最典型的应用是服务接口的实现。假如客户端经过发送服务的名字字符串来调用服务端的一个服务,名字字符串和服务有者一一对应的关系。若是没有动态调用,代码恐怕就得写成下面这个样子。
if cmd == 'service_1': serv.service_1() elif cmd == 'service_2': serv.service_2() elif cmd == 'service_3': serv.service_3() ... ...
下面的代码,演示了服务端如何根据接收到的命令动态调用对应的服务。
>>> class ServiceDemo: def service_1(self): print('Run service_1...') def service_2(self): print('Run service_2...') def onconnect(self, cmd): if hasattr(self, cmd): getattr(self, cmd)() else: print('命令错误') >>> serv = ServiceDemo() >>> serv.onconnect('service_1') Run service_1... >>> serv.onconnect('service_2') Run service_2... >>> serv.onconnect('hello') 命令错误
4. 自省和反射机制
是时候说说自省和反射了。可是,截止到这里,我已经把自省和反射所有讲完了,只是没有使用自省和反射这两个词罢了。仅从这一点,就能够说明,自省和反射是彻底多余的概念。若是有小伙伴搞不清楚这两个概念,那也彻底没有关系,一点儿都不会影响你对编程的理解。
所谓的自省,就是对象自身提供能够查看自身属性、方法、类型的手段。内建方法__dir__不正是对象的自省吗?另外,内置函数dir()、type()、isinstance()均可以提供相似自省的部分或所有功能。
反射机制是Java和PHP等语言提供的一个特性,准确描述起来有些费劲,简而言之,就是在运行态能够获取对象的属性和方法,并随时调用他们,最典型的应用就是经过字符串形式的对象名获取对象。这不就是我说的“动态加载和调用”吗?
写道这里,不禁地再次致敬龟叔当年的远见卓识:早在Java诞生前好多年,龟叔就已经全面地规划了Python对象的内建机制,其前瞻性远远超过了自省和反射机制。