python基礎之函數和面向對象詳解

函數

python中『一切皆對象』, 函數也不例外.

在之前所學的C++Java中, 可以發現函數的返回值要麼為空, 要麼是某種數據類型, 但是在python中, 返回值可以是任何對象, 包括函數.

函數參數

函數的參數種類比較多, 主要有:

1.位置參數 (positional argument): 就是最常見的x, y等

2默認參數 (default argument): 給定一個默認值, 用戶也可以傳入實參來調整.

def func(x, y=3):
    print(x+y)

func(1)  # 4
func(1, 666)  # 667

3.可變參數 (variable argument): 不限制輸入參數的個數, 傳入後自動保存為元組類型.

1.*args 是可變參數,args 接收的是一個 tuple

def printinfo(arg1, *args):
    print(arg1)
    print(args, type(args))

printinfo(10)  # 僅一個參數, 沒有屬於args的.
# 10
# () <class 'tuple'>
printinfo(70, 60, 50)  # 除arg1位置匹配的, 其他都傳入給可變參數
# 70
# (60, 50) <class 'tuple'>

4.關鍵字參數 (keyword argument): 不限制關鍵字的個數和命名, 傳入後自動保存為字典的形式.

1.**kw 是關鍵字參數,kw 接收的是一個 dict

def printinfo(arg1, *args):
    print(arg1)
    print(args, type(args))

printinfo(10)  # 僅一個參數, 沒有屬於args的.
# 10
# () <class 'tuple'>
printinfo(70, 60, 50)  # 除arg1位置匹配的, 其他都傳入給可變參數
# 70
# (60, 50) <class 'tuple'>

5.命名關鍵字參數 (name keyword argument)

1.命名關鍵字參數是為瞭限制調用者可以傳入的『參數名』,也可以提供默認值.

2.與關鍵字參數不同的是, 關鍵字參數的名字和值都是任意的, 後續再進行匹配使用, 而命名關鍵字則隻能接受給定的關鍵字作為參數.定義命名關鍵字參數

3.不要忘瞭寫分隔符 *, 否則定義的是位置參數, 命名關鍵字參數調用函數時必須給定參數名.

def person(name, *, age, height=1.90):
    print(f'{name}今年{age}歲, 身高{height:.2f}m')


person('張三', age=18, height=1.80)  # 張三今年18歲, 身高1.80m
person('李四', age=18)  # 李四今年18歲, 身高1.90m
person('王五')  # TypeError, 需要傳入給定關鍵字

6.參數組合

Python中定義函數時, 以上這5種參數都可以使用, d但最多可以使用4種, 並且要註意順序:

1.位置參數、默認參數、可變參數和關鍵字參數.

2.位置參數、默認參數、命名關鍵字參數和關鍵字參數.

變量作用域

在python程序中, 處於不同位置的變量, 有不同的作用域.

  • 定義在函數內部的變量擁有局部作用域, 該變量稱為局部變量.
  • 定義在函數外部的變量擁有全局作用域, 該變量稱為全局變量.
  • 局部變量隻能在其被聲明的函數內部訪問, 而全局變量可以在整個程序范圍內訪問.

需要註意的是:

  • 當局部變量試圖訪問全局變量時, 一定要在函數內聲明global.
  • 當局部變量與全局變量命名沖突時, 程序會優先選擇局部變量.

內嵌函數和閉包

內嵌函數就是在外層函數內定義內層函數.

def outer():
    print('outer函數在這被調用')

    def inner():
        print('inner函數在這被調用')

    inner()  # 該函數隻能在outer函數內部被調用


outer()
# outer函數在這被調用
# inner函數在這被調用

閉包是一個比較重要的語法結構, 結構上與內嵌函數類似, 區別在於返回值, 閉包的外層函數返回值是一個函數.

如果在一個內部函數裡對外層非全局作用域的變量進行引用, 那麼內部函數就被認為是閉包.

通過閉包可以訪問外層非全局作用域的變量, 這個作用域稱為閉包作用域.

def funX(x):
    def funY(y):
        print('使用funY(y)')
        return x * y

    return funY


i = funX(8)
print(type(i))  # <class 'function'>
print(i(5))  # 40

註意到上述代碼中, 內部函數FunY中使用瞭外部非全局作用域的變量x.

同樣是, 函數內嵌套的函數作用域也需要特別註意, 若我們需要修改閉包內的變量, 需要使用nonlocal關鍵字.

num = 999


def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal關鍵字聲明
        num = 100
        print(f'inner中num = {num}')

    inner()
    print(f'outer中num = {num}')


