類與對象

 

1、修改實例的字符串表示編程

  經過定義__str__()和__repr__()方法來實現。緩存

  特殊方法__repr__()返回的是實例的代碼表示(code representation)能夠用它返回字符串文原本從新建立這個實例。網絡

  特殊方法__str__()將實例轉換爲一個字符串。數據結構

  特殊的格式化代碼!r表示應該使用__repr__()輸出,而不是默認的__str__()。app

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

    def __repr__(self):
        return 'Pair({0.x!r}, {0.y!r})'.format(self)

    def __str__(self):
        return '({0.x!s}, {0.y!s})'.format(self)

  對於__repr__(),標準的作法是讓它產生的字符串文本可以知足eval(repr(x)) == x。框架

  若是沒有定義__str__(),那麼就用__repr__()的輸出看成備份。socket

  

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

  __format__()方法在Python的字符串格式化功能中提供了一個鉤子。對格式化代碼的解釋徹底取決於類自己。函數

_formas = {
    '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 = _formas[code].format(d=self)
        return fmt

>>> d = Date(2019, 9, 17)
>>>format(d)
2019-9-17
>>> 'The date is {:ymd}'.format(d)
The date is 2019-9-17

 

3、支持上下文管理協議測試

  最經常使用在須要管理相似文件、網絡鏈接和鎖這樣的資源的程序中。它們必須顯式地進行關閉或者釋放才能正確工做。

  讓對象支持上下文管理協議(context-management protocol),經過with語句觸發。

  要讓對象可以兼容with語句,須要實現__enter__()和__exit__()方法。

  主要原則:編寫的代碼須要包含在由with語句定義的代碼塊中。

  當遇到with語句時,__enter__()方法首先被觸發執行。__enter__()的返回值(若是有的話)被放置在由as限定的變量當中。

  以後開始執行with代碼塊中的語句。最後__exit__()方法被觸發來執行清理工做。

  __exit__()方法的三個參數包含了異常類型、值、和對掛起異常的追溯。

  __exit__()返回True,異常就會被清理乾淨,好像什麼也沒有發生過同樣,而程序也會馬上繼續執行with語句塊以後的代碼。

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.connections = []

    def __enter__(self):
        sock = socket(self.family, self.type)
        sock.connect(self.address)
        self.connections.append(sock)
        return sock

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.connections.pop().close()

  

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

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

  當定義了__slots__屬性時,Python就會針對實例採用一種更加緊湊的內部表示。再也不讓每一個實例都建立一個__dict__字典。

  使用__slots__帶來的反作用是咱們無法再對實例添加任何新的屬性了。

  定義了__slots__屬性的類不支持某些特定的功能,好比多重繼承。

 

5、將名稱封裝到類中

  想將私有數據封裝到類的實例上,可是有須要考慮到Python缺少對屬性的訪問控制問題。

  經過特定的命名規則來表達出對數據和方法的用途。

  第一個規則是:任何如下劃線(_)開頭的名字應該老是被認爲只屬於內部實現。

  第二個規則:以雙下劃線(__)開頭的名稱會致使出現名字重整(name mangling)的行爲。

    類中的私有屬性會被分別重命名爲_A__private和_A__private_method。爲了繼承,這樣的屬性不能經過繼承而覆蓋。

  第三個規則:定義一個變量可是名稱可能會和保留字產生衝突。應該在名稱最後加上一個單下劃線以示區別,如lambda_。

 

6、建立可管理的屬性

  property的所有意義在於咱們設置屬性時能夠執行類型檢查。

  不要編寫定義了大量重複性property的代碼。

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

    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

p = Person('David')

 

7、調用父類中的方法

  調用父類(超類)中的方法,可使用super()函數完成。

  super()函數的一種常見用途是調用父類的__init__()方法,確保父類被正確地初始化了。

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

  要實現繼承,Python從MRO列表中最左邊的類開始,從左到右一次查找,直到找到待查的屬性時爲止。

  當使用super()函數時,Python會繼續從MRO中的下一個類開始搜索。只要每個從新定義過的方法都使用了super(),而且只調用了它一次,那麼控制流最終就能夠便利整個MRO列表,而且讓每一個方法只會被調用一次。

  super()並非必定要關聯到某個類的直接父類上,甚至能夠在沒有直接父類中使用它。

  經常會在定義混合類(Mixin class)時以這種方式使用super()。

  首先,確保在繼承體系中全部同名的方法都有可兼容的調用簽名(參數數量相同,參數名稱也相同)。

class A:
    def spam(self):
        print('A.spam')
        super().spam()

class B:
    def spam(self):
        print('B.spam')

class C(A,B):
    pass

>>> c = C()
>>> c.spam()
A.spam
B.spam
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

 

8、在子類中擴展屬性

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

class SubPerson(Person):
    @Person.name.getter
    def name(self):
        print('Getting name')
        return super().name()

# 或者只想擴展setter
class SubPerson(Person):
    @Person.name.setter
    def name(self, value):
        print('Setting name to', value)
        return super(SubPerson, SubPerson).name.__set__(self,value)

  在setter函數中,對super(Subperson,SubPerson).name.__set__(self, value)的調用,須要把控制流傳遞到以前定義的name屬性的__set__()方法中去,

  可是惟一能調用到這個方法的方式就是以類變量而不是實例變量的方式去訪問。

 

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

  建立一種新形式的實例屬性,擁有額外的功能,好比類型檢查。以描述符的形式定義其功能。

  描述符只能在類的層次上定義,不能根據實例來產生。

  (基於類裝飾器的描述符例子)

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
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected' + str(self.expected_type))
        instance.__dict__[self.name] = value

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

 

10、讓屬性具備惰性求值的能力

  當把描述符放到類的定義體中時,訪問它的屬性會觸發__get__()、__set__()和__delete__()方法獲得執行。

  只有當被訪問的屬性不在底層的實例字典中時,__get__()方法纔會獲得調用。

class lazyproperty:

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

    def __get__(self, instance, owner):
        print(self,instance,owner)
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)
            return value

