Python实例(四)

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

场景:自定义一种新类型元组,对于传入的可迭代对象,我们只想保留其中int类型且值大于0的元素,要求新派生的类型IntTuple是内置tuple的子类

解决方法:定义类IntTuple继承内置tuple,并实现__new__,修改实例化行为

class IntTuple(tuple):
    def __init__(self,iterable):
        super(IntTuple,self).__init__(iterable)##调用父类构造器方法
        ##发现self(实例)传入构造器后,元组就已经被创建好了,不能被改变。
        ##因此我们要修改的是实例创建过程
    def __new__(cls,iterable):##传入类对象,python一切皆对象,先于init方法被调用
        g=(for x in iterable if isinstance(x,int) and x>0)
        return super(IntTuple,cls).__new__(cls,g)  ##调用父类方法

t = IntTuple([1,-2,'abc',6,[1,6],3])
print t
>(1,6,3)

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

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

解决方法:定义类的__slots__属性,它是用来声明实例属性名字的列表

class Player(object):
    __slots__=['uid','name','stat','level']
    ##声明这个类有哪些属性,之后不允许动态属性绑定
    def __init__(self,uid,name,status=0,level=1):
        self.uid=uid
        self.name=name
        self.stat=status
        self.level=level

##假设Player2没有指定__slot__方法,则Player2的实例就拥有__dict__属性,允许动态绑定
##Player2.x=666是被允许的,但Player.x=666是不允许的,缺少__dict__可以节省大量内存

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

场景:
上下文管理例如with…open…可以省去close()
现在实现一个telnet客户端的类TelnetClient,调用实例的start()方法启动客户端与服务器交互,交互完毕后需调用cleanup()方法,关闭已连接的socket,以及将操作历史记录写入文件并关闭

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

解决方法:
实现上下文管理协议,需 定义实例的__enter__, __exit__方法,他们分别在with开始和结束时调用

from telnetlib import Telnet
from sys import stdin,stdout
from collections import deque

class TelnetClient(object):
    def __init__(self,addr,port=23):
        self.addr=addr
        self.port=port
        self.tn=None
    def start(self):
        self.tn=Telnet(self.addr,self.port) ##创建连接对象
        self.history=deque() ##创建队列,存储用户操作历史记录
        ##登陆操作
        t=self.tn.read_until('login:')
        stdout.write(t)
        user=stdin.readline()
        self.tn.write(user)

        t=self.tn.read_until('Password:')
        if t.startswith(user[:-1]):t=t[len(user)+1:]
        stdout.write(t)
        self.tn.write(stdin.readline())
        ##与服务器进行交互
        t=self.tn.read_until('$')
        stdout.write(t)
        while True:
            uinput = stdin.readline()
            if not uinput:
                break
            self.history.append(uinput)
            self.tn.write(uinput)
            t=self.tn.read_until('$ ')
            stdout.write(t[len(uinput)+1:])
    def cleanup(self):
        self.tn.close()
        self.tn = None
        with open(self.addr+'_history.txt','w') as f:
            f.writelines(self.history)

##  如果使用上下文管理。。。
##  def __enter__(self):
##      self.tn=Telnet(self.addr,self.port) 
##      self.history=deque() ##将前面start函数中的两句命令移到这里来
##      return self
##  def __exit__(self,exc_type,exc_val,exc_tb):##后面三个是异常的类型,值,栈
##      self.tn.close()
##      self.tn = None
##      with open(self.addr+'_history.txt','w') as f:
##          f.writelines(self.history) ##将cleanup方法移到这里来
##  另外:当发生异常时则直接跳到__exit__方法,默认return None,程序不再允许
##  我们可以强调return True,则程序跳过异常继续执行

client=TelnetClient('127.0.0.1')
client.start()
client.cleanup()

##上下文管理实现效果
with TelnetClient('127.0.0.1') as Client:
    client.start()  ##自动实现cleanup方法

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

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

circle.getRadius()  ##设置器,可以对用户输入进行筛选或标准化
circle.setRadius(5.0) #繁

circle.radius ##不安全,不灵活
circle.radius=5.0   #简

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

解决方法:
使用property函数为类创建可管理属性,fget / fset / fdel 对应相应的属性访问

