类与对象

类与对象

改变对象字符串显示

  • repr() 会返回一个实例代码的表示形式
  • str() 使用str()或print()会调用此函数
  • 如果没有使用str就会使用repr代替输出
  • ({0.x!r}, {0.y!r}) 0指代了self本身
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!r}, {0.y!r})'.format(self)
out:
p = Pair(3, 4)
>>> p #repr
Pair(3, 4) 
>>> print (p) #str
(3, 4)

通过format()函数自定义字符串的格式化

_formats = {
    '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 = _formats[code]
        return fmt.format(d=self)
out:
d = date(2012, 12, 21)
>>> format(d)
'2012-12-21'
>>> format(d, 'mdy')
'12/21/2012'

让对象支持上下文管理器

  • 当出现with语句,entry方法会被处罚,返回值会被赋值给 as 后的s,结束后执行exit触发清除工作
  • 即使with中发生了一次,都会执行最后的清除工作,异常会被传递给exit()函数
  • exit可以自己处理异常 exc_ty:异常类型 exc_val:异常值 tb:追溯信息
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.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        self.sock = None

if __name__ == '__main__':
    from functools import partial
    conn = LazyConnection(('www.python.org', 80))
    with conn as s:
        s.send(b'GET /index.html HTTP/1.0\r\n')
        s.send(b'Host: www.python.org\r\n')
        s.send(b'\r\n')
        resp = b''.join(iter(partial(s.recv, 8192), b''))
        print (resp)

  • 嵌套使用with 并允许多个连接,使用一个list来存储连接
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 = list()

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

    def __exit__(self, exc_ty, exc_val, tb):
        self.connections.pop().close()

if __name__ == '__main__':
    from functools import partial
    conn = LazyConnection(('www.python.org', 80))
    with conn as s1:
        pass
        with conn as s2:
            pass

在类中对属性进行封装

  • python 不会去依赖语言特效和封装数据,而是遵循一定的属性和命名规则来达到这个效果,使用_单下划线来达到这个效果(类内部实现)
class A:
    def __init__(self):
        self._internal = 0
        self.public = 1

    def public_method(self):
        pass
    def _initernal_method(self):
        pass
  • 使用(__)双下划线命名变量或方法,函数或变量会变得不可访问,实际上,变量或函数只是被重命名了
  • 变量含函数会被重命名为_B__private _B__private_method 所以在外部无法访问
  • 在继承中,使用这种方法变量或方法不会被覆盖,本例中子类的属性重命名为_C__private _C__private_method
  • 大多数情况非公共名称以单下划线开头。如果使用继承,并且有些内部属性应该在子类中隐藏起来,使用双下划线方案。
class B:
    def __init__(self):
        self.__private = 0

    def __private_method(self):
        pass
    def public_mechod(self):
        pass
        self.__private_method()

class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1

    def __private_method(self):
        pass
  • 定义的变量和某个保留关键字冲突,使用单下划线后缀
lambda_ = 2.0

创建可管理的属性

  • 简单方法可以给方法定义一个property属性,想当于get方法。
  • 只有first_name属性被定义,setter 和 deleter才会被创建
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('Exceped a string')
        self._first_name = value

    @first_name.deleter
    def first_name(name):
        raise AttributeError("Can't delete attribute")
  • 访问时会自动触发setter getter deleter
  • 实际变量存储在_first_name中,在调用init 方法时会自动调用setter实施检查
  • 不要在没有任何检查的代码里使用property
>>> a = Person('Guido')
>>> a.first_name
'Guido'
>>> a.first_name = 'Bob'
>>> a.first_name
'Bob'

调用父类的方法

  • 使用super方法调用
class A:
    def spam(self):
        print ('A.spam')

class B(A):
    def spam(self):
        print ('B.spam')
        super().spam()
out:
>>> b = B()
>>> b.spam()
B.spam
A.spam
  • 使用super初始化父类属性
class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

简化数据结构的初始化

  • 很多class只用作存储数据,避免写很多的init()函数
  • 在基类中使用一个公共的构造函数,使用setattr为子类的参数赋值
import math
class Structure1:
    _fields = []
    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))
        for name, value in zip(self._fields, args):
            #self.name = value
            setattr(self, name, value)

class Stock(Structure1):
    _fields = ['name', 'shares', 'price']

class Point(Structure1):
    _fields = ['x', 'y']

class Circle(Structure1):
    _fields = ['radius']

    def area(self):
        return math.pi * self.radius ** 2
out:
>>> s = Stock('ACME', 50, 91.1)
>>> p = Point(2, 3)
>>> c = Circle('ACME', 50)
Traceback (most recent call last):
  File "", line 1, in 
    c = Circle('ACME', 50)
  File "/Users/gongyulei/code_example/python_example/cookbook/ch8/ch8_11.py", line 11, i
n __init__
    raise TypeError('Expected {} arguments'.format(len(self._fields)))
TypeError: Expected 1 arguments
>>> s.shares
50
  • 支持关键字参数作为传入参数