outer()
# inner中num = 100
# outer中num = 100
print(f'全局中num = {num}')
# 全局中num = 999

lambda 表達式

lambda需要註意的是:

  • 匿名函數沒有return, 表達式本身就是返回值.
  • 匿名函數擁有『自己的命名空間』, 不能訪問參數列表之外或全局變量.

匿名函數主要適用於函數式編程(函數不會影響函數之外的內容)的一些高階函數中. 例如map映射和filter過濾, 當然也可以在自己自定義函數中使用.

odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist))  # [1, 3, 5, 7, 9]

m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))  # [1, 4, 9, 16, 25]

面向對象

三大特性

面向對象就必須瞭解三大特性:

  • 封裝: 把客觀事物封裝成抽象的類, 讓數據和方法給信任的類或對象操作, 而隱藏部分信息.
  • 繼承: 子類自動共享父類的屬性和方法.
    • 一般認為一個類是自身的子類;
    • 可以使用issubclass(B, A)查看B是否是A的子類.
    • python也支持多繼承, 但會使得類的整體層次復雜, 所以並不建議使用.
  • 多態: 同一個方法的調用, 會由於不同對象而得到不同的結果.
    • 必要條件是繼承方法的重寫.

類、類對象 和 實例對象

  • 類: 就是指對類的定義
  • 類對象: 是在創建類的時候, 在內存開辟的一個空間, 一個類隻有一個類對象.
  • 實例對象: 通過實例化類創建的對象, 可以有多個.

類屬性 和 對象屬性

  • 類屬性: 類定義內, 類方法外定義的變量稱為類屬性, 類屬性屬於類對象, 可以被多個實例化對象所共享, 就像我們都有一個傢, 名字叫中國一樣.
  • 對象屬性: 對象屬性和具體創建的對象實例直接相關, 並且相互之間不共享屬性, 就像我的老婆隻是我的一樣.
class A():
    a = 0  #類屬性
    def __init__(self, xx):
        A.a = xx  #使用類屬性可以通過 (類名.類屬性)調用。

有一些操作屬性的方法:

  • 使用hasattr(object, name)來判斷對象是否包含對應的屬性或方法.
  • 使用getattr(object, name)來獲取屬性或方法.
  • 使用setattr(object, name, value)來修改屬性值, 或創建新的屬性和值.
  • 使用delattr(object, name)來刪除屬性.
class A(object):
    name = '張三'
    def set(self, a, b):
        x = a
        a = b
        b = x
        print(a, b)


a = A()
print(hasattr(a, 'name'))  # 判斷是否有name屬性 True
print(hasattr(a, 'set'))  # 判斷是否有set方法 True
x = getattr(a, 'name')  # 獲取屬性值
print(x)  # 張三
c = getattr(a, 'set')  # 獲取方法
c(a='1', b='2')  # 2 1

私有

私有屬性和方法僅需在定義命名的時候加上兩個下劃線"__"即可.

相對於公有屬性和公有方法來說, 私有屬性和私有方法更加的安全. 從定義上來說, 將需要安全保護的屬性和方法封裝為私有, 可以阻止外部直接調用, 而必須使用實例化對象方法類方法進行調用, 從而提高安全性.

但在python中的私有是『偽私有』, 即可以使用類名, 通過 object._className__attrName 訪問私有屬性,用 object._className__func() 訪問私有方法.

class JustCounter:
    __secretCount = 0  # 私有變量
    publicCount = 0  # 公開變量

    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print(self.__secretCount)


counter = JustCounter()
counter.count()  # 1
print(counter.publicCount)  # 1
# 特殊方法依舊可以訪問
print(counter._JustCounter__secretCount)  # 1
# 直接訪問則會報錯.
print(counter.__secretCount)

實例直接使用就可以增加屬性瞭, 這點需要註意一下.

class B:
    def func(self):
        print('調用func方法')


b = B()
print(b.__dict__)  # 查看屬性 {}
b.name = '張三'
b.age = 18
print(b.__dict__)  # 查看屬性{'name': '張三', 'age': 18}

b1 = B()
print(b1.__dict__)  # 查看屬性 {}

魔法方法

基本的魔法方法

