返回顶部

python 实用编程技巧 —— 类与对象深度问题与解决技巧

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

实际案例

我们想要自定义一种新类型的元组, 对于传入的可迭代对象, 我们只保留作其中int类型且值大于0的元素, 例如:

IntTuple([1, -1, 'abc', 6, ['x', 'y'], 3]) => (1, 6, 3)

如何继承 内置 tuple 实现 IntTuple?

解决方案

重构 __new__ 方法

class IntTuple(tuple):
    def __new__(cls,iterable):
        g = (x for x in iterable if isinstance(x,int) and x>0)
        return super(IntTuple,cls).__new__(cls,g)

t = IntTuple([1,-1,'abc',6,['x','y'],3])
print(t)

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

实际案例  

在某网络游戏中, 定义了玩家类 Player(id, name, level,...)
每有一个在线玩家, 在服务器程序内则有一个 Player 的实例,
当在线人数很多时, 将产生大量实例。 (如百万级)
如何降低这些大量实例的内存开销?

解决方案

  • 定义类的 slots 属性,声明实例有哪些属性(关闭动态绑定)
  • __slots__属性提前定义好了属性,不会再变,没有了__dict__属性,也就没有了动态绑定功能,类似于C语言的结构体

 

class Player1:
    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level


class Player2:
    __slots__ = ['uid', 'name', 'level']
    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level

p1 = Player1('0001', 'Jim', '20')
p2 = Player2('0001', 'Jim', '20')

set(dir(p1)) - set(dir(p2))
# 发现p1属性 多于p2
# 多了  __dict__,  和__weakref__两个属性
# 主要在__dict__  属性上 会多消耗内存
# __dict__ 动态维护 实例属性  像self.uid  self.name  这些都是它维护的,
# __dict__ 可以动态添加属性  如  p1.newadd = 1   会自动在p1.__dict__的字典中加入 'newadd': 1

import sys

sys.getsizeof(p1.__dict__)  # 864
sys.getsizeof(p1.name)  # 52
sys.getsizeof(p1.level)  # 28
sys.getsizeof(p1.uid)  # 53
# 可以看到 __dict__浪费了一部分内存, 如果实例比较少, 问题不大, 但实例非常多, 会非常浪费内存

# 如果使用了__slots__   就提前确定的内存, 无法对实例动态添加属性了  像 p2.newadd = 1 就会报错,无法实现。
# 原有属性不受影响   p2.name = "newname"  都是可行的

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

实际案例

在面对对象编程中, 我们把方法(函数)看作对象的接口。 直接访问对象的属性是不安全的,
或者设计上不够灵活。 但是使用调用方法在形式上不如访问属性简洁

circle.get_radius()
circle.set_radius(5.0)  # 繁
 
circle.radius
circle.radius = 5.0     # 简  

 

解决方案

  • 使用property
import math


class Circle:
    '''
    分别使用property的两种使用方法, 实现 面积s 和 半径r 的操作
    '''

    def __init__(self, radius):
        self.radius = radius

    def get_radius(self):
        return round(self.radius, 1)

    def set_radius(self, radius):
        if not isinstance(radius, (int, float)):
            raise TypeError('wronge type')
        self.radius = radius

    # property用法一
    # @property  和  @函数名.setter装饰器
    @property
    def S(self):
        return self.radius ** 2 * math.pi

    @S.setter
    def S(self, s):
        self.radius = math.sqrt(s / math.pi)
    # property 用法二
    # 参数分别是  属性的访问 属性的赋值  属性的删除   都是函数参数
    R = property(get_radius, set_radius)


c = Circle(5.712)

c.S = 99.88
print(c.S)  # 99.880000000
print(c.R)  # 5.6
print(c.get_radius())  # 5.6

如何让类支持比较操作

解决方案

  • 利用 __lt__小于,__eq__等于等魔法方法
  • 比较的方法写在抽象类中, 让其他类继承即可减少 代码编写量
  • 使用total_ordering装饰器装饰抽象基类来简化实现过程
from functools import total_ordering

from abc import ABCMeta, abstractclassmethod


@total_ordering  # 修饰后, 只要实现__eq__ 和 剩下的比较操作中 随便实现一个, 就能实现所有比较
class Shape(metaclass=ABCMeta):  # 定义抽象类
    @abstractclassmethod  # 定义抽象方法
    def area(self):
        pass

    def __lt__(self, obj):
        print('__lt__', self, obj)
        return self.area() < obj.area()

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

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

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

    def __str__(self):
        return 'Rect:(%s, %s)' % (self.w, self.h)


import math

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

    def area(self):
        return self.r ** 2 * math.pi


rect1 = Rect(6, 9)  # 54
rect2 = Rect(7, 8)  # 56
c = Circle(8)  # 201.06

print(rect1 < c)  # True
print(c > rect2)  # True

如何使用描述符对实例属性做类型检查

解决方案

  • 使用 __ dict __ 的特性
  • 综合使用 __ se t__, __ get __, __ dele
  • __set__(self, instance, value) 中的 instance 指的是实例对象,在这里是 Person 实例
class Attr:
    def __init__(self, key, type_):
        self.key = key
        self.type_ = type_

    def __set__(self, instance, value):
        print('in __set__')
        if not isinstance(value, self.type_):
            raise TypeError('must be %s' % self.type_)
        instance.__dict__[self.key] = value

    def __get__(self, instance, cls):
        print('in __get__', instance, cls)
        return instance.__dict__[self.key]

    def __delete__(self, instance):
        print('in __del__', instance)
        del instance.__dict__[self.key]


class Person:
    name = Attr('name', str)
    age = Attr('age', int)


p = Person()
p.name = 'cannon'  # 会调用__set__方法
p.age = '26'  # '32'不是int, 会报错

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

