第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基本順序原則 :基類永遠出現在派生類后面,如果有多個基類,基類的相對順序保持不變。
下面是對于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 這些參數是約定,不是關鍵字. 約定(協議)將實例本身作為第一個參數,傳入(自動的).
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小節中的元類實現的更優雅一點(使用了更高級的技術)。