魔法方法基本上是被下劃線包圍的一些特殊方法. 相比於普通的方法, 它能夠在適當的時候自動調用. 第一個參數一般是cls『類方法』或者self『實例方法』.

  • __init__(self[, ...]) 構造器, 當一個實例被創建的時候調用的初始化方法.
  • __new__(cls[, ...]) 在一個對象實例化的時候所調用的第一個方法, 在調用__init__初始化前, 先調用__new__.
    • 需要註意的是, __new__的返回值必須為當前類的實例, 否則將不會調用__init__初始化.
    • 主要是在繼承一些不可變的class(比如int, str, tuple)時, 提供一個自定義該類實例化過程的途徑. 
  • __del__(self) 析構器, 當一個對象將要被系統回收之時調用的方法.
  • __str__(self): 當你打印一個對象、使用%s格式化或使用str強轉數據類型的時候,觸發__str__.
  • __repr__(self)__str__(self)的備胎, 情況類似, 不過自定義時往往更加準確, 主要用於調試.

算術運算符

普通的計算在對象中是無法進行的, 需要自定義計算方式.

__add__(self, other)定義加法的行為: +

__sub__(self, other)定義減法的行為: -

__mul__(self, other)定義乘法的行為: *

__truediv__(self, other)定義真除法的行為: /

__floordiv__(self, other)定義整數除法的行為: //

__mod__(self, other) 定義取模算法的行為: %

__divmod__(self, other)定義當被 divmod() 調用時的行為 

divmod(a, b)把除數和餘數運算結果結合起來,返回一個包含商和餘數的元組(a // b, a % b)。 

__pow__(self, other[, module])定義當被 power() 調用或 ** 運算時的行為

__lshift__(self, other)定義按位左移位的行為: <<

__rshift__(self, other)定義按位右移位的行為: >>

__and__(self, other)定義按位與操作的行為: &

__xor__(self, other)定義按位異或操作的行為: ^

__or__(self, other)定義按位或操作的行為: |

還有對應的反運算符, 在之前加上r即可, 例如__rsub__. 對應增量賦值運算符, 在之前加上i即可, 例如__isub__.

屬性訪問 

__getattr__(self, name): 定義當用戶試圖獲取一個不存在的屬性時的行為.

__getattribute__(self, name): 定義當該類的屬性被訪問時的行為(先調用該方法, 查看是否存在該屬性, 若不存在, 接著去調用__getattr__).

__setattr__(self, name, value): 定義當一個屬性被設置時的行為.

__delattr__(self, name): 定義當一個屬性被刪除時的行為.

描述符

描述符就是將某種特殊類型的類的實例指派給另一個類的屬性.

__get__(self, instance, owner): 用於訪問屬性, 它返回屬性的值.

__set__(self, instance, value): 將在屬性分配操作中調用, 不返回任何內容.

__del__(self, instance): 控制刪除操作, 不返回任何內容.

迭代器和生成器

迭代器

迭代是Python最強大的功能之一, 是訪問集合元素的一種方式.

  • 迭代器是一個可以記住遍歷的位置的對象.
  • 迭代器對象從集合的第一個元素開始訪問, 直到所有的元素被訪問完結束.
  • 迭代器隻能往前不會後退.
  • 字符串, 列表或元組對象都可用於創建迭代器.

迭代器有兩個基本的方法: iter() 和 next():

  • iter(object) 函數用來生成迭代器.
  • next(iterator[, default]) 返回迭代器的下一個項目. 在元素為空時返回默認值, 若沒有則會觸發 StopIteration 異常. 在元組推導式和next中使用過, 不過是下面的『生成器』.

把一個類作為一個迭代器使用需要在類中實現兩個魔法方法 __iter__() 與 __next__() .

  • __iter__(self)定義當迭代容器中的元素的行為, 返回一個特殊的迭代器對象, 這個迭代器對象實現瞭 __next__() 方法並通過 StopIteration 異常標識迭代的完成.
  • __next__() 返回下一個迭代器對象. 
    • StopIteration 異常用於標識迭代的完成,防止出現無限循環的情況,在 __next__() 方法中我們可以設置在完成指定循環次數後觸發 StopIteration 異常來結束迭代。
class Fibs:
    def __init__(self, n=10):
        self.a = 0
        self.b = 1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > self.n:
            raise StopIteration
        return self.a


fibs = Fibs(100)
for each in fibs:
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89

生成器

在 Python 中,使用瞭 yield 的函數被稱為生成器(generator)。

  • 跟普通函數不同的是, 生成器是一個返回迭代器的函數, 隻能用於迭代操作, 更簡單點理解生成器就是一個迭代器.
  • 在調用生成器運行的過程中, 每次遇到 yield 時函數會暫停並保存當前所有的運行信息, 返回 yield 的值, 並在下一次執行 next() 方法時從當前位置繼續運行.
  • 調用一個生成器函數, 返回的是一個迭代器對象.
def libs(n):
    a = 0
    b = 1
    while True:
        a, b = b, a + b
        if a > n:
            return
        yield a


for each in libs(100):
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!  

推薦閱讀: