1. 程式人生 > >Python基礎-第七天-面向對象編程進階和Socket編程簡介

Python基礎-第七天-面向對象編程進階和Socket編程簡介

python

本篇內容:

1.面向對象編程進階-靜態方法

2.面向對象編程進階-類方法

3.面向對象編程進階-屬性方法

4.面向對象編程進階-特殊成員(內置方法)

5.面向對象編程進階-反射

6.異常處理、斷言

7.Socket編程簡介



一、面向對象編程進階-靜態方法

1.靜態方法的實現

通過@staticmethod裝飾器可以把其裝飾的方法變為一個靜態方法;

變成靜態方法後,形參中可以不用寫self了。如果寫了self,默認是不會把對象本身傳遞給self,需要手動傳遞;

class Dog(object):

    @staticmethod  # 將eat方法函數變成了靜態方法
    def eat():
        print("正在吃%s" % (self.name, "包子"))


靜態方法允許在不創建對象的情況下直接被調用,意思就是不需要實例化後通過對象來調用,可以直接通過類來調用;

Dog.eat()  # 通過類直接調用
正在吃包子



二、面向對象編程進階-類方法

1.類方法的實現

通過@classmethod裝飾器可以把其裝飾的方法變為一個類方法;

類方法至少需要一個cls參數,在調用類方法時,自動將調用該方法的類傳遞給cls參數;

class Dog(object):
    name = "二哈"
    food = "包子"

    @classmethod  # 將eat方法函數變成了類方法
    def eat(cls):
        print("%s正在吃%s" % (cls.name, cls.food))  # 可以訪問類變量


類方法允許在不創建對象的情況下直接被調用,意思就是不需要實例化後通過對象來調用,可以直接通過類來調用;

Dog.eat()  # 通過類直接調用,將類本身傳遞給cls
二哈正在吃包子



三、面向對象編程進階-屬性方法

Python中的類有經典類和新式類,新式類的屬性比經典類的屬性豐富,所以通過裝飾器定義屬性要區分新式類和經典類;

當使用靜態字段的方式創建屬性時,經典類和新式類無區別;

不管是新式類還是經典類,都只能通過對象調用,不能通過類調用。


1.通過裝飾器定義屬性

①新式類

具有三種裝飾器

class Dog(object):
    def __init__(self, name):
        """
        構造方法
        :param name: 狗的名字
        """""
        self.name = name
        self.__food = None

    @property  # 將eat方法函數變成了靜態屬性
    def eat(self):
        print("%s正在吃%s" % (self.name, self.__food))

    @eat.setter
    def eat(self, food):
        self.__food = food
        print("賦值完成為:", self.__food)

    @eat.deleter
    def eat(self):
        del self.__food
        print("刪除成功")
        
        
d = Dog("二哈")        
        
d.eat  # 執行@property裝飾的eat方法函數
二哈正在吃None

d.eat = "包子"  # 執行@eat.setter裝飾的eat方法函數,並將包子賦值給eat方法的food參數
賦值完成為: 包子

d.eat
二哈正在吃包子

del d.eat  # 執行@eat.deleter裝飾的eat方法函數
刪除成功

註意這三個裝飾器的書寫順序,@property要寫在最上面



②經典類

只具有一種裝飾器

class Dog:
    def __init__(self, name):
        """
        構造方法
        :param name: 狗的名字
        """""
        self.name = name
        self.__food = None

    @property  # 將eat方法函數變成了靜態屬性
    def eat(self):
        print("%s正在吃%s" % (self.name, self.__food))
        
        
d = Dog("二哈")

d.eat
二哈正在吃None


2.通過靜態屬性方式,定義值為property對象的靜態屬性


定義值為property對象的靜態屬性要寫在方法函數的下面

class Foo:

    def get_bar(self):
        return ‘zhong‘

    # *必須兩個參數
    def set_bar(self, value): 
        return return ‘set value‘ + value

    def del_bar(self):
        return ‘zhong‘

    BAR = property(get_bar, set_bar, del_bar, ‘description...‘)

obj = Foo()

obj.BAR  # 自動調用第一個參數中定義的方法:get_bar
obj.BAR = "alex"  # 自動調用第二個參數中定義的方法:set_bar方法,並將“alex”當作參數傳入
del Foo.BAR  # 自動調用第三個參數中定義的方法:del_bar方法
obj.BAE.__doc__  # 自動獲取第四個參數中設置的值:description...

property的構造方法中有個四個參數:

●第一個參數是方法名,調用 對象.屬性 時自動觸發執行方法;

●第二個參數是方法名,調用 對象.屬性 = XXX 時自動觸發執行方法;

●第三個參數是方法名,調用 del 對象.屬性 時自動觸發執行方法;

●第四個參數是字符串,調用 對象.屬性.__doc__ ,此參數是該屬性的描述信息;



四、面向對象編程進階-特殊成員(內置方法)

Python的類成員存在著一些具有特殊含義的成員,例如:__init__、__call__、__dict__等。

下面介紹幾個常用的特殊成員;


1.__doc__:輸出類的描述信息

class Dog(object):
    """描述狗的類"""
    def eat(self):
        pass


print(Dog.__doc__)
描述狗的類


2.__call__:在對象後面加上括號後,就觸發執行了

構造方法的執行是由創建對象觸發的,即:對象 = 類名();而對於__call__方法的執行是由對象後加括號觸發的,即: 對象() 或 類()()

class Dog(object):
    """描述狗的類"""
    def __call__(self, *args, **kwargs):
        print("__call__方法函數運行了", args, kwargs)


d = Dog()

d(1, 2, 3, name="二哈")
__call__方法函數運行了 (1, 2, 3) {‘name‘: ‘二哈‘}

Dog()(4, 5, 6, name="金毛")
__call__方法函數運行了 (4, 5, 6) {‘name‘: ‘金毛‘}


3.__dict__:查看類或對象中的所有成員

通過類調用,輸出的是字典,字典的key包括類變量、私有類變量、普通方法、類的特殊成員,字典的value是各成員所對應的值;

通過實例調用,輸出的是字典,字典的key包括實例變量名、私有實例變量名,字典的value是各成員所對應的值;

class Dog(object):
    """描述狗的類"""
    age = 2
    __sex = "公"

    def __init__(self, name):
        self.name = name
        self.__food = "包子"

    def eat(self):
        print("%s在吃%s" % (self.name, self.__food))


d = Dog("二哈")

print(d.__dict__)  # 通過對象調用
{‘_Dog__food‘: ‘包子‘, ‘name‘: ‘二哈‘}

for item in Dog.__dict__:  # 通過類調用
    print("類成員", item)
類成員 __weakref__
類成員 __doc__
類成員 __dict__
類成員 age
類成員 __module__
類成員 eat
類成員 _Dog__sex
類成員 __init__


4.__str__:如果類中定義了__str__方法,當打印對象時,默認輸出__str__方法的返回值

class Dog(object):
    """描述狗的類"""
    def __init__(self, name):
        self.name = name
        self.__food = "包子"

    def __str__(self):
        return "<對象的實例屬性姓名的值: %s>" % self.name


d = Dog("二哈")

print(d)
<對象的實例屬性姓名的值: 二哈>



五、面向對象編程進階-反射

1.為什麽需要用到反射

有時需要根據用戶的選擇去調用類中的方法,而不能通過 對象名.用戶輸入的內容 這樣調用方法,因為用戶輸入的內容的數據類型是字符串,也不能 if 用戶輸入的內容 in 對象名 這樣判斷對象中是否存在用戶所輸入的方法。

這時就可以用到反射了,反射的作用是將字符串反射成內存中的對象地址;


2.反射的使用

反射具有四個方法:hasattr、getattr、setattr、delattr


通過hasattr判斷對象中是否存在對應的成員,存在返回True,不存在返回False

class Foo(object):
    def __init__(self):
        self.name = ‘zhong‘

    def eat(self):
        print("%s正在吃東西" % self.name)


obj = Foo()

print(hasattr(obj, "name"))  # 判斷屬性
True

print(hasattr(obj, "eat"))  # 判斷方法
True

print(hasattr(obj, "function"))  # 不存在的成員
False


通過getattr獲取對象中對應的成員內存對象地址

class Foo(object):
    def __init__(self):
        self.name = ‘zhong‘

    def eat(self, food):
        print("%s正在吃%s" % (self.name, food))


