Python 进阶强化训练之类与对象深度技术进阶

385 阅读7分钟
原文链接: blog.ansheng.me

实际案例

我们想自定义一种新类型的元祖,对于传入的可迭代对象,我们只保留其中init类型且值大于0的元素,例如IntTuple([1,-1,'abc',6,['x','y'],3])=>(1,6,3)

要求IntTuple是内置的tuple的子类,如何实现?


解决方案

定义类IntTuple继承内置tuple,并实现__new__,修改实例化行为。

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. class IntTuple(tuple):
  5. def __new__(cls, iterable):
  6. g = (x for x in iterable if isinstance(x, int) and x > 0)
  7. return super(IntTuple, cls).__new__(cls, g)
  8. def __init__(self, iterable):
  9. super(IntTuple, self).__init__()
  10. t = IntTuple([1, -1, 'abc', 6, ['x', 'y'], 3])
  11. print(t)

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-1 如何派生内置不可变类型并修改实例化行为.py"
  2. (1, 6, 3)
  3. Process finished with exit code 0

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

实际案例

某网络游戏中,定义了玩家类Player(id,name,status,…),每有一个在线玩家,在服务器程序内侧有一个Player的实例,当在线人数很多时,将产生大量实例(数百万级)

如何降低这些大量实例的内存开销?

解决方案

定义类的__slots__属性,它是用来声明实例属性名字的列表。

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. class Plater:
  5. """ 关闭了动态绑定类属性的__dict__,以便节省内存,__dict__默认占用1024个字节 """
  6. __slots__ = ['uid', 'name', 'status', 'level'] # 声明实例拥有的属性,阻止动态属性绑定
  7. def __init__(self, uid, name, status=0, level=1):
  8. self.uid = uid
  9. self.name = name
  10. self.status = status
  11. self.level = level

如何让对象支持上下文管理

实际案例

我们实现了一个Telnet客户端的类TelnetClient,调用实例的start()方法启动客户端与服务器交互,交互完毕后需调用cleanup()方法,关闭已链接的socket,以及将操作历史记录写入文件并关闭.

能否让TelnetClient的实例实现上下文管理协议,从而替代手工调用cleanup()方法

解决方案

实现上下文管理协议,需定义实例的__enter____exit__方法,他们分别在with开始和结束时被调用。

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. from telnetlib import Telnet
  5. from sys import stdin, stdout
  6. from collections import deque
  7. class TelnetClient:
  8. def __init__(self, addr, port=23):
  9. self.addr = addr
  10. self.port = port
  11. self.tn = None
  12. def start(self):
  13. # user
  14. t = self.tn.read_until('login: ')
  15. stdout.write(t)
  16. user = stdin.readline()
  17. self.tn.write(user)
  18. # password
  19. t = self.tn.read_until('Password: ')
  20. if t.startswith(user[:-1]): t = t[len(user) + 1:]
  21. stdout.write(t)
  22. self.tn.write(stdin.readline())
  23. t = self.tn.read_until('$ ')
  24. stdout.write(t)
  25. while True:
  26. uinput = stdin.readline()
  27. if not uinput:
  28. break
  29. self.history.append(uinput)
  30. self.tn.write(uinput)
  31. t = self.tn.read_until('$ ')
  32. stdout.write(t[len(uinput) + 1:])
  33. def __enter__(self):
  34. self.tn = Telnet(self.addr, self.port) # 创建链接
  35. self.history = deque() # 创建历史记录队列
  36. return self # 返回当前的实例
  37. def __exit__(self, exc_type, exc_val, exc_tb):
  38. """
  39. :param exc_type: 异常类型
  40. :param exc_val: 异常值
  41. :param exc_tb: 跟踪的栈
  42. :return:
  43. """
  44. self.tn.close() # 关闭链接
  45. self.tn = None
  46. with open(self.addr + '_history.txt', 'w') as f:
  47. """ 把历史记录写入文件中 """
  48. f.writelines(self.history)
  49. # return True # 如果返回True,即使出错也会往下继续执行
  50. with TelnetClient('127.0.0.1') as client:
  51. client.start()

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

实际案例

在面向对象变成中,我们把方法(函数)看做对象的接口,直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如访问属性简洁.

  1. circle.getRadius()
  2. circle.setRadius(5.0) # 繁
  1. circle.radius
  2. circle.radius = 5.0 # 简

能否在形式上是属性访问,但实际上调用方法?

解决方案

使用property函数为类创建可管理属性,fget/fset对应相应属性访问

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. from math import pi
  5. class Circle:
  6. def __init__(self, radius):
  7. self.radius = radius
  8. def getRadius(self):
  9. return self.radius
  10. def setRadius(self, value):
  11. if not isinstance(value, (int, float)):
  12. raise ValueError('wrong type. ')
  13. self.radius = float(value)
  14. def getArea(self):
  15. return self.radius ** 2 * pi
  16. R = property(getRadius, setRadius)
  17. c = Circle(3.2)
  18. print(c.R)
  19. c.R = 5.9
  20. print(c.R)

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-4 如何创建可管理的对象属性.py"
  2. 3.2
  3. 5.9
  4. Process finished with exit code 0

如何让类支持比较操作

实际案例

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

解决方案