import math
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

 

11、簡化數據結構的初始化過程

  將初始化數據結構的步驟概括到一個單獨的__init__()函數中,將其定義在一個公共的基類中。

class Structure:
    _fields = []

    def __init__(self, *args, **kwargs):

        if len(args) > len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))
            
        if kwargs:
            raise TypeError('Duplicate values for {}'.format(','.join(kwargs)))

class Stock(Structure):

    _fields = ['name', 'shares', 'price']

  利用關鍵字參數來給類添加額外的屬性,這些額外的屬性是沒有定義在_fields中的。

class Structure:
    _fields = []

    def __init__(self, *args, **kwargs):

        if len(args) > len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        extra_args = kwargs.keys() - self._fields

        for name in extra_args:
            setattr(self, name, kwargs.pop(name))

        if kwargs:
            raise TypeError('Duplicate values for {}'.format(','.join(kwargs)))

class Stock(Structure):

    _fields = ['name', 'shares', 'price']

s1 = Stock('Acme',50,91.2)
s1 = Stock('Acme',50,91.2,date = '2/8/2019')

  利用frame hack技巧實現自動化實例變量初始化處理,編寫一個功能函數(這種方法相對慢50%)

def init_fromlocals(self):
    import sys
    locs = sys._getframe(1).f_locals
    for k,v in locs.items():
        if k != 'self':
            setattr(self, k, v)

class Stock:
    def __init__(self, name, shares, prices):
        init_fromlocals(self)

 

12、定義一個接口或抽象基類

  定義一個類做爲接口或者是抽象基類,這樣能夠在此之上執行類型檢查並確保在子類中實現特定的方法。

  抽象基類的核心特徵是:不能被直接實例化。

  抽象基類是給其餘的類當作基類使用的,這些子類須要實現基類中要求的那些方法。主要用途是:強制規定所需的編程接口。

  @abstractmethod一樣能夠施加到靜態方法、類方法和property屬性上。@abstractmethod要緊挨這函數定義。

from abc import abstractmethod,ABCMeta

class IStream(metaclass=ABCMeta):
    @abstractmethod
    def read(self, maxbytes=-1):
        pass
    @abstractmethod
    def write(self, data):
        pass

  抽象基類也容許其餘的類向其註冊,而後實現所需的接口