obj = Foo()

print(getattr(obj, "eat"))  # 獲取方法的內存對象地址
<bound method Foo.eat of <__main__.Foo object at 0x0000000000B215F8>>

getattr(obj, "eat")("包子")  # 加上括號就是調用該方法了
zhong正在吃包子

func = getattr(obj, "eat")
func("拉面")
zhong正在吃拉面

print(getattr(obj, "name"))  # 獲取屬性
zhong

value = getattr(obj, "name")
print(value)
zhong


通過setattr給對象添加成員

def bulk(self):  # 添加的方法必須要存在
    print("%s在大叫" % self.name)


class Foo(object):
    def __init__(self):
        self.name = ‘zhong‘

    def eat(self, food):
        print("%s正在吃%s" % (self.name, food))


obj = Foo()

setattr(obj, "talk", bulk)  # 添加方法,字符串類型的名稱才是方法名
obj.talk(obj)  # 調用方法,需要手動將對象傳遞給方法的self參數
zhong在大叫

setattr(obj, ‘show‘, lambda num: num + 1)  # 添加方法,字符串類型的名稱才是方法名
print(obj.show(5))
6

setattr(obj, "age", 35)  # 添加屬性,字符串類型的名稱才是屬性名
print(obj.age)
35


通過delattr刪除對象中對應的成員

class Foo(object):
    def __init__(self):
        self.name = ‘wupeiqi‘

    def eat(self, food):
        print("%s正在吃%s" % (self.name, food))


obj = Foo()

delattr(obj, "name")  # 刪除屬性

delattr(obj, "eat")  # 刪除方法



六、異常處理、斷言

1.異常處理

①常見異常

AttributeError:試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性x;

IOError:輸入/輸出異常。基本上是無法打開文件;

FileNotFoundError:指定的文件不存在;

ImportError:無法引入模塊或包。基本上是路徑問題或名稱錯誤;

IndentationError:語法錯誤。代碼沒有正確對齊,縮進錯誤。這個錯誤捕獲不到;

IndexError:下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5];

KeyError:試圖訪問字典裏不存在的鍵;

KeyboardInterrupt:Ctrl鍵 + C鍵被按下;

NameError:使用一個還未被賦予對象的變量;

SyntaxError:Python代碼非法,代碼不能編譯(個人認為這是語法錯誤,寫錯了)。這個錯誤捕獲不到;

TypeError:傳入對象類型與要求的不符合;

UnboundLocalError:試圖訪問一個還未被設置的局部變量,基本上是由於另有一個同名的全局變量,導致你以為正在訪問它。

ValueError:傳入一個調用者不期望的值,即使值的類型是正確的;


②捕獲異常的語法

try:
    主代碼塊
except 異常種類1 as e:  # e是錯誤的詳細信息。只能抓到異常種類1
    異常1時執行的代碼
except 異常種類2 as e:  # 只能抓到異常種類2
    異常2時執行的代碼
...
except Exception as e:  # Exception是萬能異常,可以捕獲任意異常。
    其它異常時執行的代碼
else:
    主代碼塊執行完後,執行該塊代碼
finally:
    無論異常與否,都會執行該塊代碼


③自定義異常

定義語法:

class 自定義的錯誤類的類名(Exception):
    def __init__(self, msg):
        self.message = msg

    def __str__(self):
        return self.message

try:
    raise 自定義的錯誤類的類名("錯誤信息")
except 自定義的錯誤類的類名 as e:
    print(e)

Exception是一個異常的基類;

raise是觸發的意思,這裏代表主動(即手動)觸發自定義的錯誤;

打印e就代表打印實例,會觸發自定義的錯誤類中的__str__方法函數;


2.斷言

assert語句用來聲明某個條件是真的。當assert語句失敗的時候,會拋出一個AssertionError異常;

如果檢驗某個條件時,在它非真的時候引發一個錯誤,那麽assert語句是應用在這種情形下的理想語句。



①斷言的定義

語法:

assert expression, arguments

python的assert斷言是聲明表達式布爾值為真,如果發生異常就說明表達式為假。可以理解為當assert斷言語句為raise-if-not,用來測試表達式,其返回值為假時,就會觸發異常。所以assert expression, arguments的等價語句為:

if not expression:
    raise AssertionError


②例子

# assert語句聲明的條件為真
assert 2 + 2 == 2 * 2, "2加2不等於2乘2"
沒有輸出


# assert語句聲明的條件為假
assert 2 == 1, ‘2不等於1‘
AssertionError: 2不等於1



七、Socket編程簡介

1.socket簡介

socket通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求。

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,對於文件用打開、讀寫、關閉模式來操作。socket就是該模式的一個實現,socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)


socket和file的區別:

●file模塊是針對某個指定文件進行【打開】【讀寫】【關閉】

●socket模塊是針對 服務器端 和 客戶端Socket 進行【打開】【讀寫】【關閉】


建立一個socket必須至少有2端, 一個服務端,一個客戶端, 服務端被動等待並接收請求,客戶端主動發起請求, 連接建立之後,雙方可以互發數據。


socket流程圖:

技術分享


2.socket使用

①服務端,要先開啟服務端。下面的語法是按照先後順序來寫的

server = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0):聲明socket類型,同時生成socket連接對象

參數詳解:

●family即地址簇,是位於網絡層的

socket.AF_INET:IPv4(默認);
socket.AF_INET6:IPv6;

socket.AF_UNIX:只能夠用於單一的Unix系統進程間通信。因為進程之間是不能直接通信的,不能直接在內存中通信。而采用其它辦法通信,比如把數據序列化到硬盤中再由其它進程反序列化得到數據這種方式通信,會受限於硬盤的讀寫速度。在沒有網絡的情況下,本機進程之間想快速的通信可以采用AF_UNIX;

●type即類型,是位於傳輸層的

socket.SOCK_STREAM:流式socket,for TCP(默認);
socket.SOCK_DGRAM:數據報式socket,for UDP;

socket.SOCK_RAW:原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。利用SOCK_RAW可以偽造ip地址實現洪水攻擊;
socket.SOCK_RDM:是一種可靠的UDP形式,即保證交付數據報但不保證順序,發送數據的順序是ABC,接收到的數據順序可能就是CAB,保證數據都被發送和接收了,但順序錯亂了。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。
socket.SOCK_SEQPACKET:可靠的連續數據包服務,已廢棄;

●proto即協議

0:與特定的地址家族相關的協議,如果是0(即零),則系統就會根據地址格式和套接類別,自動選擇一個合適的協議。(默認)


server.bind(address):將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以一個具有兩個元素的元組(host, port)的形式表示地址。host是客戶端所訪問的ip地址(服務端上的ip地址),註意ip地址要用引號引起來。port是客戶端訪問服務端上的端口號。

server.listen(backlog):開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。註意,backlog不包括正在通信的連接,是除正在通信的連接外,還能夠掛起的連接數量。並且,只有當服務端和連接通信交互後,listen設置的backlog才會生效;

conn, address = server.accept():接收連接並返回一個具有兩個元素的元組(conn, address),connect是客戶端連接上服務端後,在服務端上為其生成的連接對象,address是一個具有兩個元素的小元組(host, port),客戶端的ip地址與客戶端使用的端口號。接收TCP 客戶的連接(阻塞式)等待連接的到來;

data = conn.recv(bufsize, flags=None):接受套接字的數據。註意將對象改成conn(即客戶端連接上服務端後,在服務端為其生成的連接對象)後,就支持多個客戶端同時和一個服務端交互數據了。Python 2.X中數據以字符串類型返回,Python 3.X中數據以bytes類型返回。bufsize指定最多可以接收的數量,單位為字節。flags提供有關消息的其他信息,通常可以忽略。

conn.send(data, flags=None):將data中的數據發送到連接的套接字。註意將對象改成conn(即客戶端連接上服務端後,在服務端為其生成的連接對象)後,就支持多個客戶端同時和一個服務端交互數據了。Python 2.X中數據以字符串類型發送,Python 3.X中數據以bytes類型發送。返回發送的字節數量,該數量可能小於data的字節大小,即:可能未將指定內容全部發送。

註意,用send發送空內容會卡住;

因為對端會設置最大能接收的數據量,當本端發送的數據量比對端設置接收的數據量要大時,本端就會出現在一個發送動作中無法將全部數據發送出去,剩下部分的數據會存在網絡發送接口緩存區中,等到下次再向對端發送數據時,優先發送被存起來的數據;

