文章目錄
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對象的內建機制,其前瞻性遠遠超過了自省和反射機制。