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

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

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

IntTuple([2,-2,'jr',['x','y'],4])=>(2,4)

  • 如何继承内置tuple,实现IntTuple?
    • 了解对象创建的步骤
    • 实现创建时的数据判断
# 对象到底是谁创建的
class A(object):
    def __new__(cls, *args, **kwargs):
        print("A.__new___", cls, args)
        return object.__new__(cls)
     
    def __init__(self, *args):
        print("A.__init__")

a = A(1, 2)
>>>A.__new___ <class '__main__.A'> (1, 2)
>>>A.__init__
# 结论:对象是通过__new__方法创建的
#      __init__方法只是初始化了此对象
#注意:必须要有__new__方法必须返回,返回格式:return object.__new__(cls)

a = A(1, 2) 其实相当于:a = A.new(A, 1, 2) A.init(a, 1, 2)

# 实现创建时的数据判断
class IntTuple(tuple):
    def __new__(cls, iterable):
        # 生成器
        f = [i for i in iterable if isinstance(i, int) and i > 0]
        #列表推导式
        # f = [i for i in iterable if isinstance(i, int) and i > 0] 
        return super().__new__(cls, f)


int_t = IntTuple([2, -2, 'jr', ['x', 'y'], 4])

print(int_t)        
>>> (2, 4)

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

在游戏中,定义了玩家类Player,每有一个在线玩家,在服务器内则有一个Player的实例,当在线人数很多时,将产生大量实例(百万级)
如何降低这些大量实例的内存开销
解决方案:

  • 动态绑定属性:_dict_
  • 通过在类中定义_slots_属性,声明实例有哪些属性,关闭了动态绑定,节省了内存的开销

import tracemalloc


class Player1(object):
    def __init__(self, uid, name, status=0, level=1):
        self.uid = uid
        self.name = name
        self.status = status
        self.level = level


class Player2(object):
    __slots__ = ('uid', 'name', 'status', 'level')

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


tracemalloc.start()
p1 = [Player1(1, 2, 3) for _ in range(100000)]      # 16.8 MiB
p2 = [Player2(1, 2, 3) for _ in range(100000)]        # 7837 KiB
#执行100000条数据后发现,没有使用__slots__占用内存是16.8M  使用了__slots__占用内存仅仅才7837K 。
end = tracemalloc.take_snapshot()
top = end.statistics('filename')

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

3.Python中的with语句

通过类实现上下文管理器
通过实现类中的enter方法和exit方法
注意:在exit方法中的几个参数,可以实现异常抛出。

# 上下文管理器
class Sample(object):

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

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

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


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

上下文管理器协议
contextlib简化上下文管理器

import contextlib

@contextlib.contextmanager
def file_open(filename):
    print("file open")
    yield {}
    print("file end")

with file_open('lib1.py') as f:
    print("file operation")

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

在面向对象编程中,我们把方法看做对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如访问属性简洁。
所有通过两种方式来实现这个需求:

  • 通过装饰器实现:
'''
@property
def x(self):
  "I am the 'x' property."
  return self._x
@x.setter
def x(self, value):
  self._x = value
@x.deleter
def x(self):
  del self._x'
'''
class A:
    @property       # get
    def S(self):
        return self.age

    @S.setter       # set
    def S(self, age):
        if not isinstance(age, int):
            raise TypeError('Type Error')
        self.age = age
a = A(18)
print(a.S)
a.S = 22
print(a.S)
>>>18
>>>22
  • 通过property实现:
'''
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
'''

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

    def get_age(self):
        return self.age

    def set_age(self, age):
        if not isinstance(age, int):
            raise TypeError('Type Error')
        self.age = age
  # property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
    R = property(get_age, set_age)
a = A(18)
a.R = 20
print(a.R)
>>>20

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

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

  • str方法在打印类的时候被调用
  • 字符串的比较是通过字符的ord函数,一个一个比较
  • 列表进行比较时,比的是是否包含
  • 类的比较,只要内部实现_lt_和_eq_
  • 并且引入:from functools import total_ordering
    并且在类的开头使用@total_ordering装饰器
  • 在引入total_ordering后,只要实现lt和eq,如果不导入,则必须将很多比较方法都实现了。
# 同类不同对象的比较
from functools import total_ordering
@total_ordering
# 使用装饰器的作用可以使lt和eq方法覆盖所有的运算符
class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

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

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

    def __eq__(self, other):
        return self.area() == other.area()
rect1 = Rect(1, 2)
rect2 = Rect(1, 2)
print(Rect(1, 2) > Rect(1, 2))    
>>> False

# 不同类不同对象的比较
from functools import total_ordering
import math
@total_ordering
# 使用装饰器的作用可以使lt和eq方法覆盖所有的运算符
class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

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

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

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


class Cirle(object):
    def __init__(self, r):
        self.r = r

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

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

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

c = Cirle(8)
rect = Rect(1, 2)
print(c >= rect)
>>> True
# 通过抽象基类的方式实现
from functools import total_ordering
import math
import abc


@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 Cirle(Shape):
    def __init__(self, r):
        self.r = r

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


c = Cirle(8)
rect = Rect(1, 2)

print(c == rect)
>>>False

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

  • 弱引用

import weakref
a = weakref.ref(A())
那么,a就是弱引用,假如A()对应的其它引用断开,那么系统也会调用A中的del函数释放引用。

双向循环链表

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.通过实例方法名字的字符串调用方法

我们有三个图形类

Circle,Triangle,Rectangle

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

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
shape1 = Triangle(3, 4, 5)
shape2 = Rectangle(4, 6)
shape3 = Circle(1)

def get_area(shape):
    method_name = ['get_area', 'getArea', 'area']
    for name in method_name:
        f = getattr(shape, name, None)
## 使用getattr函数判断类中是否含有某个方法
        if f:
            return f()
print(get_area(shape1))
>>> 6.0
print(get_area(shape2))
>>> 24.0
print(get_area(shape3))
>>> 3.14159
# 使用map函数实现
'''
# map 函数举例
def demo (x)
    return x**2
demo(4) >>> 16
map(demo,[1,2,3])
>>><map at xxx>
list(map(demo,[1,2,3]))
>>> [1,4,9]
'''
shape_list = [shape1, shape2, shape3]
area_list = list(map(get_area, shape_list))
print(area_list)
>>>[6.0, 24.0, 3.14159]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容