自定義函數

1.1 使用函數

在Python中,函數必須先聲明,而後才能調用它,使用函數時,只要按照函數定義的形式,向函數傳遞必需的參數,就能夠調用函數完成相應的功能或者得到函數返回的處理結果。編程

若是函數有返回值,那麼須要在函數中使用return語句返回計算結果,聲明函數的通常形式以下。app

  

1.1.1 聲明函數並調用

def <函數名 > (參數列表):
   <函數語句>
   return <返回值>
   
其中參數列表和返回值不是必須的,return後也能夠不跟返回值,甚至連return也沒有。對於return後沒有返回值的和沒有return語句的函數都會返回None值。
有些函數可能既不須要傳遞參數,也沒有返回值。函數

def tpl_sum( T ):
     result = 0
     for i in T:
        result += i
     return result

1.2深刻函數

1.2.1默認值參數

聲明一個參數具備默認值的函數形式以下。測試

  def <函數名> (參數=默認值):
   <語句>this

聲明瞭一個帶默認值參數的函數,代碼以下:url

def hello (name='Python'):
     print ('你好,%s!' % name)
  print ('無參數調用時的輸出:')
  hello()
  print ('有參數("Jonson")調用時的輸出:')
  hello('Jonson')

1.2.2參數傳遞

調用函數提供參數時,按順序傳遞的參數要位於關鍵字參數以前,並且不能有重複的狀況。spa

1.2.3可變數量的參數傳遞

在自定義函數時,若是參數名前加上一個星號「*」,則表示該參數就是一個可變長參數。在調用該函數時,若是依次序將全部的其餘變量都賦予值以後,剩下的參數將會收集在一個元組中,元組的名稱就是前面帶星號的參數名。線程

def change_para_num (*tp1):
     print (type (tp1))             #輸出tp1變量的類型
     print (tp1)

  change_para_num (1)
  change_para_num (1,2,3)

當自定義函數時,參數中含有前面所介紹的三種類型的參數,則通常來講帶星號的參數應放在最後。當帶星號的參數放在最前面時,仍然能夠正常工做,但調用時後面的參數必須以關鍵字參數方式提供,不然因其後的位置參數沒法獲取值而引起錯誤。code

def change_para_num(*tpl,a,b=0,,**dict):
     print('tp1:',tp1)
     print("dict",dict)
     print('a:',a)
     print ('b:',b)

  change_para_num (1,2,3,a=5,c=1,d=2,e=3) #c d e 會以字典的形式儲存
  change_para_num(1,2,3)             #該調用會出錯,a變量沒有默認值,也不能獲取值

若是要收集不定數量的關鍵字參數能夠在自定義函數時的參數前加兩顆星即**valuename,能夠以字典的方式被收集到變量valuename之中。對象

def cube (name, **nature):
        all_nature = {'x':1,
                      'y':1,
                      'z':1,
                      'color':'white',
                      'weight':1}
        all_nature.update (nature)
        print (name,"立方體的屬性:")
        print ('體積:',all_nature ['x']*all_nature ['y']*all_nature ['z'])
        print ('顏色:',all_nature ['color'])
        print ('重量:',all_nature ['weight'])

cube ('first')                                    #只給出必要參數的調用
cube ('second',y=3, color='red')                  #提供部分可選參數
cube ('third', z=2, color='green', weight=10)     #提供部分可選參數

1.2.4拆解序列的函數調用

前面使用函數調用時提供的參數都是位置參數關鍵字參數,實際上調用函數時還能夠把元組和字典進行拆解調用。

  • 拆解元組提供位置參數;
  • 拆解字典提供關鍵字參數。

調用時使用拆解元組的方法是在調用時提供的參數前加一個*號;要拆解字典必須在提供的調用參數前加兩個*號。

