第2章 类与对象深度问题与解决技巧

1.如何派生内置不可变类型并修改其实例化行为

我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素,例如:
IntTuple([2,-2,‘jr’,[‘x’,‘y’],4]) => (2,4)
如何继承内置tuple 实现 IntTuple

1.1 元组是python内置的不可变类型对象,我们通过重写元祖的 new 方法来实现上述功能

#通过继承元祖重写 __new__ 方法
class IntTuple(tuple):
    def __new__(cls, iterable):
        l = []
        for i in iterable:
            if isinstance(i, int) and i > 0:
                l.append(i)
        return super().__new__(cls, l)

if __name__ == '__main__':
    int_t = IntTuple([2,-2,'jr',['x','y'],4])
    print(int_t)

上面的例子可以简化为列表推导式:

class IntTuple(tuple):
    def __new__(cls, iterable):
        l = [i for i in iterable if isinstance(i, int) and i > 0]  #列表推导式
        return super().__new__(cls, l)

if __name__ == '__main__':
    int_t = IntTuple([2,-2,'jr',['x','y'],4])
    print(int_t)

上面代码返回值:
在这里插入图片描述

1.2 self 对象到底是由谁创建的?

通过这个例子说明1.1中为什么 return super().new(cls, l) 要返回 cls,因为这个cls就是 init 中的 self

class A(object):
    def __new__(cls, *args, **kwargs):
        print("A.__new__:", cls, args)
        return object.__new__(cls)
        # return super().__new__(cls)

    def __init__(self, *args):
        print('A.__init__', self, *args)


if __name__ == '__main__':
    a = A(1, 2)
    print('-------------------------------------')
    a = A.__new__(A, 1, 2)
    A.__init__(a, 1, 2)

说明:由返回值可知,a = A(1,2) 的实例化过程,相当于调用了
“a = A.new(A, 1, 2) 和
A.init(a, 1, 2)”
两条语句。

上面代码返回值:
在这里插入图片描述

1.3 列表推导式简介

下面通过一个列子简单介绍下列表推导式

l = []
for i in range(10):
    if i % 2 == 0:
        l.append(i)
print(l)
#下面列表推导式,实现上面同样的功能
l = [i for i in range(10) if i % 2 == 0]
print(l)

上面程序运行结果相同:
在这里插入图片描述

2.如何为创建大量实例节省内存

在游戏中,定义了玩家类player,每有一个在线玩家,在服务器内则有一个player的实例,当在线人数很多时,将产生大量实例(百万级)
解决方案:
定义类的__slots__属性,声明实例有哪些属性(关闭动态绑定)

例子:定义两个用户类,分析内存使用情况,p2比p1多定义了 slots

import sys
import tracemalloc
class Player1(object):
    def __init__(self, uid, uname, status = 0, level = 1):
        self.uid = uid
        self.uname = uname
        self.status = status
        self.level = level


class Player2(object):
    # 通过 __slots__可以禁用动态绑定
    __slots__ = ('uid', 'uname', 'status', 'level')

    def __init__(self, uid, uname, status=0, level=1):
        self.uid = uid
        self.uname = uname
        self.status = status
        self.level = level


p1 = Player1('0001', 'juran')
p2 = Player2('0002', 'campo')

2.1实例化上面两个类,比较两个类对象的dir()属性

通过dir()属性查看类对象属性
print(dir(p1))
print(dir(p2))
p1 比 p2 多两个属性
print(set(dir(p1))-set(dir(p2)))
通过比较得知,p1比p2多了两个属性:
__weakref__弱引用,__dict__动态绑定

2.2 p1,p2动态绑定变量

p1对象动态绑定变量示例

p1.x = 'marry'
p1.__dict__['y'] = 7
print(p1.__dict__)

上面代码返回值:
{‘uid’: ‘0001’, ‘uname’: ‘juran’, ‘status’: 0, ‘level’: 1, ‘x’: ‘marry’, ‘y’: 7}