class Structure2:
    _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):
            #self.name = value
            setattr(self, name, value)

        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))

        if kwargs:
            raise TypeError('Invalid argument(s):{}'.format(',', join(kwargs)))

if __name__ == '__main__':
    class Stock(Structure2):
        _fields = ['name', 'shares', 'price']
    s1 = Stock('ACME', 50, 91.1)
    s2 = Stock('ACME', shares=91.1, price=91.1)
  • 在构造子类的实例时加入在fields中不存在的属性
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):
            #self.name = value
            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('Invalid argument(s):{}'.format(',', join(kwargs)))

if __name__ == '__main__':
    class Stock(Structure3):
            _fields = ['name', 'shares', 'price']

        s1 = Stock('ACME', 50, 91.1, date='8/2/2012')

实现自定义容器

  • 自定义类来模拟容器的功能,比如字典,列表。
  • 要实现的类继承至collections,并实现部分方法
  • 可以查看需要实现什么方法:len getitem
>>> import collections
>>> collections.Sequence()
Traceback (most recent call last):
  File "", line 1, in 
    collections.Sequence()
TypeError: Can't instantiate abstract class Sequence with abstract methods __getitem__, 
__len__
  • 实现实现了len 和 getitem方法,完成对class的迭代
import bisect
import collections

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

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

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

    def add(self, item):
        #在插入的时候进行排序
        bisect.insort(self._items, item)

if __name__ == '__main__':
    items = SortedItems([5, 1, 3])
    print (list(items))
    print (items[0], items[-1])
    items.add(2)
    print (list(items))
out:
[1, 3, 5]             
1 5                   
[1, 2, 3, 5]  
  • 可以使用isinstance实现类型检查
>>> from ch8_14 import SortedItems
>>> import collections
>>> items = SortedItems()
>>> isinstance(items, collections.Sequence)
True
  • collections 中很多抽象类会为一些常见容器操作提供默认的实现,只需要实现部分方法即可
class Items(collections.MutableSequence):
    def __init__(self, initial=None):
        self._items = list(initial) if initial is not None else []

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

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

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

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

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

out:
>>> a = Items([1, 2, 3])
>>> len(a)
Len
3
>>> a.append(4)
Len
Inserting 3 4
>>> a[0]
Getting: 0
1

属性的代理访问

  • 某个实例的属性访问代理到另一个实例中去,在另一个类中使用
  • 简单的代理
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):
        pass
  • 大量方法需要代理,使用getattr,在调用抛出attribute异常属性不存在时,会自动调用A对象的相关方法
class B2:
    def __init__(self):
        self._a = A()

    def bar(self):
        pass

    def __getattr__(self, name):
        #self._a.name
        return getattr(self._a, name)
out:
>>> from ch8_15 import B2
>>> b = B2()
>>> b.bar()
>>> b.spam(42)
  • 实现代理模式
  • getattr 实际上是一个后备方法,proxy不存在会调用此函数
  • setattr, delattr通过下划线区分代理实例和被代理实例的属性
class Proxy:
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        print ('getattr:', name)
        return getattr(self._obj, name)

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

    def __delattr__(self, name):
        if name.startswith('_'):
            super().__delattr__(name)
        else:
            print ('delattr:', name)
            setattr(self._obj, name)

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

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


if __name__ == '__main__':
    s = spam(2)
    p = Proxy(s)
    print (p.x)
    p.bar(3)
    p.x = 37

在类中定义多个构造器(实现init)

  • 类方法的一个主要用途就是定义多个构造器。它接受一个 class 作为第一个参数(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)
out:
>>> a = Date(2012, 12, 21)
>>> b = Date.today()
>>> b.day
30

不通过init方法初始化实例

  • 需要批量创建实例,可以使用这种方法
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

if __name__ == '__main__':
    data = {'year':2012, 'month':8, 'day':29}
    d = Date.__new__(Date)
    for key, value in data.items():
        setattr(d, key, value)

实现状态机,避免过多的条件判断

  • 定义每个状态类的基类
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)

  • 所有的状态实例存储在Connection中,通过动态的改变该实例状态,调用相关方法,自动实现状态控制
class Connection:

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

    def new_state(self, newstate):
        self._state = newstate

    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)


  • test
>>> c = Connection()
>>> c._state

>>> c.read()
Traceback (most recent call last):
  File "", line 1, in 
    c.read()
  File "/Users/gongyulei/code_example/python_example/cookbook/ch8/ch8_19.py", line 16, i
n read
    return self._state.read(self)
  File "/Users/gongyulei/code_example/python_example/cookbook/ch8/ch8_19.py", line 47, i
n read
    raise RuntimeError('Not open')
RuntimeError: Not open
>>> c.open()
>>> c._state

>>> c.write('hello')
writing
>>> c.close()
>>> c._state

通过字符串调用对象的方法

  • 使用getattr
import math
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance(self, x, y):
        return math.hypot(self.x - x, self.y - y)
out:
>>> p = Point(2, 3)
>>> d = getattr(p, 'distance')(0, 0)
>>> d
3.605551275463989
>>> d = getattr(p, 'distance')
>>> d
>

你可能感兴趣的:(类与对象)