前言
先扯一點背景知識
PEP8(Python Enhancement Proposal)是一份python的編碼規范,鏈接:http://www.python.org/dev/peps/pep-0008/
在這份編碼規范中的“命名規范-命名風格”這一節的最后,提到了對幾種使用前置和后置下划線的,對變量的比較特殊的命名方式:
- 單下划線開頭:弱內部使用標識,無法被from M import *所引用
- 單下划線結尾:避免和python關鍵字沖突,可以加個后置下划線
- 雙下划線開頭:類成員變量中的私有變量,
- 雙下划線開頭,雙下划線結尾:這是magic對象或屬性的名字,永遠不要將這樣的命名方式應用於自己的變量和函數
本文主要關注正是以上第四種--python自動在用戶命名空間創建的magic變量
1、__name__變量
__name__屬性是直接內置在.py文件中的。
- 如果直接執行.py文件,__name__將被設置為__main__。
- 如果.py文件是被import,__name__將被設置為.py文件的名字
這個屬性經常用來當做一個使用模式的標識:
#a.py print 'a function' if __name__=='__main__': print 'a test' ------------------------------ #b.py import a
如果執行python a.py將打印出兩行內容,執行python b.py只會打印出'a function'。一般可以把只針對a.py的測試代碼寫在if __name__=='__main__',因為如果a.py被其他的腳本import之后,這部分代碼將不會被執行。可以很安全的對a.py進行單獨的測試。
2、__file__變量
__file__可以用來獲取python腳本的“路徑+腳本名稱”,這可能是一個相對路徑也可能是一個絕對路徑,取決按照什么路徑來執行的腳本,一般來說__file__變量和os.path配合,可以用來獲取python腳本的絕對路徑:
#a.py import os print os.path.realpath(__file__) out>>E:\Eclipse_workspace\python_learn\a.py
3、__import__函數
python導入模塊時,一般使用import,而import語句其實也是調用builtin函數:__import__()實現的導入,直接使用__import__比較少見,除非導入的模塊是不確定的,需要在運行時才能確定導入哪些模塊,可以使用__import__,默認接收需要導入的模塊名的字符串:
#a.py def f1(): print 'f1' def f2(): print 'f2' #b.py model=__import__('a') model.f1() model.f2()
在memfs的測試中,我的每一個測試case就是一個獨立的.py文件,在確定需要測試哪些case后,在運行時才‘動態的’去import相應的case,就是通過__import__來實現的。
4、__str__函數
__str__是一個比較常用的內置函數,在定義類的時候經常使用,__str__函數返回一個字符串,這個字符串就是此對象被print時顯示的內容,(如果不定義這個函數,將會顯示默認的格式:<__main__.A object at 0x0000000001FB7C50>):
#a.py import datetime import os class A(object): def __str__(self): #返回當前的日期 return str(datetime.datetime.now()) a=A() print a time.sleep(1) #每次打印A()的對象,都返回當前的時間 print a out>>2015-06-25 15:01:01.573000 out>>2015-06-25 15:01:02.573000
這個函數在django的model類中如果定義的話,print一條數據庫中的數據,可以指定顯示任何的值:
class Question(models.Model): #定義一個數據庫表,其中包含question_id和question_text #.... def __str__(self): #只想顯示question_text return self.question_text
注:在python3.x中str被廢棄,使用unicode
5、__init__對象函數
__init__比較常見,是對象的初始化函數,例子如下:
#a.py class A(object): pass class B(A): #B類繼承自A,如果要重寫__init__,需要先調用父類的__init__ def __init__(self,*args): super(B,self).__init__(*args)
6、__new__對象函數
__new__()函數是類創建對象時調用的內置函數,必須返回一個生成的對象,__new__()函數在__init__()函數之前執行。一般來說沒有比較重載這個函數,除非需要更改new對象的流程,有一種場景“單例模式”要求只能存在一個class A的對象,如果重復創建,那么返回的已經創建過的對象的引用。可以這樣使用__new__函數:
a.py
class A(object): def __new__(cls): if not "_instance" in vars(cls): cls._instance=super(A,cls).__new__(cls) return cls._instance a=A() b=A() print id(a)==id(b) out>>True
可以看出,a和b其實引用了同一個對象
7、__class__對象變量
instance.__class__表示這個對象的類對象,我們知道在python中,類也是一個對象(好理解么),例:
#a.py class A(object): pass a=A() B=a.__class__ b=B() print type(b) out>><class '__main__.A'>
可以看出,a是A類的一個對象,a.__class__就是A類,將這個類賦值給B,使用B()又可以創建出一個對象b,這個對象b也是A類的對象,(暈了么?),這個__class__有什么卵用呢?下面的例子就可以用到
8、__add__對象函數
這其實是一類函數,包括__sub__,__mul__,__mod__,__pow__,__xor__,這些函數都是對加、減、乘、除、乘方、異或、等運算的重載,是我們自定義的對象可以具備運算功能:
#a.py class A(object): def __init__(self,v): self.v=v def __add__(self,other): #創建創建一個新的對象 x=self.__class__(self.v+2*other.v) return x a=A(1) b=A(2) c=a+b print c.v ouot>>5
這樣我們就定義了一個加法操作1+2=1+2*2=5
9、__doc__文檔字符串
python建議在定義一個類、模塊、函數的時候定義一段說明文字,例子如下:
#c.py
""" script c's doc """ class A(object): """ class A's doc """ pass def B(): """ function B's doc """ pass print __doc__ print A.__doc__ print B.__doc__ out>>script c's doc out>>class A's doc out>>function B's doc
調用別的模塊、函數的時候如果不清楚使用方法,也可以直接查看doc文檔字符串
10、__iter__和next函數
凡是可以被for....in的循環調用的對象,我們稱之為可以被迭代的對象,list,str,tuple都可以被迭代,它們都實現了內部的迭代器函數,比如說list,tuple,字符串這些數據結構的迭代器如下:
a=[1,2,3,4] b=('i',1,[1,2,3]) print a.__iter__() print b.__iter__() out>><listiterator object at 0x0000000001CC7C50> out>><tupleiterator object at 0x0000000001CC7B00>
如果我們要實現一個我們自己的迭代器對象,那么我們必須實現兩個默認的方法:__iter__和next。
__iter__()函數將會返回一個迭代器對象,next()函數每次被調用都返回一個值,如果迭代完畢,則raise一個StopIteration的錯誤,用來終止迭代。下面的例子將實現一個可以迭代的對象,輸出a~z的26個字母,該對象接收一個int參數用來表示輸出字母的數量,如果該參數超過字母表的長度,則循環從‘a-z’再次進行循環輸出:
import random
class A(object):
def __init__(self,n):
self.stop=n
self.value=0 #字母列表 self.alph=[chr(i) for i in range(97,123)] def __iter__(self): return self def next(self): #如果超過長度超過26則重置 if self.value==len(self.alph): self.value=0 self.stop=self.stop-len(self.alph) #最終,已完成n個字符的輸出,則結束迭代 if self.value>self.stop: raise StopIteration x=self.alph[self.value] self.value+=1 return x for i in A(1000): print i, out>>a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k .....
11、__dict__、__slot__和__all__
這三個變量有一些關系,__dict__在類和對象中都存在,它是一個包含變量名和變量的字典,見以下的例子:
#a.py class A(object): c=3 d=4 def __init__(self): self.a=1 self.b=2 def func(self): pass print A().__dict__ print A.__dict__ out>>{'a': 1, 'b': 2} out>>{'__module__': '__main__', 'd': 4, 'c': 3, 'func': <function func at 0x00000000021F2BA8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, '__init__': <function __init__ at 0x00000000021F2AC8>}
一個對象的__dict__只包含self定義的變量,而一個類的__dict__包含了類里面的函數(func函數)、類變量,以及很多隱性的變量,包括__dict__變量本身也是隱性的。
__slot__變量的用法理解起來比較要難一點,正常的情況下,我們實例化一個對象,可以給這個對象增加任意的成員變量,即使不在類里面定義的變量都可以,如下:
#a.py class A(object): def __init__(self): self.a=1 self.b=2 a=A() #給a增加一個x變量 a.x=1 #也可以給a增加一個匿名函數 a.y=lambda x,y:x*y print a.x print a.y(3,5) out>>1 out>>15
如果我們想限制一下對象綁定的變量,我們可以在類定義的時候增加一個slots變量,這個變量是一個字符串的元組,例子如下:
class A(object): __slots__=('a','b','x') def __init__(self): self.a=1 self.b=2 pass #__slots__=('a','b',) def func(self): pass a=A() a.x=1 #執行到a.y時會報錯:AttributeError: 'A' object has no attribute 'y' a.y=lambda x,y:x*y print a.y(3,5)
__all__變量是一個字符串列表,它定義了每一個模塊會被from module_name import *這樣的語句可以被import的內容(變量,類,函數)
#a.py 不定義__all__ class A(object): def __init__(self): self.a=1 self.b=2 def func(self): pass def B(): pass c=10 #b.py from a import * print A print B print c out>><class 'learn_draft.A'> out>><function B at 0x00000000021D1438> out>>10
如果在a.py中定義__all__=['A','c'],則B函數對於b.py來說是不可見的。
12、__hash__
哈希函數,在python中的對象有一個hashable(可哈希)的概念,對於數字、字符串、元組來說,是不可變的,也就是可哈希的,因此這些對象也可以作為字典的key值。另外,列表、字典等,是可變對象,因此也就是不可哈希的,也就不能作為字典的key值。是否可哈希,可以調用內置函數hash()進行計算,hash()函數返回計算的到的hash值。
- 完全相同的變量,調用哈希算法的到的hash值一定是相同的
當然一般來說,我們不會去重新定義一個對象的__hash__函數,除非我們想實現一個自定義的需求,在stackoverflow有人提出這樣一個需求,需要判斷有相同詞頻的字符串是相等的,也就是說“abb”和“bab”這樣的字符串是相等的,這個時候我們可以繼承字符串類,然后重寫哈希函數,如下:
import collections class FrequencyString(str): @property def normalized(self): try: return self._normalized except AttributeError: self._normalized = normalized = ''.join(sorted(collections.Counter(self).elements())) return normalized def __eq__(self, other): return self.normalized == other.normalized def __hash__(self): return hash(self.normalized)
13、__getattr__和__setattr__,__delattr__對象函數
先介紹兩個內置函數,getattr()和setattr(),使用這兩個函數可以獲取對象的屬性,或者給對象的屬性賦值:
#a.py class A(object): def __init__(self): self.a=1 self.b=2 a=A() setattr(a,'a',3) print a.a print getattr(a,'b') out>>3 out>>2
其實使用這兩個函數和直接訪問a.a,a.b沒有任何區別,但好處是setattr和getattr接受兩個字符串去確定訪問對象a的哪一個屬性,和__import__一樣,可以在運行時在決定去訪問對象變量的名字,在實際工作中經常會使用這兩個函數。
__getattr__()這個函數是在訪問對象不存在的成員變量是才會訪問的,見下面的例子:
class A(object): def __init__(self): self.a=1 self.b=2 def func(self): pass def __getattr__(self,name): print 'getattr' return self.a a=A() print a.d out>>getattr out>>1
在調用a.d時,d不是a的成員變量,則python會去查找對象是否存在__getattr__()函數,如果存在,則返回__getattr__()函數的返回值,我們這里返回的是self.a的值1。
由於__getattr__()的特性,我們可以將__getattr__()設計成一個公共的接口函數,在autotest的proxy.py中就看到了這樣的用法:
class ServiceProxy(object): def __init__(self, serviceURL, serviceName=None, headers=None): self.__serviceURL = serviceURL self.__serviceName = serviceName self.__headers = headers or {} def __getattr__(self, name): if self.__serviceName is not None: name = "%s.%s" % (self.__serviceName, name) return ServiceProxy(self.__serviceURL, name, self.__headers) #調用的時候,op是執行的特定操作的字符串,op傳入__getattr__將會把ServiceProxy對象重新的內部變量重新賦值,然后返回一個更新之后的對象 function = getattr(self.proxy, op)
__setattr__和__getattr__不一樣,對象的所有屬性賦值,都會經過__setattr__()函數,看下面的例子:
class A(object): def __init__(self): self.a=1 self.b=2 def func(self): pass def __getattr__(self,name): print 'getattr' return self.a def __setattr__(self, name, value): print 'setattr %s' % name if name == 'f': return object.__setattr__(self,name,value+1000) else: return object.__setattr__(self, name, value) a=A() a.f=1000 print a.f out>>setattr a out>>setattr b out>>setattr f out>>2000
從輸出可以看到init函數的self.a和self.b的賦值也經過了__setattr__,而且在賦值的時候我們自定義了一個if邏輯,如果name是‘f’,那么value會增加1000,最終的a.f是2000
__delattr__不舉例了,刪除一個對象屬性用的。
14、__call__對象函數
如果一個對象實現了__call__()函數,那么這個對象可以認為是一個函數對象,可以使用加括號的方法來調用,見下面例子:
class A(object): def __init__(self): self.li=['a','b','c','d'] def func(self): pass def __call__(self,n): #返回li列表的第n個元素 return self.li[n] a=A() #a可以當做函數一樣調用 print a(0),a(1),a(2) out>>a b c
在實際工作中__call__函數非常有用,可以把一個對象變成callable的對象