因为p2的魔态方法 slots ,禁用动态绑定,所以 p2.x = 7 系统报错:
AttributeError: ‘player2’ object has no attribute ‘dict

2.3 查看p1各个属性的资源使用情况

查看p1内存资源使用情况

print('__dict__使用内存:',sys.getsizeof(p1.__dict__))
print('   uname使用内存:',sys.getsizeof(p1.uname))
print('     uid使用内存:',sys.getsizeof(p1.uid))
print('  status使用内存:',sys.getsizeof(p1.status))
print('   level使用内存:',sys.getsizeof(p1.level))

返回值:

__dict__使用内存:152
   uname使用内存: 54
     uid使用内存: 53
  status使用内存: 24
   level使用内存: 28

通过观察内存占用量可以看出,动态绑定占用了相对多的内存,dict 动态绑定属性占用内存最高 152
创建大量实例时,禁用动态绑定,可以很好的提高性能。

2.4 也可以这样看内存使用情况

导入 tracemalloc 查看内存使用情况

import tracemalloc
tracemalloc.start()
# p1 = [player1(1, 'juran', 3) for _ in range(100000)]
p2 = [player2(1, 2, 3) for _ in range(100000)]
end = tracemalloc.take_snapshot()
top = end.statistics('filename')
# top = end.statistics('lineno')

for stat in top[:100]:
    print(stat)

p1内存使用情况:
py:0: size=16.8 MiB, count=299994, average=59 B
p2内存使用情况:
py:0: size=7837 KiB, count=100002, average=80 B
结论:
Player2 通过设置 slots = (‘uid’, ‘uname’, ‘status’, ‘level’) 禁用动态绑定 dict,大大降低了内存的使用情况,

3.Python中的with语句

3.1 打开一个文件的完整处理流程

先来看一下try … except … finally 如何处理文件的

try:
    f = open('test.txt','r')
    raise IndexError
except KeyError as e:
    print('keyError')
    f.close()
except IndexError as e:
    print('IndexError')
    f.close()
except Exception as e:
    print(e)
    f.close()
finally:
    #有无异常,总是执行
    print('end')
    f.close()

上面的过程可以用with来实现,

with open('test.txt','r',encoding='utf-8') as f:
    content = f.read()
    print(content)

3.2 with 和上下文管理器

3.2.1 通过 enterexit 简化文件处理流程

class Sample(object):
    #获取资源
    def __enter__(self):
        print('start')
        return self

    def demo(self):
        print('this is demo')

    #释放资源
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('end')


with Sample() as sample:
    sample.demo()

用 with 实例化对象返回值:这里调用语句:sample.demo() 正常调用
start
this is demo
end

3.2.2打印 exc_type, exc_val, exc_tb 信息

#上下文管理器

class Sample(object):
    #获取资源
    def __enter__(self):
        print('start')
        return self

    def demo(self):
        print('this is demo')

    #释放资源
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('end')
        #<class 'AttributeError'> - 异常类
        print(exc_type,'-')
        #'Sample' object has no attribute 'demos' - 异常值
        print(exc_val,'-')
        #<traceback object at 0x0000028A602FA9C8> - 异常信息追踪
        print(exc_tb,'-')

with Sample() as sample:
    sample.demo()

如果 sample.demos(),调用了一个不存在的方法sample.demos(),则程序会报三条异常信息:
<class ‘AttributeError’> - 异常类
‘Sample’ object has no attribute ‘demos’ - 异常值
<traceback object at 0x0000028A602FA9C8> - 异常信息追踪

3.2.3 contextlib简化上下文管理器

class Test:
    def print_test(self):
        print("第二次输出.....")
@contextlib.contextmanager
def file_open(filename):
    #yield上面相当于 __enter__ 函数
    print('file open')
    yield Test()
    #yield下面相当于 __exit__ 函数
    print('file close')

with file_open('test.txt') as f:
    f.print_test()
    print('file opretoin')

