第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小节中的元类实现的更优雅一点(使用了更高级的技术)。