從 Python到Tensorflow 學習之路(四)
最近畢業設計題目是研究對抗樣本,要用tensorflow來搭建神經網絡,因此python必不可少,這個不是一個傳統的Python學習教程只是把學習Python過程中遇到的問題和經驗記錄下來(基於Python3.5),如果想要一步一步學習Python建議看下面的網站。
Python學習教程
模塊
- 在Python中,一個.py文件就稱之爲一個模塊(module),其大大提高了代碼的可維護性。
- 使用模塊可以避免函數名和變量名衝突,相同名字的函數和變量完全可以分別存在於不同的模塊中。
- 爲了避免模塊名衝突,Python引入了按照目錄來組織模塊的方法,稱爲包(package)。
假設我們有兩個模塊abc和xyz,與其他模塊衝突,我們可以使用包來組織模塊,方法是選擇一個頂層包名,比如mycompany
按照如下目錄存放
那麼abc.py模塊名就變成了mycompany.abc,類似的xyz.py模塊名就變成了mycompany.xyz
注意每一個包目錄下都會有一個__init__.py
,這個文件是必須存在的。否則Python就把這個目錄當成普通目錄,而不是一個包。__init__.py
可以是空文件,也可以有Python代碼。因爲__init__.py
本身就是一個模塊,它的模塊名就是mycompany
使用模塊
"""a test module"""
__author__ = 'LWP'
import sys
def test():
args = sys.argv
if len(args)==1:
print('%s!' %args[0])
elif len(args)==2:
print('Hello, %s!' %args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
- 使用
sys
模塊第一步就是導入該模塊:import sys
導入後,就有了變量sys
指向該模塊,利用sys
這個變量,就可以訪問sys
模塊的所有功能。sys
模塊有一個argv
變量,用list存儲了命令行的所有參數。argv
至少有一個參數,因爲第一個參數永遠是該.py
文件的名稱,當我們運行Python3 blog_test.py
獲得的sys.argv
就是[/home/lwp/PycharmProjects/blog_test/blog_test.py!]
- 注意這個代碼:
if __name__ = '__main__':
test()
- 當我們在命令行運行
blog_test
模塊時,Python解釋器會把一個特殊變量__name__
置爲__main__
,而如果在其他地方導入該blog_test
模塊時,if
判斷將失敗,因此這種if
測試可以讓一個模塊通過命令行運行時執行一些額外的代碼,最常見的就是運行測試。- Python解釋器在讀取一個源文件時,會執行從中找到的所有代碼。在執行所有代碼前,它會定義一些特殊的變量。例如,當Python解釋器在運行源文件模塊並把它當做主程序,它會將
__name__
設置爲'__main__'
。如果這個源文件被其他模塊導入那麼__name__
設置爲模塊本身的名字。因此你可以將一些不希望被導入的人運行的程序放進檢查中,其他人便不會運行
作用域
在一個模塊中,我們可能會定義很多的函數和變量,但有的函數和變量我們希望給別人使用,有的函數我們希望僅僅在模塊內部使用。在Python中,是通過前綴_
實現的
- 正常的函數和變量名是公開的(public),可以被直接引用,比如
abc, x123
- 類似與
__xx__
這樣的前後雙下劃線變量是特殊變量,可以直接被引用,但是有特殊用途,比如上面的__author__, __name__
- 類似於
_xx, __xx
這樣的前單下劃線或單雙下劃線函數和變量”不應該“被直接引用,而不是”不能“被直接引用,因爲Python並沒有一種方法可以完全限制訪問private函數或者變量。
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
import blog_test
print(blog_test.greeting('world'))
# output: Hello, world
面向對象編程
一個簡單的處理學生成績的類
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
alice = Student('Alice', 99)
bob = Student('Bob', 85)
alice.print_score()
bob.print_score()
# output:Alice: 99
# Bob:85
類和實例
- class後面緊接着是類名,即Student,類名通常是大寫開頭的單詞,緊接着是
(Object)
,表示該類是從哪個類繼承下來的。- 由於類可以起到模板的作用,因此可以在創建實例的時候,把一些我們認爲必須綁定的屬性強制填寫進去。通過定義一個特殊的
__init__
方法,在創建實例的時候,就把name, score
等屬性綁定上去。注意__init__
前後都是雙下劃線。__init__
方法的第一個參數永遠是self
,表示創建的實例本身,因此,在__init__
方法內部,就可以把各種屬性綁定到self
,因爲self
就指向創建的實例本身。- 有了
__init__
方法,在創建實例時,不能夠傳入空的參數,必須傳入與__init__
方法相匹配的參數,但是self
不用傳,Python解釋器自己會把實例變量傳進去。
訪問限制
- 從前面Student定義來看,外部的代碼仍然可以自由地修改一個實例的
name、score
屬性
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
alice = Student('Alice', 99)
bob = Student('Bob', 85)
print(alice.score)
# output:99
- 如果要讓內部屬性不被外部訪問,可以把屬性前面的名稱加上兩個下劃線
__
,在Python中,實例變量名如果以__
開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
alice = Student('Alice', 99)
bob = Student('Bob', 85)
# print(alice.__score)
'''Traceback (most recent call last):
File "/home/lwp/PycharmProjects/blog_test/blog_test.py", line 17, in <module>
print(alice.score)
AttributeError: 'Student' object has no attribute '__score'''
alice.print_score()
# output:Alice: 99
- 如果外部代碼需要獲取name和score怎麼辦,可以增加變量的get方法;如果要允許外部代碼修改score怎麼辦,可以增加變量的set方法。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
def set_name(self, name):
self.__name = name
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('Bad Score!')
def get_name(self):
return self.__name
def get_score(self):
return self.__score
alice = Student('Alice', 99)
print(alice.get_name())
# output:Alice
print(alice.get_score())
# output:99
alice.set_name('ali')
print(alice.get_name())
# output: ali
alice.set_score(60)
print(alice.get_score())
# output: 60
#alice.set_score(111)
- 在設置參數方法中我們可以對參數進行類型檢查,避免傳入無效參數。
- 在Python中,變量名類似於
__xxx__
的,即雙下劃線開頭,雙下劃線結尾的是特殊變量,特殊變量可以直接訪問,不是private變量,所以不能起__name__, __score__
。- 以一個下劃線開頭的變量,比如
_name
,這樣的實例變量外部是可以訪問的,然而其約定“雖然我可以被訪問,但是請把我視爲私有變量,不要隨意訪問”- 雙下劃線開頭的實例變量也並不是完全不能被訪問。不能直接訪問
__name
是因爲Python解釋器對外把__name
變量改成了_Student__name
,我們仍然可以通過_Student__name
來訪問__name
變量。但是儘量不要這麼幹!不同版本的Python解釋器可能會把__name
改成不同的變量名
...
print(alice._Student__name)
# output:Alice
- 注意一種錯誤寫法,這個
__name
並不是真的是class內部的__name
,內部的__name
變量已經被Python解釋器自動改成了_Student__name
,而外部代碼給alice
新增加了一個__name
變量
alice = Student('Alice', 99)
print(alice.get_name())
# output:Alice
alice.__name = 'Bob'
print(alice.__name)
# output:Bob
print(alice.get_name())
# output:Alice
繼承和多態
class Animal(object):
def run(self):
print('Animal is running......')
class Dog(Animal):
def run(self):
print('Dog is running......')
class Cat(Animal):
def run(self):
print('Cat is running......')
a = Animal()
a.run()
# output:Animal is running.....
c = Cat()
c.run()
# output:Cat is running......
d = Dog()
d.run()
# output:Dog is running.....
- 在繼承關係中,如果一個實例的數據類型是某個子類,那麼它的數據類型也可以被看作是父類。但是,反過來就不行
- 對於一個變量,我們只需要知道它是
Animal
類型,無需確切地知道它的子類型,就可以放心地調用run()
方法,具體調用的run()
方法是作用在哪個對象時,由運行時該對象的確切類型決定,這就是多態。- 調用方只管調用,不管細節,當我們新增一種
Animal
的子類時,只要確保run()
方法編寫正確,不用管原來的代碼是如何調用的,這即是著名的開閉原則
- 對擴展開放:允許新增
Animal
子類- 對修改封閉:不需要修改依賴
Animal
類型的run_twice()
函數
......
def run_twice(animal):
animal.run()
animal.run()
a = Animal()
run_twice(a)
# output:Animal is running......
# output:Animal is running......
c = Cat()
run_twice(c)
# output:Cat is running......
# output:Cat is running......
- 靜態語言vs動態語言:
- 靜態語言(比如Java),如果需要傳入
Animal
類型,則傳入的對象必須是Animal
類型或者它的子類,否則將無法調用run()
方法。- 對於Python這種動態語言,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個
run()
方法即可。這就是動態語言的“鴨子類型。”它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。
......
class Timer(object):
def run(self):
print('Start.....')
......
run_twice(Timer())
# output:Start.....
# output:Start.....
獲取對象信息
如何在拿到一個對象的引用時,知道這個對象是什麼類型、有哪些方法?
- 可以判斷對象的基本類型
- 如果一個變量指向函數或者類,也可以用type()判斷
print(type(123))
# output:<class 'int'>
print(type('str'))
# output:<class 'str'>
print(type(None))
# output:<class 'NoneType'>
print(type(abs))
# output:<class 'builtin_function_or_method'>
- 判斷一個對象是否是函數
import types
def fn():
pass
print(type(fn) == types.BuiltinFunctionType)
print(type(abs) == types.BuiltinFunctionType)
isinstance()
可以判斷一個對象是否是該類型本身,或者位於該類型的父繼承鏈上。- 判斷一個變量是否是某些類型的一種
print(isinstance([1, 2, 3], (list, tuple)))
# output: True
print(isinstance({'name': 'Peter', 'age': 20}, (list, tuple)))
# output: False
- 使用
dir()
可以獲得一個對象的所有屬性和方法,它返回一個包含字符串的list
print(dir('ABC'))
# output: ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
- 裏面有
__xx__
的特殊方法以及upper()
等普通屬性或方法- 使用
getattr()、setattr()、hasattr()
直接操作一個對象狀態, 如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤。可以傳入一個default參數,如果屬性不存在,就返回默認值
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
print(hasattr(obj, 'power'))
# output: True
print(getattr(obj, 'x'))
# output: 9實例屬性和類屬性使用相同的名字,因爲相同名稱的實例屬性將屏蔽掉類屬性,但是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性。
print(getattr(obj, 'y', 44))
# output: 44
實例屬性和類屬性
由於Python是動態語言,根據類創建的實例可以任意綁定屬性。
- 給實例綁定屬性的方法是通過實例變量,或者通過
self
變量
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
- 可以直接在class中定義屬性,這個屬性是類屬性,歸
Student
類所有
class Student(object):
name = 'Student'
s = Student()
print(s.name)
# output: Student(因爲實例沒有name屬性,所以會查找class的name屬性)
print(Student.name)
# output: Student(打印類的name屬性)
s.name = 'Bob'
print(s.name)
# output: Bob(實例屬性的優先級高於類的屬性)
print(Student.name)
# output: Student
del s.name
print(s.name)
# output: Student
- 千萬不要對實例屬性和類屬性使用相同的名字,因爲相同名稱的實例屬性將屏蔽掉類屬性,但是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性
使用slots限制定義的屬性僅僅對當前類實例起作用
- 之前我們可以給實例綁定一個屬性,其實還可以綁定一個方法。但是給一個實例綁定的方法,對另一個實例是不起作用的。爲了給所有實例都綁定方法,可以給class綁定方法
class Student(object):
pass
s = Student()
def set_age(self, age):
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s)
s.set_age(25)
print(s.age)
# output: 25
class Student(object):
pass
s = Student()
def set_age(self, age):
self.age = age
Student.set_age = set_age
s.set_age(11)
print(s.age)
# output: 11
s2 = Student()
s2.set_age(22)
print(s2.age)
# output: 22
- 如果我們想限制實例的屬性怎麼辦,我們應該在定義class的時候,定義一個特殊的變量
__slot__
變量,來限制該class實例能添加的屬性
class Student(object):
__slots__ = {'name', 'age'}
s = Student()
s.age = 55
s.name = 'Bob'
print(s.age, s.name)
# output: 55 Bob
s.score = 100
# AttributeError: 'Student' object has no attribute 'score'
- 使用score, 其定義的屬性僅僅對當前類實例起作用,對繼承的子類不起作用
使用@property既可以檢查參數又可以用類似於屬性這樣簡單的方式來訪問類變量
- 把一個getter方法變成屬性,只需要加上
@property
即可,此時@property
本身又創建了另外一個裝飾器@score.setter
,負責把一個setter方法變成屬性賦值
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0~100!')
self._score = value
s = Student()
s.score = 60
print(s.score)
# output: 60
s.score = 9999
# ValueError: score must between 0~100!
- 還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2017-self._birth
s = Student()
s.birth = 1996
print(s.age)
# output : 21
s.age = 30
# AttributeError: can't set attribute
Python中多重繼承
- 在設計類的繼承關係時,通常主線都是單一繼承下來的,如果需要混入額外的功能,通常是通過多重繼承實現,這種設計稱爲MixIn
Python中定製類
- Python中打印一個類實例需要自定義
__str__()
方法, 但是這樣仍需要print,直接顯示變量調用的不是__str__()
而是__repr__()
,兩者的區別在於__str()__
返回的是用戶看到的字符串,而__repr()__
返回的是程序開發者看到的字符串。
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
print(Student('Bob'))
# output: Student object (name: Bob)
s = Student('Bob')
- 如果一個類想被用於
for...in..
那麼必須要實現__iter__()
方法,該方法返回一個迭代對象,Python的for循環會不斷調用該迭代對象的__next__()
方法拿到循環的下一個值,知道遇到StopIteration
錯誤時退出循環。
下面是一個斐波那契數列
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a+self.b
if self.a > 10000:
raise StopIteration()
return self.a
for n in Fib():
print(n)
- Fib實例雖然能夠作用於for循環,但是不能像list一樣支持索引,我們需要實現
__getitem__()
方法,但是這個實現沒有對slice中的step參數作處理,也沒有對負數進行處理
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a+self.b
if self.a > 10000:
raise StopIteration()
return self.a
def __getitem__(self, item):
if isinstance(item, int):
a, b = 1, 1
for x in range(item):
a, b = b, a+b
return a
if isinstance(item, slice):
start = item.start
stop = item.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a+b
return L
f = Fib()
print(f[0])
# output: 1
print(f[10])
# output: 89
print(f[1:10])
# output:[1, 2, 3, 5, 8, 13, 21, 34, 55]
- 正常情況下,我們調用類中方法或者屬性時,如果不存在就會報錯,但是我們可以通過
__getattr__()
方法動態返回一個屬性
class Student(object):
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
if attr == 'age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
s = Student('Bob')
print(s.age())
# output: 25
print(s.score())
# AttributeError: 'Student' object has no attribute 'score'
- 我們可以利用
__call__()
方法,直接對實例進行調用, 所以可以把對象看成函數,把函數看成對象。我們可以通過callable
函數來判斷一個對象是否能被調用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
return ('Student name is %s'% self.name)
s = Student('Bob')
print(s())
- 使用枚舉類,可以從
Enum
派生出自定義類
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
day1 = Weekday.Mon
print(day1)
print(Weekday['Tue'])
print(Weekday(1))
print(Weekday.Thu.value)
for name, member in Weekday.__members__.items():
print(name, ' ==> ', member)
"""
Weekday.Mon
Weekday.Tue
Weekday.Mon
4
Sun ==> Weekday.Sun
Mon ==> Weekday.Mon
Tue ==> Weekday.Tue
Wed ==> Weekday.Wed
Thu ==> Weekday.Thu
Fri ==> Weekday.Fri
Sat ==> Weekday.Sat
"""