import io
IStream.register(io.IOBase)
f
= open('1.txt') isinstance(f, IStream) # True

 

十3、實現一種數據模型或類型系統

  對某些實例屬性賦值時進行檢查。 自定義屬性賦值函數,這種狀況下最好使用描述器。

# Base class. Uses a descriptor to set a value
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


# Descriptor for enforcing types
class Typed(Descriptor):
    expected_type = type(None)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('expected ' + str(self.expected_type))
        super().__set__(instance, value)


# Descriptor for enforcing values
class Unsigned(Descriptor):
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super().__set__(instance, value)


class MaxSized(Descriptor):
    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super().__init__(name, **opts)

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super().__set__(instance, value)
系統類型和賦值驗證框架
class Integer(Typed):
    expected_type = int

class UnsignedInteger(Integer, Unsigned):
    pass

class Float(Typed):
    expected_type = float

class UnsignedFloat(Float, Unsigned):
    pass

class String(Typed):
    expected_type = str

class SizedString(String, MaxSized):
    pass
定義的各類不一樣的數據類型

  測試類:

class Stock:
    # Specify constraints
    name = SizedString('name', size=8)
    shares = UnsignedInteger('shares')
    price = UnsignedFloat('price')

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

  (1)使用裝飾器簡化代碼

def check_attributes(**kwargs):
    def decorate(cls):

        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                setattr(cls, key, value)
            else:
                setattr(cls, key, value(key))

        return cls
    return decorate

@check_attributes(name=SizedString(size=8),
                  shares=UnsignedInteger,
                  price=UnsignedFloat)
class Stock:
    def __init__(self, name, shares, prices):
        self.name = name
        self.shares = shares
        self.prices = prices

  (2)使用元類簡化代碼:

class checkedmeta(type):

    def __new__(cls, clsname, bases, methods):

        for key, value in methods.items():
            if isinstance(value, Descriptor):
                value.name = key
        return type.__new__(cls, clsname, bases, methods)

class Stock2(metaclass=checkedmeta):
    name = SizedString(size=8)
    shares = UnsignedInteger()
    price = UnsignedFloat()

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

  (3)類裝飾器備選方案:

# Decorator for applying type checking
def Typed(expected_type, cls=None):
    if cls is None:
        return lambda cls: Typed(expected_type, cls)
    super_set = cls.__set__

    def __set__(self, instance, value):
        if not isinstance(value, expected_type):
            raise TypeError('expected ' + str(expected_type))
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Decorator for unsigned values
def Unsigned(cls):
    super_set = cls.__set__

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Decorator for allowing sized values
def MaxSized(cls):
    super_init = cls.__init__

    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super_init(self, name, **opts)

    cls.__init__ = __init__

    super_set = cls.__set__

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Specialized descriptors
@Typed(int)
class Integer(Descriptor):
    pass


@Unsigned
class UnsignedInteger(Integer):
    pass


@Typed(float)
class Float(Descriptor):
    pass


@Unsigned
class UnsignedFloat(Float):
    pass


@Typed(str)
class String(Descriptor):
    pass


@MaxSized
class SizedString(String):
    pass
View Code

 

十4、實現自定義的容器

  實現一個自定義的類,用來模仿普通的內建容器類型好比列表或者字典的行爲。

  collections 定義了不少抽象基類,當你想自定義容器類的時候它們會很是有用。

  包括Sequence、MutableSequence、Mapping、MutableMapping、Set以及MutableSet

  這些類中有許多事按照功能層次的遞增來排列的如:Container、Iterable、Sized、Sqeuence以及MutableSequence。

  這樣對這些類進行實例化操做,就能夠知道須要實現哪些方法才能讓自定義的容器具備相同的行爲:

>>> import collections
>>> collections.Sequence()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Sequence with abstract methods \
__getitem__, __len__

  例子:建立一個 Sequence類,元素老是一排序後的順序進行存儲:

class SortedItems(collections.Sequence):
    def __init__(self, initial=None):
        self._items = sorted(initial) if initial is not None else []

    # Required sequence methods
    def __getitem__(self, index):
        return self._items[index]

    def __len__(self):
        return len(self._items)

    # Method for adding an item in the right location
    def add(self, item):
        bisect.insort(self._items, item)

  SortedItems的實例表現出的行爲和普通序列對象徹底同樣。支持全部常見的操做索引、迭代、len()、是否包含(in操做符)甚至是分片

  collections庫中提供的抽象基類,便於咱們作類型檢查。

