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

你可能感兴趣的:(pythoncookbook 第8章 类和对象)