1. 程式人生 > >從 Python到Tensorflow 學習之路(三)

從 Python到Tensorflow 學習之路(三)

從 Python到Tensorflow 學習之路(四)


最近畢業設計題目是研究對抗樣本,要用tensorflow來搭建神經網路,因此python必不可少,這個不是一個傳統的Python學習教程只是把學習Python過程中遇到的問題和經驗記錄下來(基於Python3.5),如果想要一步一步學習Python建議看下面的網站。
Python學習教程


模組

  • 在Python中,一個.py檔案就稱之為一個模組(module),其大大提高了程式碼的可維護性。
  • 使用模組可以避免函式名和變數名衝突,相同名字的函式和變數完全可以分別存在於不同的模組中。
  • 為了避免模組名衝突,Python引入了按照目錄來組織模組的方法,稱為包(package)。
    假設我們有兩個模組abc和xyz,與其他模組衝突,我們可以使用包來組織模組,方法是選擇一個頂層包名,比如mycompany按照如下目錄存放
    snapshot2.png-2.8kB
    那麼abc.py模組名就變成了mycompany.abc,類似的xyz.py模組名就變成了mycompany.xyz

注意每一個包目錄下都會有一個__init__.py,這個檔案是必須存在的。否則Python就把這個目錄當成普通目錄,而不是一個包。__init__.py可以是空檔案,也可以有Python程式碼。因為__init__.py
本身就是一個模組,它的模組名就是mycompany

使用模組
snapshot4.png-31.1kB

"""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
"""

我們下期見~