當對端把接收數據量設置很大後,這時發送數據量的瓶頸是send的緩存區,不同系統的緩存區大小不一樣,Linux是32768字節,即在linux系統上通過send每次最多只能發送32768字節的數據量。


server.close():關閉套接字;


②客戶端,下面的語法是按照先後順序來寫的

client = socket.socket():聲明socket類型,同時生成socket連接對象。由於family默認是AF_INET,type默認是SOCK_STREAM,所以可以不用再聲明。

client.connect(address):連接到address處的套接字。address為一個具有兩個元素的元組元組(host, port),host是客戶端所訪問的ip地址(服務端上的ip地址),註意ip地址要用引號引起來。port是客戶端訪問服務端上的端口號。如果連接出錯,返回socket.error錯誤。

client.send(data, flags=None):將data中的數據發送到連接的套接字。Python 2.X中數據以字符串類型發送,Python 3.X中數據以bytes類型發送。返回發送的字節數量,該數量可能小於data的字節大小,即:可能未將指定內容全部發送。註意,用send發送空內容會卡住;

因為對端會設置最大能接收的數據量,當本端發送的數據量比對端設置接收的數據量要大時,本端就會出現在一個發送動作中無法將全部數據發送出去,剩下部分的數據會存在網絡發送接口緩存區中,等到下次再向對端發送數據時,優先發送被存起來的數據;

當對端把接收數據量設置很大後,這時發送數據量的瓶頸是send的緩存區,不同系統的緩存區大小不一樣,Linux是32768字節,即在linux系統上通過send每次最多只能發送32768字節的數據量。

data = client.recv(bufsize, flags=None):接受套接字的數據。Python 2.X中數據以字符串類型返回,Python 3.X中數據以bytes類型返回。bufsize指定最多可以接收的數據大小,單位為字節。flags提供有關消息的其他信息,通常可以忽略。

client.close():關閉套接字;


3.實例

server:

import socket


server = socket.socket()  # 聲明socket類型,同時生成socket連接對象

server.bind(("localhost", 55555))  # 綁定客戶端訪問的ip地址和客戶端要訪問的端口

server.listen(5)  # 監聽,最多允許掛起5個連接
print("開始等待客戶端訪問的連接了")

while True:  # 能接收多個客戶端的連接

    # 接收客戶端訪問的連接
    # connect是客戶端連接上服務器端後,在服務端為其生成的連接對象
    # address是一個元組,客戶端ip地址與客戶端使用的端口號
    connect, address = server.accept()
    print("接收到客戶端訪問的連接了")

    while True:  # 能和一個連接通信多次
        data = connect.recv(1024)  # 接收客戶端發送的數據,一次最多可以接收1024字節的數據

        # 當接收的內容為空就退出,在Unix/Linux/OSX系統上當連接斷開後,服務端會一直接收空內容,在Windows上會報錯
        if not data:  
            print("客戶端發送的數據為空")
            break

        data = data.decode(encoding="utf-8")  # 從bytes類型轉換成str類型
        print("客戶端發送的數據為: %s" % data)

        new_data = "成功接收消息:" + data
        connect.send(new_data.encode(encoding="utf-8"))  # 響應客戶端

server.close()


client:

import socket


client = socket.socket()  # 聲明socket類型,同時生成socket連接對象

client.connect(("localhost", 55555))  # 連接服務端的ip地址和要訪問服務端的服務端口

while True:  # 能向服務端發送多次消息

    message = input("輸入你想要發送的消息\n>>>").strip()

    if len(message) == 0:  # 防止輸入的內容為空,因為send發送空內容會卡住
        continue

    # client.send(b"abc")  # 只能發送bytes類型。bytes方法只能轉換ascii裏面的數據
    client.send(message.encode(encoding="utf-8"))

    data = client.recv(1024)  # 接收服務器端響應的數據,一次最多可以接收1024字節的數據
    print("服務端響應的數據為: %s" % data.decode(encoding="utf-8"))

client.close()


本文出自 “12031302” 博客,謝絕轉載!

Python基礎-第七天-面向對象編程進階和Socket編程簡介