def mysum (a,b):
     return a+b

  print ('拆解元組調用:')
  print (mysum(* (3,4)))              #調用時拆解元組做爲位置參數
  print ('拆解字典調用:')
  print (mysum(**{'a':3, 'b':4})      #調用時拆解字典做爲關鍵字參數

1.2.5函數調用時參數的傳遞方法

Python中的元素有可變和不可變之分,如整數、浮點數、字符串、元組等都屬於不可變的;而列表和字典都屬於可變的。

列表和字典的可變也是很好理解的,即它們能夠增減元素、修改元素的值。那麼整數、浮點數、字符串等爲何是不可變的呢?
「=」號的做用是將對象引用與內存中某對象進行綁定,既然整數是不可變的,那麼怎麼改變一個指向整數的變量的值的呢?答案是直接在內存中建立一個新的整數值,而後將變量引用與其綁定,這雖然本質上的其餘高級語言不一樣,但在使用上是看不出什麼差異的,但若將其提供給函數做爲參數,則效果不一樣。

在函數調用時,若提供的是不可變參數,那麼在函數內部對其修改時,在函數外部其值是不變的;若提供是可變參數,則在函數內部對它修改時,在函數外部其值也會改變的。

def change (aint, alst):              #定義函數
        aint = 0                      #修改 aint 值
        alst[0]=0                     #修改alst第1個值爲0
        alst.append (4)               #在 alst 中添加一個元素4
        print ('函數中aint:', aint)    #輸出函數中的aint的值
        print ('函數中alst:', alst)    #輸出函數中的alst的值

aint = 3
alst = [1,2,3]
print ('調用前aint:', aint)
print ('調用前 alst:', alst)
change(aint, alst)
print ('調用後aint:', aint)      #調用後值和調用前值相同
print ('調用後alst:', alst)      #調用後值和調用前值不一樣

def myfun (lst= []):     #定義有一個默認值爲空列表的參數
    lst.append('abc')
    print (lst)
#此後三次調用自定義函數
myfun()
myfun()
myfun()
#每次調用函數按默認值的理解,應該每次傳入空列表,列表中只有一個元素'abc'
#在該函數的域上lst是已經存在的,在一個線程內屢次執行會保留上一次執行的值。

def myfun (lst=None):       #定義有一個默認值爲空列表的參數
        lst = [] if lst is None else lst
        lst.append('abc')
        print (lst)

myfun()
myfun()
myfun()
#增長一條推導語句,將lst置空,就能夠達到想要的效果。

1.3變量的做用域

  • 內置做用域:Python預先定義的;
  • 全局做用域:所編寫的整個程序;
  • 局部做用域:某個函數內部範圍。

每次執行函數,都會建立一個新的命名空間,這個新的命名空間就是局部做用域,同一函數不一樣時間運行,其做用域是獨立的,不一樣的函數也能夠具備相同的參數名,其做用域也是獨立的。
在函數內已經聲明的變量名,在函數之外依然可使用。而且在程序運行的過程當中,其值並不相互影響。

def myfun():
        a = 0                   #函數內聲明並初始化變量a爲整數,局部做用域
        a += 3                  #修改a的值
        print ('函數內a:', a)    #輸出函數內a的值

a = 'external'             #全局做用域內a聲明並初始化

print ('全局做用域a:', a)
myfun()                    #調用函數myfun()
print('全局做用域a:', a)

#總結:函數內部聲明的a和外部聲明的a在各自的域上互不干擾!

#那麼如何在函數內使用全局做用域的變量呢?
    def myfun():
        global a             #增長此語句
        a = 0
        a += 3
        print ('函數內a:', a)

a = 'external'
print ('全局做用域a:',a)
myfun()
print ('全局做用域a:', a)

在局部做用域內能夠引用全局做用域內的變量,但不能夠修改它。

好比如下代碼是沒有錯誤的:

 

 a = 3 #定義全局變量
  def myprint(): #聲明函數myprint()
   print (a) #引用全局變量

運行函數myprint()時,會輸出全局變量a的值3。可是若將其改成則會引起錯誤:

 

 a = 3 #定義全局變量
  def myprint(): #聲明函數myprint()
   print (a) #引用全局變量
   a = 5

以上代碼引起的錯誤是局部變量在分配前不能引用,緣由是與Python中的變量查找有關,在此外代碼中函數內聲明瞭a變量並初始化,因此a被判爲局部變量,但卻以前在print(a)中引用了它。

Python中的變量查找:

clipboard.png

1.4使用匿名函數(lambda)

語法形式以下:
lambda params:expr

其中params至關於聲明函數時的參數列表中用逗號分隔的參數,expr是函數要返回值的表達式,而表達式中不能包含其餘語句,也能夠返回元組(要用括號),還容許在表達式中調用其餘函數。

lambda的示例代碼:

import math
s = lambda x1, y1, x2, y2:math. sqrt( (x1-x2) **2+(y1-y2) **2)
s(1,1,0,0)
#結果:1.4142135623730951

代碼的第二行定義了一個求兩座標點距離的匿名函數,並用s引用,以後調用它來求(1,1)與(0,0)座標點的距離,結果爲1.414。


這裏的params是參數列表。它的結構與Python中函數(function)的參數列表是同樣的。具體來講,argument_list能夠有很是多的形式。例如:

a, b
  a=1, b=2
  *args
  **kwargs
  a, b=1, *args

這裏的expr是一個關於參數的表達式。表達式中出現的參數須要在argument_list中有定義,而且表達式只能是單行的。如下都是合法的表達式:

1

None

a + b

sum(a)

1 if a >10 else 0

下面是一些lambda函數示例:

lambda x, y: x*y;函數輸入是x和y,輸出是它們的積x*y

lambda:None;函數沒有輸入參數,輸出是None

lambda *args: sum(args); 輸入是任意個數的參數,輸出是它們的和(隱性要求是輸入參數必須可以進行加法運算)

lambda **kwargs: 1;輸入是任意鍵值對參數,輸出是1
  • 簡單匿名函數 寫起來快速而簡單,省代碼;
  • 不復用的函數 在有些時候須要一個抽象簡單的功能,又不想單獨定義一個函數;
  • 爲了代碼清晰 有些地方使用它,代碼更清晰易懂。
  • 好比在某個函數中須要一個重複使用的表達式,就可使用lambda來定義一個匿名函數,屢次調用時,就能夠少寫代碼,但條理清晰。

1.5Python經常使用內建函數

沒有導入任何模塊或包時Python運行時提供的函數稱爲內建函數

dir(obj) 列出對象的相關信息
help(obj) 顯示對象的幫助信息
bin(aint) 十進制數轉換爲二進制數的字符串形式
hex(aint) 十進制數轉換爲十六進制數的字符串形式
oct(aint) 十進制數轉換爲八進制數的字符串形式
callable(obj) 測試對象是否可調用(函數)
chr(aint) ascii碼轉爲字符
ord(char) 將字符轉爲ascii碼
filter(seq) 簡單的理解爲過濾器,須要兩個參數,function,和一個序列(字符串、列表、元組都是序列),過濾器會依次將序列的值傳入function中,若是返回True的話,將其從新生成一個列表返回。
map(seq) 和filter()相似,也是將序列放入函數進行運算,可是,不論運算結果爲何,map()都將忠實反饋,這是map()和filter()的主要區別。請注意,filter()和map()中的function都必要有一個返回值。
isinstance(obj,typestr) 測試對象是否爲某類型
print(list(filter(lambda x:True if x % 3 == 0 else False, range(100)))) #返回100之內3的倍數
print(list(map(lambda x:True if x % 3 == 0 else False, range(100))))#返回是一堆true或者false
print(list(filter (lambda x:x % 2, alst)))#當x爲偶數時生成一個新列表將其加入
print(list (map (lambda x:2*x,alst)))#list(map(lambda x:2*x,alst))中的lambda x:2*x,就是用lambda定義的一個匿名函數,並經過map()函數,將其應用到alst列表中的每一個元素。

1.6類的屬性和方法

class <類名>(父類名):
     pass

class MyClass():
    "pass"

myclass=MyClass()
help(MyClass)           輸出MyClass的幫助信息
print(myclass.__doc__)  輸出類的描述信息爲pass

1.6.1類的方法

類中的方法定義和調用與函數定義和調用的方式基本相同,其區別有:

  • 方法的第一個參數必須是self,並且不能省略
  • 方法的調用須要實例化類,並以實例名.方法名(參數列表)形式調用;
  • 總體進行一個單位的縮進,表示其屬於類體中的內容。

class Demolnit:

def __init__(self,x,y=0):      #定義構造方法,具備兩個初始化
    self.x = x
    self.y = y

def mycacl (self):             #定義應用初始化數據的方法
      return self.x + self.y

def info (self):             #定義一個類方法info()
    print ('我定義的類!')

def mycacl_2 (self,x,y):       #定義一個類方法mycacl()
    return x + y
    
dia = Demolnit (3)                #用一個參數實例化類
print ('調用mycacl方法的結果1:')
print (dia.mycacl())

dib = Demolnit (3, 7)             #用二個參數實例化類
print ('調用mycacl方法的結果2:')
print (dib.mycacl())

def coord_chng(x,y):                 #定義一個全局函數,模擬座標值變換
        return (abs (x),abs (y))          #將x,y值求絕對值後返回

class Ant: #定義一個類Ant

        def __init__(self,x=0,y=0):       #定義一個構造方法
            self.x = x
            self.y = y
            self.disp_point()              #構造函數中調用類中的方法disp_point()


        def move (self,x, y):            #定義一個方法move()
            x,y = coord_chng(x,y)         #調用全局函數,座標變換
            self.edit_point (x, y)        #調用類中的方法edit_point()
            self.disp_point()             #調用類中的方法disp_point()

        def edit_point (self, x,y):      #定義一個方法
            self.x += x
            self.y += y

        def disp_point (self):          #定義一個方法
            print ("當前位置:(%d,%d)" % (self .x, self.y))

ant_a = Ant()                      #實例化Ant()類
ant_a.move(2,4)                   #調用ant_a實例的方法move()
ant_a.move (-9, 6)                #調用ant_a實例的方法move()

1.6.2類的屬性

類的屬性有兩類:

  • 實例屬性
  • 類屬性

實例屬性即同一個類的不一樣實例,其值是不相關聯的,也不會互相影響的,定義時使用「self.屬性名」,調用時也使用它;
類屬性則是同一個類的全部實例所共有的,直接在類體中獨立定義,引用時要使用「類名.類變量名」形式來引用,只要是某個實例對其進行修改,就會影響其餘的全部這個類的實例。

class Demo_Property: #定義類
    class_name = "Demo_Property"            #類屬性

    def __init__(self,x=0):
        self.x = x                           #實例屬性

    def class_info (self):                  #輸出信息的方法
        print ('類變量值:', Demo_Property.class_name)
        print ('實例變量值:', self.x)

    def chng (self,x):                      #修改實例屬性的方法
        self.x = x                           #注意實例屬性的引用方式

    def chng_cn (self, name):               #修改類屬性的方法
        Demo_Property.class_name = name      #注意類屬性引用方式

dpa = Demo_Property()                      #實例化類
dpb = Demo_Property()                      #實例化類
print ('初始化兩個實例')
dpa.class_info()
dpb.class_info()
print ('修改實例變量')
print ('修改dpa實例變量')
dpa.chng(3)
dpa.class_info()
dpb.class_info()
print ('修改dpb實例變量')
dpb.chng(10)
dpa.class_info()
dpb.class_info()
#這裏獲得實例變量是屬於每一個對象本身的。
print ('修改類變量')
print ('修改dpa類變量')
dpa.chng_cn('dpa')
dpa.class_info()
dpb.class_info()
print ('修改dpb實例變量')
dpb.chng_cn('dpb')
dpa.class_info()
dpb.class_info()
#這裏獲得類變量是屬於每一個對象共有的,一旦修改每一個對象的類變量都會相應的改變。

綜上:對於實例屬性來講,兩個實例之間互不聯繫,它們各自能夠被修改成不一樣的值;對於類屬性來講,不管哪一個實例修改了它,會致使全部實例的類屬性的值發生變化。

1.6.3 類成員方法與靜態方法

類的屬性有類屬性和實例屬性之分,類的方法也有不一樣的種類,主要有:

  • 實例方法;
  • 類方法;
  • 靜態方法。

前文中定義的全部類中的方法都是實例方法,其隱含調用參數是類的實例。類方法隱含調用參數則是類,靜態方法沒有隱含調用參數。類方法和靜態方法的定義方式都與實例方法不一樣,它們的調用方式也不一樣。

靜態方法定義時應使用裝飾器@staticmethod進行修飾,是沒有默認參數的。類方法定義時應使用裝飾器@classmethod進行修飾,必須有默認參數「cls」。它們的調用方式能夠直接由類名進行調用,調用前也能夠不實例化類,固然也能夠用該類的任一個實例來進行調用。

class DemoMthd:                   #定義一個類

    @staticmethod                  #靜態方法的裝飾器
    def static_mthd():            #靜態類定義
        print ('調用了靜態方法!')

    @classmethod                   #類方法的裝飾器
    def class_mthd(cls):          #類方法定義,帶默認參數cls
        print ('調用了類方法!')

DemoMthd.static_mthd()            #未實例化類,經過類名調用靜態方法
DemoMthd.class_mthd()             #未實例化類,經過類名調用類方法
dm = DemoMthd()                   #實例化類
dm.static_mthd()                  #經過類實例調用靜態方法
dm.class_mthd()                   #經過類實例雅爾塔類方法

1.7類的繼承

類是能夠繼承的,而且也給出了繼承類的代碼格式。子類繼承了父類以後,就具備了父類的屬性與方法,但不能繼承父類的私有屬性和私有方法(屬性名或方法名前綴爲兩個下畫線的__),子類中還能夠重載來修改父類的方法,以實現與父類不一樣的行爲表現或能力。

1.7.1 類的繼承

演示了方法的重寫和添加

class Ant:                                    #定義類Ant

    def __init__(self,x=0,y=0,color='black'):#定義構造方法
        self.x = x
        self.y = y
        self.color =color

    def __crawl (self,x,y):                     #定義方法(模擬爬行)
        self.x = x
        self.y = y
        print ('爬行...')
        self.info()

    def info(self):
        print ('當前位置:(%d,%d)' % (self.x, self.y))

    def attack (self):                       #定義方法(模擬攻擊)
        print ("用嘴咬!")

class FlyAnt(Ant):                          #定義FlyAnt類,繼承Ant類

    def attack (self):                      #修改行爲(攻擊方法不一樣)
        print ("用尾針!")

    def fly(self,x,y):                       #定義方法(模擬飛行)
        print ('飛行...')
        self.x = x
        self.y = y
        self.info()

flyant = FlyAnt (color='red')               #實例化類
flyant.crawl(3,5)                            #__crawl此時會報錯沒法繼承父類實例方法
#flyant.crawl(3,5)                           #調用方法(模擬爬行)
flyant.fly(10,14)                           #調用訪求(模擬飛行)
flyant.attack() 

                        #調用方法(模擬攻擊)

1.7.2 多重繼承

在面向對象編程的語言中,有的容許多重繼承,即一個類能夠繼承多個類;有的只容許單一繼承,即一個類只能繼承一個類,而Python中則容許多重繼承。

多重繼承的方式是在類定義時的繼承父類的括號中,以「,」分隔開要多重繼承的父類便可。而多重繼承時,繼承順序也是一個很重要的要素,若是繼承的多個父類中有相同的方法名,但在類中使用時未指定父類名,則Python解釋器將從左至右搜索,即調用先繼承的類中的同名方法。

1.7.3 方法的重載

當子類繼承父類時,子類若是要想修改父類的行爲,則應使用方法重載來實現,方法重載的基本方法是在子類中定義一個和所繼承的父類中須要重載方法同名的一個方法便可。

多重繼承中,兩個父類都具備同名方法info(),但在子類中也定義了一個info()方法,即info()方法被重載了。當子類實例調用info()方法時,就會直接調用該實例中定義的info()方法,而不會去調用任何一個父類的info()方法。

綜合來說一個同名方法先找子類,而後父類由先繼承到後繼承。

裝飾器重載:

import functools
import locale
from urllib import request

# 函數重載
@functools.singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

print('函數重載')
fun("test.", verbose=True)
fun(42, verbose=True)
fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
fun({'a':'txt','b':'dat'}, verbose=True)
相關文章
相關標籤/搜索