比较符号运算符重载,需要实现以下方法:__lt____le____gt____ge____eq____ne__,或者使用标准库下的functools下的类装饰器total_ordering可以简化此过程.

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. from functools import total_ordering
  5. from abc import ABCMeta, abstractmethod
  6. @total_ordering
  7. class Shape(object):
  8. __metaclass__ = ABCMeta
  9. @abstractmethod
  10. def area(self):
  11. pass
  12. def __lt__(self, other):
  13. if not isinstance(other, Shape):
  14. raise TypeError('other not Shape')
  15. return self.area() < other.area()
  16. def __eq__(self, other):
  17. if not isinstance(other, Shape):
  18. raise TypeError('other not Shape')
  19. return self.area() == other.area()
  20. class Rectangle(Shape):
  21. def __init__(self, w, h):
  22. self.w = w
  23. self.h = h
  24. def area(self):
  25. return self.w * self.h
  26. class Circle(Shape):
  27. def __init__(self, r):
  28. self.r = r
  29. def area(self):
  30. return self.r ** 2 * 3.14
  31. r1 = Rectangle(5, 3)
  32. r2 = Rectangle(5, 4)
  33. c1 = Circle(3)
  34. print(c1 <= r1)
  35. print(r1 > c1)
  36. print(r1 < r2)

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-5 如何让类支持比较操作.py"
  2. False
  3. False
  4. True
  5. Process finished with exit code 0

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

实际案例

在某些项目中,我们实现了一些类,并希望能像静态类型语言那样对它们的实例属性做类型检查:

  1. p = Person()
  2. p.name = 'Bob' # 必须是str
  3. p.age = 18 # 必须是int
  4. p.height = 1.83 # 必须是float

要求:

  1. 可以对实例变量名制定类型
  2. 赋予不正确类型时抛出异常

解决方案

使用描述符来实现需要类型检查的属性,分别实现__get____set____delete__方法,在__set__内使用isinstance函数做类型检查。

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. class Attr:
  5. def __init__(self, name, type_):
  6. self.name = name # 实例名字
  7. self.type_ = type_ # 类型
  8. def __get__(self, instance, owner):
  9. return instance.__dict__[self.name]
  10. def __set__(self, instance, value):
  11. if not isinstance(value, self.type_):
  12. raise TypeError('expected an %s' % self.type_)
  13. instance.__dict__[self.name] = value
  14. def __delete__(self, instance):
  15. del instance.__dict__[self.name]
  16. class Person:
  17. name = Attr('name', str)
  18. age = Attr('age', int)
  19. height = Attr('height', float)
  20. p = Person()
  21. p.name = 'Bob'
  22. print(p.name)
  23. p.age = '17'

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-6 如何使用描述符对实例属性做类型检查.py"
  2. Bob
  3. Traceback (most recent call last):
  4. File "/Users/ansheng/MyPythonCode/Code/7-6 如何使用描述符对实例属性做类型检查.py", line 33, in <module>
  5. p.age = '17'
  6. File "/Users/ansheng/MyPythonCode/Code/7-6 如何使用描述符对实例属性做类型检查.py", line 16, in __set__
  7. raise TypeError('expected an %s' % self.type_)
  8. TypeError: expected an <class 'int'>
  9. Process finished with exit code 1

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

实际案例

在Python中,垃圾回收器通过引用计数来回收垃圾对象,但某些环状数据结构,存在对象间的循环引用,比如书的父节点引用子节点,子节点也同时引用父节点,此时同事del掉父子节点,两个对象不能立即回收,如何解决此类的内存管理问题?

解决方案

使用标准库weakref,它可以创建一种访问对象但不增加引用计数的对象。

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. import weakref
  5. class Data:
  6. def __init__(self, value, owner):
  7. self.owner = weakref.ref(owner)
  8. self.value = value
  9. def __str__(self):
  10. return "%s's data, value is %s" % (self.owner(), self.value)
  11. def __del__(self):
  12. print('in Data.__del__')
  13. class Node:
  14. def __init__(self, value):
  15. self.data = Data(value, self)
  16. def __del__(self):
  17. print('in Node.__del__')
  18. node = Node(100)
  19. del node
  20. input('wait...')

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-7 如何在环状数据结构中管理内存.py"
  2. in Node.__del__
  3. in Data.__del__
  4. wait...

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

实际案例

某项目中,我们的代码使用了三个不同库中的图形类:

  1. Circle
  2. Triangle
  3. Rectangle

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

解决方案

  1. 使用内置函数getattr,通过名字在实例上获取方法对象,然后调用;
  2. 使用标准库operator下的methodcaller函数调用;

解决代码

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. # Created by 安生 on 2017/2/19
  4. class Circle:
  5. def __init__(self, r):
  6. self.r = r
  7. def area(self):
  8. return self.r ** 2 * 3.14
  9. class Rectangle:
  10. def __init__(self, w, h):
  11. self.w = w
  12. self.h = h
  13. def get_area(self):
  14. return self.w * self.h
  15. class Triangle:
  16. def __init__(self, a, b, c):
  17. self.a = a
  18. self.b = b
  19. self.c = c
  20. def getArea(self):
  21. a, c, b = self.a, self.b, self.c
  22. p = (a + b + c) / 2
  23. area = (p * (p - a) * (p - b) * (p - c)) ** 0.5
  24. return area
  25. def getArea(shape):
  26. for name in ('area', 'get_area', 'getArea'):
  27. f = getattr(shape, name, None)
  28. if f:
  29. return f()
  30. shape1 = Circle(2)
  31. shape2 = Triangle(3, 4, 5)
  32. shape3 = Rectangle(6, 4)
  33. shapes = [shape1, shape2, shape3]
  34. print(list(map(getArea, shapes)))

输出

  1. /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 "/Users/ansheng/MyPythonCode/Code/7-8 如何通过实例方法名字的字符串调用方法.py"
  2. [12.56, 6.0, 24]
  3. Process finished with exit code 0

方法2

  1. >>> s = 'abc123abc456'
  2. >>> s.find('abc',4)
  3. 6
  4. >>> from operator import methodcaller
  5. >>> methodcaller('find','abc',4)(s)
  6. 6

作者:安生

出处:blog.ansheng.me/article/adv…

本文章采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可。