zoukankan      html  css  js  c++  java
  • 流畅的python学习笔记:第九章:符合python风格的对象

    首先来看下对象的表现形式:
    class People():
        def __init__(self,name,age):
            self.name=name
            self.age=age

    if __name__== "__main__":
        p=People('zhf','30')
        print p
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    <__main__.People instance at 0x01763B48>
    当我们打印People的实例的时候得到的是这个类的内存地址。这个对用户而言其实没太多的用处。我们想在打印的时候得到关于这个类的更多的信息。这个时候就要用到__str__.代码修改如下:
    class People():
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):
            return 'People:%s,%s' % (self.name,self.age)

    if __name__== "__main__":
        p=People('zhf','30')
        print p
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    People:zhf,30

    得到的结果是People:zhf,30。这样就更直接也更直观。__str__的作用在于print对象实例的时候会被调用。但是我们如果不用print p而是直接用p的时候,__str__则不会被调用。这个时候就要用到__repr__。代码改成如下的时候。在单独调用People实例的时候也可以打印出相应的信息。

    class People():
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):
            return 'People:%s,%s' % (self.name,self.age)
        def __repr__(self):
            return 'People:%s,%s' % (self.name,self.age)

    其实__str__和__repr__的返回都是一样的,每一个都写上一个实现其实有点冗余。代码可以精简如下,这样不管是print打印还是单独调用都可以打印。

    class People():
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):
            return 'People:%s,%s' % (self.name,self.age)
        __repr__=__str__
    通过两种不同的调用方式可以看出__str__主要是显示给用户的。而__repr__主要是给开发人员使用的。
    我们再来看下更多的内置方法:
    class Vector():
        def __init__(self,x,y):
            self.x=float(x)
            self.y=float(y)
        def __iter__(self):
            return (i for i in (self.x,self.y))
        def __str__(self):
            return str(tuple(self))
        def __bytes__(self):
            return (bytes([ord(self.typecode)])+bytes(array(self.typecode,self)))
        def __eq__(self, other):
            return tuple(self) == tuple(other)
        def __abs__(self):
            return math.hypot(self.x,self.y)
        def __bool__(self):
            return bool(abs(self))
    if __name__== "__main__":
        v1=Vector(3,4)
        v2=Vector(3,1)
        x,y=v1          #调用__iter__
        print x,y
        print abs(v1)   #调用__abs__
        print v1==v2    #调用__eq__
        print bool(v1)  #调用__bool__
        print bytes(v1) #调用__bytes__
    从上面的实现可以看到,当对对象实例采用各自不同的运算的时候,会调用相应的内部方法。
     
    classmethod与staticmethod
    我们先来看下一个普通的类实例调用方法:
    class People():
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):
            return 'People %s,%s' % (self.name,self.age)
        def print_info(self):
            print self.name,self.age
        __repr__=__str__
     
    if __name__== "__main__":
        p1=People('zhf',30)
        p2=People('zhf1',32)
        p1.print_info()
        p2.print_info()

    在这里有两个实例化的对象,p1和p2.这里的实例对象是通过self参数传递给函数的。来看下运行状态
    P1的实例对象,可以看到self参数是被不同的实例对象给赋值的

    P2的实例对象

    因此在打印的时候,也是将不同的实例对象传入至函数,这样就能打印出不同的实例对象的参数。

    __init__中定义的参数都属于各自的实例对象。如果我们仅想实现类的交互而不是实例的交互呢,比如我想统计这个对象有多少个实例。下面的代码是否可以呢?
    class cls_instance(object):
        inst_num=0
        def __init__(self):
            self.inst_num+=1

    if __name__== "__main__":
        c1=cls_instance()
        c2=cls_instance()
        print c2.inst_num
    通过执行结果可以看到实例个数为1.但其实有C1和C2两个实例化的对象。从下面的关系图可以看到,self.inst_num是被各自实例计数保存的。并没有做到全局共享,要做到全局共享,就必须以类的方式交互而不是实例的方式

    这里就要用到classmethod这个装饰器了。代码修改如下:

    class cls_instance(object):
        inst_num=0
        def __init__(self):
            cls_instance.inst_num+=1
        @classmethod
        def get_no_instance(cls):
            return cls.inst_num

    if __name__== "__main__":
        c1=cls_instance()
        c2=cls_instance()
        print c2.get_no_instance()
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    2
    首先在__init__中调用的是cls_instance.inst_num+=1。也就是通过类来对计数器进行加一,而非实例对象。然后通过get_no_instance来统计实例对象。注意,get_no_instance的参数为cls且前面带有装饰器@classmethod,因此这个cls就是代表的cls_instance这个类对象,而非实例。所以得到的实例个数就为2个

    从上面的关系图很容易看出,get_no_instance中的cls参数为cls_instance类。这就是classmethod的用法。我们来看一个classmethod更实用的用法。
    如果我们想实例化一个时间的类,传入具体的年,月,日

    class Date_format(object):
        def __init__(self,day=0,month=0,year=0):
            self.day=day
            self.month=month
            self.year=year
    if __name__== "__main__":
        date='2017-7-10'
       
    day,month,year=map(int,date.split('-'))
        d=Date_format(day,month,year)

    时间的格式是2017-7-10,为了区分年,月,日,首先要按照这个格式分割开来并且转换为整数。
    但是如果我们有很多类似这种格式的格式字符串信息。这样一个个的去实例也挺麻烦的。我们需要把day,month,year=map(int,date.split('-'))这个的具体实现做到类中去。通过调用类来得到一个时间实例。

    class Date_format(object):
        def __init__(self,day=0,month=0,year=0):
            self.day=day
            self.month=month
            self.year=year
        @classmethod
        def from_string(cls,date_string):
            day,month,year=map(int,date_string.split('-'))
            date=cls(day,month,year)
            return date

    if __name__== "__main__":
        d=Date_format.from_string('2017-7-10')
        print d.day,d.month,d.year
    通过from_string来得到具体的日期并通过date=cls(day,month,year)来实例化对象并返回这个实例化对象
     
    下面来看下staticmethod。还是刚才的例子,如果我们只是想得到具体的日期,而不需要实例化的话可以用到staticmethod。如下的代码:
    class Date_format(object):
        def __init__(self,day=0,month=0,year=0):
            self.day=day
            self.month=month
            self.year=year
        @classmethod
        def from_string(cls,date_string):
            day,month,year=map(int,date_string.split('-'))
            date=cls(day,month,year)
            return date
        @staticmethod
        def get_detail_time(date_string):
            day,month,year=map(int,date_string.split('-'))
            return day,month,year

    if __name__== "__main__":
        day,month,year=Date_format.get_detail_time('2017-7-10')
        print day,month,year
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    2017 7 10

    __slots__
     
    class A(object):
        def __init__(self,x):
            self.x=x

    if __name__== "__main__":
        a=A(3)
        print a.x
        print a.__dict__
        print a.__dict__['x']
    a.y=1
    print a.__dict__
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    3
    {'x': 3}
    3
    {'y': 1, 'x': 3}
    python中,实例化对象的变量都保存在字典中,因此我们可以用a.__dict__来查看所有的属性。也可以通过a.__dict__[‘x’]来代替a.x。本质上原理都是一样的。另外也可以自由添加变量。比如a.y=1.添加成功后再字典中也能看到对应的变量。但是如果我们想固定变量,比如只有x和y该如何操作呢。这里就需要用到__slots__方法。代码修改如下:
    class A(object):
        __slots__=('x','y')
        def __init__(self,x,y):
            self.x=x
    if __name__== "__main__":
        a=A(3,2)
        print a.x
        print a.__slots__
    print a.__dict__
    我们来看下运行结果:
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    3
    ('x', 'y')
    Traceback (most recent call last):
      File "E:/py_prj/fluent_python/chapter9.py", line 65, in <module>
        print a.__dict__
    AttributeError: 'A' object has no attribute '__dict__'

    首先__slots__是返回的元组形式存储变量,而非字典。另外在执行__dict__的时候报错,提示没有__dict__属性。因此如果用了__slots__来设定变量,则__dict__将不会再有。那么我们可以添加变量么。我们新增一个变量z

    a.z=3
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter9.py
    Traceback (most recent call last):
      File "E:/py_prj/fluent_python/chapter9.py", line 65, in <module>
        a.z=3
    AttributeError: 'A' object has no attribute 'z'

    结果提示没有z的属性。说明使用了__slots__后不能任意添加属性。
    那么__slots__的优势在哪呢?不用__slots__的时候,变量是保存在字典里的。字典是很耗内存的,如果这个对象有很多个实例对象,那么每个实例对象都将会建立一个字典。在这种情况下内存的消耗是很惊人的。我们来实测一下。我们用profile来测试代码所占的内存。首先创建100000个实例对象

    class A(object):
        def __init__(self,x,y):
            self.x=x
            self.y=y
    @profile
    def profile_result():
        f=[A(1,2) for i in range(100000)]

    if __name__== "__main__":
        profile_result()
    测试结果:建立实例对象消耗了21.4M的内存


    改用__slots__的方式:
    class A(object):
        __slots__=('x','y')
        def __init__(self,x,y):
            self.x=x
            self.y=y
    @profile
    def profile_result():
        f=[A(1,2) for i in range(100000)]
    if __name__== "__main__":
        profile_result()
    测试结果:同样个数的实例值消耗了5.7M的内存。相比字典确实节省了不少的内存


    最后总结一下:
    __slots__用元组的形式保存变量,而非字典。且__slots__无法添加变量。由于采用了元组的方式,因此在创建多个实例的时候,会节省不少内存。但由于无法添加变量,也有很大的局限性。最适合__slots__的场景就是类的变量恒定不变且会创建多个实例化对象的时候,可以采用__slots__
     
     
     
    
    
    
    
    
    
    
    
    
    
    
    
  • 相关阅读:
    qt运行编译按钮居然是灰色的
    suse linux服务器的常用命令
    windows 防火墙的bug
    烦恼核心
    System.Data.OleDb.OleDbException (0x80004005) 的问题
    c#利用批处理清理大目录
    Spider Studio 社区信息
    示例
    示例
    Spider Studio 新版本 (20140225)
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/7146195.html
Copyright © 2011-2022 走看看