>>> items = SortedItems()
>>> import collections
>>> isinstance(items, collections.Iterable)
True
>>> isinstance(items, collections.Sequence)
True
>>> isinstance(items, collections.Container)
True
>>> isinstance(items, collections.Sized)
True
>>> isinstance(items, collections.Mapping)
False

  例子:建立一個Items類,支持列表全部核心方法,如append()、remove()、count()等。

class Items(collections.MutableSequence):
    def __init__(self, initial=None):
        self._items = list(initial) if initial is not None else []

    # Required sequence methods
    def __getitem__(self, index):
        print('Getting:', index)
        return self._items[index]

    def __setitem__(self, index, value):
        print('Setting:', index, value)
        self._items[index] = value

    def __delitem__(self, index):
        print('Deleting:', index)
        del self._items[index]

    def insert(self, index, value):
        print('Inserting:', index, value)
        self._items.insert(index, value)

    def __len__(self):
        print('Len')
        return len(self._items)

 

十5、委託屬性的訪問

  訪問實例的屬性時可以將其委託(delegate)到一個內部持有的對象上。

  將某個特定的操做轉交給另外一個不一樣的對象實現。

class A:
    def spam(self, x):
        pass

    def foo(self):
        pass

class B:
    """使用__getattr__的代理,代理方法比較多時候"""

    def __init__(self):
        self._a = A()

    def bar(self):
        pass

    # Expose all of the methods defined on class A
    def __getattr__(self, name):
        """這個方法在訪問的attribute不存在的時候被調用
        the __getattr__() method is actually a fallback method
        that only gets called when an attribute is not found"""
        return getattr(self._a, name)

  上述例子,在訪問B中未定義的方法時就能把這個操做委託給A。

  實現代理例子:

# A proxy class that wraps around another object, but
# exposes its public attributes
class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        print('getattr:', name)
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            print('setattr:', name, value)
            setattr(self._obj, name, value)

    # Delegate attribute deletion
    def __delattr__(self, name):
        if name.startswith('_'):
            super().__delattr__(name)
        else:
            print('delattr:', name)
            delattr(self._obj, name)

class Spam:
    def __init__(self, x):
        self.x = x

    def bar(self, y):
        print('Spam.bar:', self.x, y)

# Create an instance
s = Spam(2)
# Create a proxy around it
p = Proxy(s)
# Access the proxy
print(p.x)  # Outputs 2
p.bar(3)  # Outputs "Spam.bar: 2 3"
p.x = 37  # Changes s.x to 37

  利用委託替代繼承:

class A:
    def spam(self, x):
        print('A.spam', x)
    def foo(self):
        print('A.foo')

class B:
    def __init__(self):
        self._a = A()
    def spam(self, x):
        print('B.spam', x)
        self._a.spam(x)
    def bar(self):
        print('B.bar')
    def __getattr__(self, name):
        return getattr(self._a, name)

  首先,__getattr__()其實是一個回滾方法,只會在某個屬性沒有找到的時候纔會調用。

  所以,若是訪問的是代理實例自己的屬性,這個方法就不會被觸發調用。

  其次,__setattr__()和__delattr__()方法須要添加一點額外的邏輯來區分代理實例自己的屬性和內部對象_obj上的屬性。

  同時,__getattr__()方法不適用於大部分名稱以雙下劃線開頭和結尾的特殊方法,須要本身手動實現。

class ListLike:
    """__getattr__對於雙下劃線開始和結尾的方法是不能用的,須要一個個去重定義"""

    def __init__(self):
        self._items = []

    def __getattr__(self, name):
        return getattr(self._items, name)

    # Added special methods to support certain list operations
    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        self._items[index] = value

    def __delitem__(self, index):
        del self._items[index]

 

十6、類中定義多個構造函數

  可以以多種方式建立實例,而不侷限於__init__()。

  要定義一個含有多個構造函數的類,應該使用類方法。

  類方法的一個關鍵特性就是把類做爲其接收的第一個參數(cls)。