返回值:
file open
第二次输出…
file opretoin
file close

3.2.4 进一步理解 yeild

class Test:
    def print_test(self):
        print("第二次输出.....")
@contextlib.contextmanager
def test():
    print("第一次输出.....")
    yield Test()
    print("最后一次输出....")

##直接调用test()方法,t=yield返回的生成器结果
with test() as t:
    t.print_test()

上面程序返回值:
第一次输出…
第二次输出…
最后一次输出…

4.如何创建可管理的对象属性

在面向对象编程中,我们把方法看做对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如访问属性简洁。
通过装饰器,将方法视作属性进行操作
形式上 属性访问
实际上 调用方法
@property
@方法名.setter

class A():
    def __init__(self, age):
        self.age = age

    def get_age(self):
        return self.age

    def set_age(self, age):
        #判断age必须是int类型,否则抛出类型错误
        if not isinstance(age, int):
            raise TypeError('TypeError')
        self.age = age
    #def __init__(self, fget=None, fset=None, fdel=None, doc=None):
    R = property(get_age,set_age)
a = A(18)
a.R = 22
print(a.R)

上面代码返回值:22

将上面代码用 @property 和 @Age.setter 装饰

class A():
    def __init__(self, age):
        self.age = age

    @property    #相当于get方法
    def Age(self):
        return self.age

    @Age.setter   #相当于set方法
    def Age(self, age):
        if not isinstance(age, int):
            raise TypeError('TypeError')
        self.age = age
a = A(18)
a.Age = 20
print(a.Age)

上面程序返回值:20
访问Age函数就像访问类的变量一样方便

5.如何让类支持比较操作

有时,我们希望自定义类的实例间可以使用,<,<=,>,>=,==,!=符号进行比较,我们自定义比较的函数,例如,有一个矩形的类,比较两个矩形的实例时,比较的是他们的面积

5.1 导入functools.total_ordering方法,只需要实现 < 和 == 就能实现所有比较方法

5.1.1 字符串的比较实用ascII码进行比较的:

s1 = ‘abc’
s2 = ‘abd’
s1 > s2
返回:False
因为:ord(‘c’) < ord(‘d’)

5.1.2 集合比较

{1, 2, 3} > {4}
返回:False

{1, 2, 3} < {4}
返回:False

{1, 2, 3} == {4}
返回:False

集合比较的是 “是否包含”
{1, 2, 3} > {1}
返回:True

{1, 2, 3} > {1, 2}
返回:True

{1, 2, 3} > {1, 4}
返回:False

5.1.3 @total_ordering 装饰器

from functools import total_ordering
@total_ordering
#装饰器 @total_ordering
class Rect():
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

    def __str__(self):
        return '我是Rect对象宽%d,高%d'%(self.w, self.h)

    def __lt__(self, other):
        return self.area() < other.area()

    def __eq__(self, other):
        return self.area() == other.area()

下面实例化 rect1 与 rect2进行比较


if __name__ == '__main__':
    rect1 = Rect(2,3)
    print(rect1.area())
    返回值:6
    rect2 = Rect(4, 5)
    print(rect1 >= rect2)
    返回值:False

5.2 用抽象基类实现不同形状的比较

1、抽象出计算面积的共同方法 area() , lt, eq
2、@total_ordering 装饰器扩充比较类型,使得类只需实现 lteq 即可实现所有的比较类型
3、@abc.abstractmethod 抽象基类装饰器装饰area()方法,所以area方法在子类中必须重写,否则报错

from functools import total_ordering
#abc.abstractmethod定义抽象基类,在继承时必须实现
import abc
import math
#total_ordering 装饰器扩充比较类型
@total_ordering
class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def area(self):
        pass
    
    def __lt__(self, other):
        return self.area() < other.area()

    def __eq__(self, other):
        return self.area() == other.area()