实际案例

在python中, 垃圾回收器通过引用计数来回收垃圾对象,但在某些环状数据结构中(树, 图...), 存在对象间的循环引用, 比如树的父节点引用子节点, 子节点引用父节点。

此时同时 del掉引用父子节点, 两个对象不能被立即回收( 引用计算无法变为0) 如何解决此类的内存管理问题? 

解决方案

  • 使用弱引用 (不会增加引用计数的引用)
  • 使用标准库weakref.ref() 创建弱引用
  • 对于链表, 可以右node引用计数为1, 左引用计数为0
  • 下面例子中 如果left是弱引用, 要得到引用就得head.left() , 为了不要(), 使用property

 

简单的测试小案例

import sys

class A(object):
    def __del__(self):        #析构函数,在垃圾回收器回收变量时会调用析构函数
        print('in __del__')

if __name__ == '__main__':
    a = A()
    print(sys.getrefcount(a)) # 打印a的循环引用计数
    a1 = a
    print(sys.getrefcount(a))  # 打印a的循环引用计数
    del a1
    print(sys.getrefcount(a))  # 打印a的循环引用计数
    a = 5  # 将a赋值 时,a的引用计数再减1,就不再使用了,此时垃圾回收器将回收这个变量

输出结果如下

/usr/bin/python3.5 /home/python/Desktop/zhang/import_csv/import_csv.py
2
3
2
in __del__

Process finished with exit code 0

弱引用的使用  

  • Weakref,可以创建能访问对象但不增加引用计数的对象。当这个对象存在时返回这个对象的引用,当这个对象不存在时返回None

简单实例

import sys
import weakref

class A(object):
    def __del__(self):        #析构函数,在垃圾回收器回收变量时会调用析构函数
        print('in __del__')

if __name__ == '__main__':
    a = A()
    print(sys.getrefcount(a)) # 打印 a 的循环引用计数
    # 创建a的弱引用对象
    a_wref = weakref.ref(a)
    print(sys.getrefcount(a)) # 此时a的循环引用计数,数量不变
    a2 = a_wref()  # 将弱引用对象赋给a2
    print(sys.getrefcount(a)) # 此时的引用计数增加了
    print(a2 is a)
    print(sys.getrefcount(a2)) # 查看a2的循环引用计数

    # 删除a和a2
    del a2
    del a

    print(a_wref() is None) #此时弱引用对象已回收,返回None

 

输出结果如下

/usr/bin/python3.5 /home/python/Desktop/zhang/import_csv/import_csv.py
2
2
3
True
3
in __del__
True

Process finished with exit code 0

  

键表的节点和数据

class Data(object):
    def __init__(self,value,node): #定义数据的值,和属于的结点
        self.node = node
        self.value = value

    def __str__(self):
        return "%s 's data,value is %s" %(self.node,self.value)

    def __del__(self):
        print('in Data.__del__')

class Node(object):
    def __init__(self,value):   #定义一个结点,并赋初值数据。
        self.data = Data(value,self) #结点的数据是Data()类的对象,

    def __del__(self):
        print ('in Node.__del__')


node = Node(100)

del node
print ('end')

 

输出结果如下

/usr/bin/python3.5 /home/python/Desktop/zhang/import_csv/import_csv.py
end
in Node.__del__
in Data.__del__

先输出的是 end 说明循环引用没有被释放  

使用 弱引用进行改写

import weakref
class Data(object):
    def __init__(self,value,node):
        self.node = weakref.ref(node)  #创建结果的弱引用对象
        self.value = value

    def __str__(self):
        return "%s 's data,value is %s" %(self.node(),self.value)

    def __del__(self):
        print('in Data.__del__')

class Node(object):
    def __init__(self,value):
        self.data = Data(value,self)

    def __del__(self):
        print ('in Node.__del__')


node = Node(100)

del node
print ('end')

 

输出结果如下

/usr/bin/python3.5 /home/python/Desktop/zhang/import_csv/2-1.py
in Node.__del__
in Data.__del__
end

如何通过方法名字的字符串调用方法

实际案例  

在某项目中, 我们的代码使用了三个不同库中的图形类:
Circle, Triangle, Rectangle
它们都有一个获取图形面积的接口(方法), 但接口名字不同。我们可以实现一个统一的获取面积的函数,
使用每种方法名进行尝试, 调用相应类的接口

解决方案  

  • lib1
# lib1
class Circle:
    def __init__(self, r):
        self.r = r
    
    def area(self):
        return relf.r ** 2 ** 3.14159
  • lib2
# lib2
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
  • lib3
# lib3
class Rectangle:
    def __init__(self, a, b):
        self.a, self.b = a, b
 
    def getArea(self):
        return self.a * self.b
  • 对Circle, Rectangel, Triangle 求面积
  • 方法一: 使用内置函数getattr, 通过名字获取方法对象, 然后调用
  • 方法二: 使用标准库operator 下的methodcaller函数调用
from lib1 import Circle
from lib2 import Triangle
from lib3 import Rectangle
from operator import methodcaller
 
def get_area(shape, method_name = ['area', 'get_area', 'getArea']):  # 方法的名字都先放入一个列表中
    for name in method_name:
        if hasattr(shape, name):
            return methodcaller(name)(shape)
        # 或者
        # f = getattr(shape, name, None)
        # if f:
        #     return f()
 
 
shape1 = Circle(1)
shape2 = Triangle(3, 4, 5)
shape3 = Rectangle(4, 6)
 
shape_list = [shape1, shape2, shape3]
# 获得面积列表
area_list = list(map(get_area, shape_list))
print(area_list)

  

  

 

posted @ 2019-08-25 23:38  Crazymagic  阅读(290)  评论(0编辑  收藏  举报