cookbook_類與對象

1修改實例的字符串表示

能夠經過定義__str__()和__repr__()方法來實現python

class Pair:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __str__(self):
        return "(%s,%s)"%(self.x,self.y)

    def __repr__(self):
        return "Pair(%s,%s)"%(self.x,self.y)

p = Pair(2,5)
print(p)
print("p is {0!r}".format(p))

 

對於__repr__(),標準的方法是讓他產生的字符串文本可以知足eval(repr(x)) == x數組

__str__()則產生一段有意義的文本網絡

 

2自定義字符串的輸出格式

 

咱們想讓對象經過format()函數和字符串方法來支持自定義的輸出格式數據結構

要自定義字符串的輸出格式,能夠在類中定義__format__()方法 socket

_formats = {
    "ymd":"{d.year}-{d.month}-{d.day}",
    "mdy":"{d.month}/{d.day}/{d.year}",
    "dmy":"{d.day}/{d.month}/{d.year}"
}

class Date:
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

    def __format__(self,code):
        if code == "":
            code = "ymd"
        fmt = _formats[code]
        return fmt.format(d = self)

d = Date(2018,9,26)
print(format(d))
print(format(d,"dmy"))
print(format(d,"mdy"))

 

 

3讓對象支持上下文管理協議

 

咱們想讓對象支持上下文管理協議,便可以經過with語句觸發。函數

想讓對象支持上下文管理協議,對象需實現__enter__()和__exit__()方法,好比實現網絡鏈接的類。優化

from socket import socket,AF_INET,SOCK_STREAM

class LazyConnection:
    
    def __init__(self,address,family = AF_INET, type = SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError("Already connected")
        self.sock = socket(self.family,self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.sock.close()
        self.sock = None

conn = LazyConnection("www.baidu.com")

with conn as s:
    s.send(b'hahhahah')

 

 

4當建立大量實例時如何節省內存

當咱們的程序須要建立大量的實例(百萬級),這樣會佔用大量的內存。spa

#對於那些主要用做簡單數據結構的類,一般能夠在類定義中增長__slot__屬性,以此來大量減小對內存的使用。code

class Date:
    __slots__ = ["year","month","day"]

    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

 

當定義了__slots__屬性時,python會採用一種更加緊湊的內部表示,會將實例的屬性添加到一個小型數組裏,再也不爲每一個實例建立__dict__。orm

反作用是咱們不能爲實例添加新的屬性。是一種優化手段

 

5將名稱封裝到類中

在python中,以單下劃線_開頭的屬性被認爲是一種私有屬性

class A:
    def __init__(self):
        self._name = "jiaojianglong"
        self.age = 24

    def _internal_method(self):
        print("i am a internal method")


a = A()
print(a._name) #jiaojianglong

 

 

python並不會阻止訪問屬性,但編譯器不會作提示。若是強行訪問會被認爲是粗魯的。

在類的定義中也見到過雙下劃線__開頭的名稱,以雙下劃線開頭的名稱會致使出現名稱重組的行爲

class B:
    def __init__(self):
        self.__name = "jiaojianglong"

b = B()
# print(b.__name)#AttributeError: 'B' object has no attribute '__name'
print(b._B__name)#jiaojianglong

 

這樣的行爲是爲了繼承,以雙下劃線開頭的屬性不會被子類經過繼承而覆蓋。

class C(B):
    def __init__(self):
        super().__init__()

c = C()
print(c._B__name)#jiaojianglong

 

 

大部分狀況下咱們使用單下劃線,涉及到子類繼承覆蓋的問題時使用雙下劃線

當咱們想定義一個變量,可是名稱可能會與保留字段衝突,基於此,咱們在名稱後加一個單下劃線以示區別。lambda_

 

6建立可管理的屬性

#在對實例的獲取和設定上,咱們但願增長一些額外的處理過程。

class Person:
    def __init__(self,first_name):
        self.first_name = first_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self,value):
        if not isinstance(value,str):
            raise TypeError("Excepted a string")
        self._first_name = value



p = Person("jiao")
print(p.first_name)

 

在建立實例時,__inti__()中咱們將name賦值到self.first_name,實際會調用setter方法,因此name實際仍是儲存在self._first_name中

 

7調用父類中的方法

#咱們想調用一個父類中的方法,這個方法在子類中已經被覆蓋了。

class A:
    def spam(self):
        print("A.spam")

class B(A):
    def spam(self):
        print("B.spam")
        super().spam()

b = B().spam()#B.spam,A.spam

print(B.__mro__)#(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

 

 

爭對每個類,python都會計算出一個稱爲方法解析順序(MRO)的列表,MOR列表只是簡單的對全部的基類進行線性排列。

 

8在子類中擴展屬性

咱們想在子類中擴展某個屬性的功能,而這個屬性是在父類中定義的

 

9建立一種新形式的類屬性或實例屬性

若是想定義一種新形式的實例屬性,能夠以描述符的形式定義其功能。

class Integer():

    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value,int):
            raise TypeError("Expected an int")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


class Point:
    x = Integer("x")
    y = Integer("y")

    def __init__(self,x,y):
        self.x = x
        self.y = y

p = Point(2,3)
print(p.x)#2
p.y = 5
print(p.y)#5
# p.x = "a"#TypeError: Expected an int
print(Point.x)#<__main__.Integer object at 0x00000141E2ABB5F8>

 

__get__()方法看起來有些複雜的緣由是實例變量和類變量的區別,若是是類變量則簡單的返回描述符自己,若是是實例變量返回定義的值

 

關於描述符,常容易困惑的地方就是他們只能在類的層次上定義,不能根據實例來產生,下面的代碼是沒法工做的

class Point:

    def __init__(self,x,y):
        self.x = Integer("x")
        self.y = Integer("y")
        self.x = x
        self.y = y

p = Point(2,"c")
print(p.x)#2
print(p.y)#c

class Typed:
    def __init__(self,name,expected_type):
        self.name = name
        self.expected_type = expected_type


    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value,self.expected_type):
            raise TypeError("Expected %s"%self.expected_type)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

def typeassert(**kwargs):
    def decorate(cls):
        for name,expected_type in kwargs.items():
            setattr(cls,name,Typed(name,expected_type))
        return cls
    return decorate

@typeassert(name=str,shares = int,price=float)
class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

 

對於少許的定製仍是使用property簡單些,若是是大量的定製則使用描述符要簡單些

相關文章
相關標籤/搜索