pythoncookbook 第8章 類和對象

第8章 類和對象

8.1 __str__, __repr__

__repr__ goal is to be unambiguous
__str__ goal is to be readable
__str__

是將對象可讀,

repr(...)
    repr(object) -> string
    Return the canonical string representation of the object.
    For most object types, eval(repr(object)) == object.

是將對象變為字符,可以通過eval轉換過來

8.2 自定義format()格式化

8.3 with的上下文處理器

上下文協議:
使用with時

with some_function()  as s:
    #dosomething

先調 some_function()的enter的代碼

然后 #dosomething
結束后再調用exit

8.4 創建大量對象時節省內存方法

程序需要創建大量(上百萬)對象時,可以對簡單的對象經行限制屬性

class Date:
    __slots__ = ['year', 'month', 'day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

以Date威力在 64 位的 Python 上面要占用 428 字節,而如果
使用了 slots,內存占用下降到 156 字節

8.5 類中封裝屬性名

“私有”化變量

  • 1 "單下劃線" 開始的成員變量叫做保護變量,意思是只有類對象和子類對象自己能訪問到這些變量;
  • 2 "雙下劃線" 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。
class A(object):
       def __init__(self):
              self.__private()
              self.public()
       def __private(self):
              print 'A.__private()'
       def public(self):
              print 'A.public()'
class B(A):
       def __private(self):
              print 'B.__private()'
       def public(self):
              print 'B.public()'
       def _public(self):
              print 'B.public()' 
b = B()
結果:
A.__private()
B.public()

不推薦的訪問方法

b._public()
b._B____private()  # ._ClassName__funct()

8.6 創建可管理的屬性

將類的方法變成屬性使用
參考最黑魔法之一 描述器.md

class Person:
    def __init__(self, first_name):
        self.first_name = first_name
# Getter function
    @property
    def first_name(self):
        # do something
        return self._first_name
# Setter function
    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
                raise TypeError('Expected a string')
        self._first_name = value
# Deleter function (optional)
    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

# Make a property from existing get/set methods
# name = property(get_first_name, set_first_name, del_first_name)

8.7 調用父類方法super()(重要)

基本使用方法super(ClassName, self).init(val1,val2)
寫在前面,super() 其實并不是調用用父類的,而是是調用繼承mro鏈上的下一個對象的方法.

class Proxy(object):
    def __init__(self, obj):
        self._obj = obj
#方法不存在時起作用
    def __getattr__(self, name):
        return getattr(self._obj, name)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super(Proxy, self).__setattr__(name, value)  # 調用原先的serattr方法
        else:
            setattr(self._obj, name, value)


class Test(object):
    pass

test = Test()

p = Proxy(test)
p.func = lambda x: x+1
p._func = lambda x: x+2

print test.func(2)
print p._func(3)

遇到普通方法時,將方法付給 self.object()
遇到特殊方法_開頭時,將方法付給self

class Root(object):
    def __init__(self):
        print("this is Root")
 
class B(Root):
    def __init__(self):
        print("enter B")
        # print(self)  # 注意這里的 self 是 D 的 instance 而不是 B 的
        super(B, self).__init__()
        print("leave B")
 
class C(Root):
    def __init__(self):
        print("enter C")
        # print(self) # 注意這里的 self 是 D 的 instance 而不是 C 的
        super(C, self).__init__()
        print("leave C")
 
class D(B, C):
    pass
 
d = D()
 #注意觀察調用順序
# enter B
# enter C
# this is Root
# leave C
# leave B

super() 本質為類,可用一個簡單的函數解釋

# def super(cls, inst):
#     mro = inst.__class__.mro()
#     return mro[mro.index(cls) + 1]

用當前的類,定位mro鏈.調用mro鏈上的下一個對象.

tips:

  • 1 注意在繼承中的self 為實例的self.即為d
  • 2 mro基本順序原則 :基類永遠出現在派生類后面,如果有多個基類,基類的相對順序保持不變。

http://hanjianwei.com/2013/07/25/python-mro/

下面是對于self,cls的簡單解釋

  • function就是可以通過名字可以調用的一段代碼,我們可以傳參數進去,得到返回值。所有的參數都是明確的傳遞過去的。
  • method是function與對象的結合。我們調用一個方法的時候,有些參數(self)是隱含的傳遞過去的

**此外,什么是未綁定錯誤呢? **
$就是方法(函數)未得到一個實例,不能將其作為第一個參數傳入.即方法未綁定實例$

class Human(object):
    def __init__(self, weight):
         self.weight = weight
    def get_weight(self):
        return self.weight
 
Human.get_weight(Human(20))
person = Human(20)
person.get_weight()

# 1 instance method 就是實例對象與函數的結合。
# 2 使用類調用,第一個參數明確的傳遞過去一個實例。
#3 使用實例調用,調用的實例被作為第一個參數被隱含的傳遞過去。

class Human(object):
    weight = 12
    @classmethod  # 默認傳送的是cls 是類
    def get_weight(asd):
        print asd

# 1 classmethod 是類對象與函數的結合。
# 2 可以使用和類的實例調用,但是都是將類作為隱含參數傳遞過去。
# 3 使用類來調用 classmethod 可以避免將類實例化的開銷。

self ,cls 這些參數是約定,不是關鍵字. 約定(協議)將實例本身作為第一個參數,傳入(自動的).

https://www.zhihu.com/question/22869546

8.8子類中擴展property

只擴展父類的中的某個方法

class SubPerson(Person):
    @Person.name.getter
    def name(self):
        print('Getting name')
        return super().name
 
class SubPerson(Person):
    @Person.name.setter
    def name(self, value):
        print('Setting name to', value)
        super(SubPerson, SubPerson).name.__set__(self, value)

8.9 描述器進行類型檢查

自定義描述器,用裝飾器來設置
可以抄來用

# 定義描述器
class Typed:
 
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
 
    def __get__(self, instance, cls):
        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 ' + str(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():
            # 在stock類中添加類屬性,來作為描述器.攔截實例屬性
            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

8.10 使用延遲計算屬性

第一次調用,先用非資料描述器,去攔截實例對類的訪問。同時,計算,生成實例屬性。
第二次以后,實例屬性攔截,非資料描述器

class lazyproperty(object):
    def __init__(self, func):
        self.func = func # 原先的def area

    def __get__(self, instance, cls):
        #  self:類屬性 instance:c  cls :Crice()
        if instance is None:
            return self
        else:
            value = self.func(instance)  #方法的顯示調用 Class.func(instance)
            setattr(instance, self.func.__name__, value) # 將計算結果付于實例屬性 實例屬性攔截非資料描述器
            return value

import math
class Circle(object):

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

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

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

c = Circle(4)
print c.area
print c.area

8.11 簡化數據結構的初始化

當你需要使用大量很小的數據結構類的時候,相比手工一個個定義而已,使用這種方式可以大大簡化代碼。init () 方法

class Structure3:

    _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 = set(kwargs.keys()) - set(self._fields)
        for name in list(extra_args):
            setattr(self, name, kwargs.pop(name))
        if kwargs:
            raise TypeError('Duplicate values for {}'.format(','.join(kwargs)))

if __name__ == '__main__':
    class Stock(Structure3):
        _fields = ['name', 'shares', 'price']
    s1 = Stock('ACME', 50, 91.1)
    s2 = Stock('ACME', 50, 91.1, date='8/2/2012')

盡管這也可以正常工作,但是當定義子類的時候問題就來了。當一個子類定義了
slots 或者通過 property(或描述器) 來包裝某個屬性,那么直接訪問實例字典就不
起作用了。

class Structure:

    _fields= []
    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        self.__dict__.update(zip(self._fields,args))

8.12 定義接口或者抽象基類

就是在父類里面預先定義好了,幾個方法。
子類沒有重寫,就不能實例化
抽象基類不能直接實例化

from abc import ABCMeta,abstractmethod
clas IStream(object):
    __metaclass__ = ABCMeta
    @abstractmethod
    def read (self maxbtyes = -1):
        pass

    @abstractmethod
    def write(self, data):
        pass

注冊方式來讓某個類實現抽象基類

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

@abstractmethod 還能注解靜態方法、類方法和 properties 。你只需保證這個注
解緊靠在函數定義前即可:

class A(object):
    __metaclass__ = ABCMeta
    @property
    @abstractmethod
    def name(self):
    pass

collections 模塊定義了很多跟容器和迭代器 (序列、映射、集合等) 有關的抽象基類。 numbers 庫定義了跟數字對象 (整數、浮點數、有理數等) 有關的基類。io 庫定義了很多跟 I/O 操作相關的基類。

collections.Sequence
collections.Iterable
collections.Mapping

8.13 實現數據模型的類型約束

有點類似于orm
描述器有set就可以攔截實例了

class Descriptor(object):
    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(Typed, self).__set__(instance, value)


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


class MaxSized(Descriptor):

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

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super(MaxSized, self).__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(object):
    name = SizedString('name', size=8)  ##name 必須等于實例的name,用于實例的字典的賦值
    shares = UnsignedInteger('shares')
    price = UnsignedFloat('price')
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
        
s = Stock('jin',123,100.0)

代碼簡化,用裝飾器去實現類屬性生成,描述器

def check_attributes(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                value.name = key
                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, price):
        self.name = name
        self.shares = shares
        self.price = price

用元類實現代碼的簡化

class checkedmeta(type):
    def __new__(cls, clsname, bases, methods):
        for key, value in methods.items():
            if isinstance(value, Descriptor):
                value.name = key  ###value 為描述器的實例
    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

8.15 屬性的代理訪問

代理訪問:即為要訪問A的屬性,其實時訪問B的屬性

class A:
    def spam(self, x):
        pass
    def foo(self):
        pass
class B1:
    """ 簡單的代理 """
    def __init__(self):
        self._a = A()
    def spam(self, x):
        return self._a.spam(x)
    def foo(self):
        return self._a.foo()
    def bar(self):

當代理的屬性較多時,可以使用

def __getattr__(self, name):
""" 這個方法在訪問的 attribute 不存在的時候被調用
    return getattr(self._a, name)
__getattr__ # 的缺陷是不能查找'__'方法
需要手動的指向
def __len__(self):
    return len(self._items)

8.16 在類中定義多個構造器

就是在類里面定義一個類方法,
調用這個類方法再實例化一個對象出來

8.17 創建不調用 init 方法的實例

py2無法創建
type(類名,父類,屬性字典)
類名用來標記: 一般 類名= 變量名

Date.__new__(Date,'Date',(Date.__class__,),{}) 也是調用了type函數
type.__new__(Date,'Date',(Date.__class__,),{})  這樣Data必須時type類型的

8.18 利用 Mixins 擴展類功能

Mixins 通常是指具有單個功能的類,配合其他功能類使用
對于混入類,有幾點需要記住。首先是,混入類不能直接被實例化使用。其次,混
入類沒有自己的狀態信息,也就是說它們并沒有定義 init () 方法,并且沒有實例
屬性。這也是為什么我們在上面明確定義了 slots = () 。

8.19 實現狀態對象或者狀態機

少些一點狀態,用類來顯示。

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

    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)


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()


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()

8.20 字符串調用對象方法

方法一 getattr函數

d = getattr(p, 'distance')(0, 0)

方法二 operator.methodcaller

operator.methodcaller('distance', 0, 0)(p)
operator.methodcaller('distance', 0, 0)創建一個調用容器,然后把實例p放進去

8.21 訪問者模式(todo)

class HTTPHandler:
    def handle(self, request):
        methname = 'do_' + request.request_method
        getattr(self, methname)(request)
    def do_GET(self, request):
        pass
    def do_POST(self, request):
        pass
    def do_HEAD(self, request):
        pass

8.22 不用遞歸實現訪問者模式(todo)

8.23 循環引用數據結構的內存管理(todo)

8.24 讓類支持比較操作

裝飾器 functools.total_ordering 就是用來簡化這個處理的。 使用它來裝飾一個來,你只需定義一個 eq() 方法, 外加其他方法(lt, le, gt, or ge)中的一個即可。 然后裝飾器會自動為你填充其它比較方法。

8.25 緩存實例

類實例化時,同名實例只有一個。創建同名實例時,返還以前的實例。

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

當我們保持實例緩存時,你可能只想在程序中使用到它們時才保存。 一個 WeakValueDictionary 實例只會保存那些在其它地方還在被使用的實例。 否則的話,只要實例不再被使用了,它就從字典中被移除了。觀察下下面的測試結果:

>>> a = get_spam('foo')
>>> b = get_spam('bar')
>>> c = get_spam('foo')
>>> list(_spam_cache)
['foo', 'bar']
>>> del a
>>> del c
>>> list(_spam_cache)
['bar']
>>> del b
>>> list(_spam_cache)

9.13小節中的元類實現的更優雅一點(使用了更高級的技術)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,186評論 6 13
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,973評論 6 342
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,789評論 0 9
  • 1 老劉,三十歲出頭,某IT公司資深老員工,典型的技術型選手,做得了前端,看得了匯編,知識淵博,思維活躍,用IT行...
    灰常出色閱讀 763評論 6 9