class Circle(object):
    def __init__(self,radius):
        self.radius=radius
    def getRadius(self):
        return self.radius
    def setRadius(self,value):
        if not isinstance(value,(int,long,float)):
            raise ValueError('wrong type')
        self.radius=float(value)
    def getArea(self):
        return self.radius**2*pi
    R = property(getRadius,setRadius)##调用方法,有4个参数

如何让类支持比较操作

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

解决方法:
1. 比较符号运算符重载,分别实现以下方法:__lt__,__le__,__gt__,__ge__,__eg__,__ne__
2. 使用标准库下的functools下的类型装饰器total_ordering可以简化此过程

from functools import total_ordering

@total_ordering ##我们只需要定义<,=号,其他符号比较就可以推测出来
class Rectangle:
    def __init__(self,w,h):
        self.w=w
        self.h=h
    def area(self):
        return self.w*self.h
    def __lt__(self,obj):
        return self.area() < obj.area()
    def __eq__(self,obj):
        return self.area() == obj.area()

r1=Rectangle(5,3)
r2=Rectangle(4,4)
r1<r2  ##相当于r1.__lt__(r2)
>True

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

场景:我们希望一些类,能像静态类型语言那样(c,java)对它们的实例属性做类型检查
p.name=’Bob’ ##必须是str
要求:可以对实例变量名指定类型;赋予不正确类型时抛出异常

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

class Attr(object):
    ##描述符就是包含__get__,__set__,__delete__任一个的类
    def __init__(self,name,type_): ##注意type是内置关键字不可使用
        self.name 
        self.type_=type_
    def __get__(self,instance,cls):
         ##参数instance相当于实例p,cls相当于Person()
        return instance.__dict__[self.name] 
         ##对实例本身进行操作,若没有则实例p并没有定义属性
    def __set__(self,instance,value):
        #if not isinstance(value,self.type_):
        #   raise TypeError('expect an %s' % self.type_) ##类型检查
        instance.__dict__[self.name]=value
    def  __delete__(self,instance):
        del instance.__dict__[self.name]
class Person (object):
    name = Attr('name',str) ##定义一个类属性是描述符的实例
    age = Attr('age',int)
    height = Attr('height',float)

p=Person() ##Person类的实例进行读,写,删除操作会被描述符截获
p.name='bob' 
p.name ##通过实例访问属性,被__get__截获
Person.name ##通过类访问属性,同样被__get__截获,此时参数instance为None

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

场景:在python中,垃圾回收站通过引用计数来回收垃圾对象,但某些环状数据结构(树,图…),存在对象间的循环引用,比如树的父节点,子节点也同时引用父节点。此时同时del掉引用父子节点,两个对象不能被立即回收

解决方案:
使用标准库中weakref,它可以创建一种能访问对象但不增加引用计数的对象(引用计数为0的对象会被回收)

##import weakref class Date(object):
    def __init__(self,value,owner):
        ##self.owner=weakref.ref(owner) ##创建一个对象的弱引用
        self.owner=owner
        self.value=value
    def __str__(self):
        return (self.owner,self.value)
        ##return (self.owner(),self.value) ##必须使用函数调用的形式使用
        ##源对象被删除调用,则函数返回None,因此源函数不必因为引用而拖后回收
class Node(object):
    def __init__(self,objecct):
        self.date=Date(value,self) ##循环引用
    def __del__(self):
        print()

node=Node(100)
del node  ##发现没有被回收
import gc
gc.collect()  ##强制回收,也不能回收

##del node 
##发现node的Node和Date都被成功删除

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

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

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

from lib1 import Circle
from lib2 import Triangle
from lib3 import Rectangle

shape1=Circle(2)
shape2=Triangle(3,4,5)
shape3=Rectangle(6,4)
shapes=[shape1,shape2,shape3] 
print(map(getArea,shapes)) ##使用统一接口getArea
##方法一:
def getArea():
    for name in ('area','getarea','get_area'): 
    ##尝试获取对象的这些属性,如果获取到就调用这个方法,获取不到继续向下进行
        f = getattr(shape,name,None)  
        ##找对象的属性,找不到返回None,f指方法对象
        if f:
            return f()
##方法二:
from operator import methodcaller
s='abc123abc123'
s.find('abc',4) ##从第4个字符开始查找abc
methodcaller('find','abc',4)(s) 
##得到一个可以调用的对象,再把s传入函数中使用即可
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值