class Rect(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return round(math.pi * self.r ** 2,4)
if __name__ == '__main__':
    c = Circle(2)
    print(c.area())
    #圆的面积返回值:12.5664
    r = Rect(2, 3)
    #矩形面积返回值:6
    print(r.area())
    print(c <= r )
    #Circle类 与 Rect类 比较的返回值:False

6.如何在环状数据结构中管理内存

6.1 del 方法只有当引用计数为零时才会执行,下面程序证明

class A():
    def __del__(self):
        print('__del__执行了')

a = A()
a2 = a
a = 1   #a释放了A类的引用
a2 = None   #a2释放了A类的引用

此时程序输出:
__del__执行了

6.2 弱引用 weakref

b2 = weakref.ref(b) 是弱引用,弱引用不占用引用计数,所以当b 和 b3 销毁,程序执行 del 方法

import  weakref
class B:
    def __del__(self):
        print('B类__del__执行了')


b = B()
b2 = weakref.ref(b)
b3 = b
del b, b3

程序打印:
在这里插入图片描述

6.3 双向循环链表

如何在环状数据结构中管理内存,add_right方法中引入弱引用,当head = None时可以释放资源

import weakref 	
class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

    def add_right(self, node):
        self.right = node
        #node.left = self
        node.left = weakref.ref(self)

    def __str__(self):
        return 'Node:<%s>' % self.data

    def __del__(self):
        print('in __del__: delete %s' % self)

def create_linklist(n):
    head = current = Node(1)
    for i in range(2, n + 1):
        node = Node(i)
        current.add_right(node)
        current = node
    return head

head = create_linklist(1000)
head = None

import time
for _ in range(1000):
    time.sleep(1)
    print('run...')
input('wait...')

7、通过实例方法名字的字符串调用方法

7.1 getattr()

 s = '123abc'
 getattr(s, 'find')('abc')

上面程序返回值:3
说明:‘abc’在s的索引位置是‘3’

7.2 map 映射函数

def demo(x):
     return x ** 2
     
list(map(demo, [1, 2, 3]))

返回值: [1, 4, 9]

7.3 getattr() 和 map() 函数应用

我们有三个图形类
Circle,Triangle,Rectangle
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口

lib_shape.py 包含下面三个类Triangle、Rectangle、 Circle

class Triangle:
    def __init__(self,a,b,c):
        self.a,self.b,self.c = a,b,c
    
    def get_area(self):
        a,b,c = self.a,self.b,self.c
        p = (a+b+c)/2
        return (p * (p-a)*(p-b)*(p-c)) ** 0.5

class Rectangle:
    def __init__(self,a,b):
        self.a,self.b = a,b
    
    def getArea(self):
        return self.a * self.b
        
class Circle:
    def __init__(self,r):
        self.r = r
    
    def area(self):
        return self.r ** 2 * 3.14159
if __name__ == '__main__':
    t = Tranigle(3, 4, 5)
    print(t.get_area())   
    返回值:6
    r = Rectangle(4, 6)
    print(r.getarea())
    返回值:20
    c = Circle(2)
    print(c.area())
    返回值:12.5664

下面使用一个类的将上面类包装
这里主要讲两个小知识点:1、getattr() 2、map() 函数

import lib_shape

shape1 = lib_shape.Traingle(3, 4, 5)
shape2 = lib_shape.Rectangle(4, 6)
shape3 = lib_shape.Circle(2)

#此处计算面积,两个小知识点:1、getattr()    2、map()   函数
def  get_area(shape):
    method_name = ['get_area','getarea','area']
    for name in method_name:
        f = getattr(shape,name,None)
        if f:
            return f()

if __name__ == '__main__':
    shape_list = [shape1, shape2, shape3]
    area_list = list(map(get_area, shape_list))
    print(area_list)

上面程序返回值:
[6.0, 24, 13]

发布了31 篇原创文章 · 获赞 0 · 访问量 349

猜你喜欢

转载自blog.csdn.net/weixin_38027481/article/details/103754800