类与对象

类与对象

改变对象字符串显示

  • 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 "<input>", line 1, in <module>
    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 "<input>", line 1, in <module>
    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
<class 'ch8_19.ClosedConnectionState'>
>>> c.read()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    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
<class 'ch8_19.OpenConnectionState'>
>>> c.write('hello')
writing
>>> c.close()
>>> c._state
<class 'ch8_19.ClosedConnectionState'>

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

  • 使用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
<bound method Point.distance of <ch8_20.Point object at 0x109815438>>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容