import time

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

    @classmethod
    def today(cls):
        t = time.localtime()
        return cls(t.tm_year, t.tm_mon, t.tm_mday)

a = Date(2012, 12, 21) # Primary
b = Date.today() # Alternate

 

十7、不經過調用__init__來建立實例

  直接調用類的__new__()方法來建立一個未初始化的實例

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

>>> d = Date.__new__(Date)
>>> d
<__main__.Date object at 0x1006716d0>
>>> d.year
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AttributeError: 'Date' object has no attribute 'year'

  給實例變量設定合適的初始值

>>> data = {'year':2012, 'month':8, 'day':29}
>>> for key, value in data.items():
...     setattr(d, key, value)
...
>>> d.year
2012
>>> d.month
8

 

十8、Mixin技術來擴展類定義

  有一些十分有用的方法,但願用它們來擴展其餘類的功能。可是,須要添加的這些類之間並不必定屬於繼承關係。所以,無法將這些方法直接關聯到一個共同的基類上。

  如:日誌記錄、類型檢查等Mixin類,

class LoggedMappingMixin:
    """
    Add logging to get/set/delete operations for debugging.
    """
    __slots__ = ()  # 混入類都沒有實例變量,由於直接實例化混入類沒有任何意義

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return super().__setitem__(key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return super().__delitem__(key)

class SetOnceMappingMixin:
    '''
    Only allow a key to be set once.
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(str(key) + ' already set')
        return super().__setitem__(key, value)

class StringKeysMappingMixin:
    '''
    Restrict keys to strings only
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if not isinstance(key, str):
            raise TypeError('keys must be strings')
        return super().__setitem__(key, value)

  這些類存在的意義就是要和其餘映射型類經過多重繼承的方式混合在一塊兒使用。

class LoggedDict(LoggedMappingMixin, dict):
    pass

d = LoggedDict()
d['x'] = 23
print(d['x'])
del d['x']

from collections import defaultdict
class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
    pass

d = SetOnceDefaultDict(list)
d['x'].append(2)
d['x'].append(3)
# d['x'] = 23  # KeyError: 'x already set'

  mixin類和其餘已有的類結合在一塊兒。當他們混合在一塊兒時,全部的類經過一塊兒工做提供所需的功能。

  mixin類通常來講是沒有狀態的。意味着mixin類沒有__init__()方法,也沒有實例變量,沒有屬於本身的實例數據。

  經過使用super(),將這個任務轉交給方法解析順序MRO上的下一個類。

  實現mixin的另外一種方法是利用類裝飾器。

def LoggedMapping(cls):
    """第二種方式:使用類裝飾器"""
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__
    return cls


@LoggedMapping
class LoggedDict(dict):
    pass

 

十9、帶有狀態的對象或狀態機

  爲每個狀態定義一個單獨的類。

class Connection1:
    """新方案——對每一個狀態定義一個類"""

    def __init__(self):
        self.new_state(ClosedConnectionState)

    def new_state(self, newstate):
        self._state = newstate
        # Delegate to the state class

    def read(self):
        return self._state.read(self)

    def write(self, data):
        return self._state.write(self, data)

    def open(self):
        return self._state.open(self)

    def close(self):
        return self._state.close(self)


# Connection state base class
class ConnectionState:
    @staticmethod
    def read(conn):
        raise NotImplementedError()

    @staticmethod
    def write(conn, data):
        raise NotImplementedError()

    @staticmethod
    def open(conn):
        raise NotImplementedError()

    @staticmethod
    def close(conn):
        raise NotImplementedError()


# Implementation of different states
class ClosedConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        raise RuntimeError('Not open')

    @staticmethod
    def write(conn, data):
        raise RuntimeError('Not open')

    @staticmethod
    def open(conn):
        conn.new_state(OpenConnectionState)

    @staticmethod
    def close(conn):
        raise RuntimeError('Already closed')


class OpenConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        print('reading')

    @staticmethod
    def write(conn, data):
        print('writing')

    @staticmethod
    def open(conn):
        raise RuntimeError('Already open')

    @staticmethod
    def close(conn):
        conn.new_state(ClosedConnectionState)

  使用演示:

>>> c = Connection()
>>> c._state
<class '__main__.ClosedConnectionState'>
>>> c.read()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "example.py", line 10, in read
        return self._state.read(self)
    File "example.py", line 43, in read
        raise RuntimeError('Not open')
RuntimeError: Not open
>>> c.open()
>>> c._state
<class '__main__.OpenConnectionState'>
>>> c.read()
reading
>>> c.write('hello')
writing
>>> c.close()
>>> c._state
<class '__main__.ClosedConnectionState'>

 

二10、調用對象上的方法,方法名以字符串形式給出

  (1)使用getattr()

import math
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Point({!r:},{!r:})'.format(self.x, self.y)

    def distance(self, x, y):
        return math.hypot(self.x - x, self.y - y)

p = Point(2, 3)
d = getattr(p, 'distance')(0, 0)  # Calls p.distance(0, 0)

  (2)使用operator.methodcaller()

import operator
operator.methodcaller('distance', 0, 0)(p)

points = [
    Point(1, 2),
    Point(3, 0),
    Point(10, -3),
    Point(-5, -7),
    Point(-1, 8),
    Point(3, 2)
]
# Sort by distance from origin (0, 0)
points.sort(key=operator.methodcaller('distance', 0, 0))

  operator.methodcaller()建立了一個可調用對象,並且把所需的參數提供給了被調用的方法,提供適當的self參數便可。

 

二十4、讓類支持比較操做

  爲每種比較操做符實現一個特殊方法,Python中的類能夠支持比較操做。

  functools.total_ordering裝飾器能夠用來簡化這個過程。只需定義一個 __eq__() 方法, 外加其餘方法(__lt__, __le__, __gt__, or __ge__)中的一個便可。

from functools import total_ordering

class Room:
    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width
        self.square_feet = self.length * self.width

@total_ordering
class House:
    def __init__(self, name, style):
        self.name = name
        self.style = style
        self.rooms = list()

    @property
    def living_space_footage(self):
        return sum(r.square_feet for r in self.rooms)

    def add_room(self, room):
        self.rooms.append(room)

    def __str__(self):
        return '{}: {} square foot {}'.format(self.name,
                self.living_space_footage,
                self.style)

    def __eq__(self, other):
        return self.living_space_footage == other.living_space_footage

    def __lt__(self, other):
        return self.living_space_footage < other.living_space_footage

 

二十5、建立緩存實例

  建立類實例時想返回一個緩存引用,讓其指向上一個用一樣參數建立出的類實例。

  logging模塊中,給定的一個名稱只會關聯到一個單獨的logger實例。

>>> import logging
>>> a = logging.getLogger('foo')
>>> b = logging.getLogger('bar')
>>> a is b
False
>>> c = logging.getLogger('foo')
>>> a is c
True

  用一個於類自己相分離的工廠函數:

# The class in question
class Spam:
    def __init__(self, name):
        self.name = name

# Caching support
import weakref
_spam_cache = weakref.WeakValueDictionary()
def get_spam(name):
    if name not in _spam_cache:
        s = Spam(name)
        _spam_cache[name] = s
    else:
        s = _spam_cache[name]
    return s

  (2)修改__new__()方法來實現:

import weakref

class Spam:
    _spam_cache = weakref.WeakValueDictionary()
    def __new__(cls, name):
        if name in cls._spam_cache:
            return cls._spam_cache[name]
        else:
            self = super().__new__(cls)
            cls._spam_cache[name] = self
            return self
    def __init__(self, name):
        print('Initializing Spam')
        self.name = name

  (3)將緩存代碼放到另外一個單獨的管理類中。

import weakref

class CachedSpamManager:
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()

    def get_spam(self, name):
        if name not in self._cache:
            s = Spam(name)
            self._cache[name] = s
        else:
            s = self._cache[name]
        return s

    def clear(self):
            self._cache.clear()

class Spam:
    manager = CachedSpamManager()
    def __init__(self, name):
        self.name = name

    def get_spam(name):
        return Spam.manager.get_spam(name)

  (4)防止用戶直接實例化類,能夠將類名用_開頭,用類方法建立或者讓__init__()拋出異常。

相關文章
相關標籤/搜索