zoukankan      html  css  js  c++  java
  • python学习笔记13类的设计

    如何使用类来对有用的对象进行建模?

    一、Python和OOP

    Python和OOP实现可以概括为三个概念。

    继承

             继承是基于Python中属性查找(在X.name表达式中)

    多态

             在X.method方法中,method的意义取决于X的类型(类)

    封装

             方法和运算符实现行为,数据隐藏默认是一种惯例。

    封装指的是在Python中打包,也就是把实现的细节隐藏在对象接口之后。这并不代表有强制的私有性。封装可以让对象接口的现实

    出现变动时,不影响这个对象的用户。

    1、不要通过调用标记进行重载

    不要在同一个类中对同一个方法名定义两次,后面的会覆盖前面,也不要对对象类型进行测试。应该把程序代码写成预期的对象接口。而不是特定类型的数据类型。

    2、类作为记录

    通过类的实例来创建多个记录。

    3、类和继承:是“一个”关系 (is a)

    从程序员的角度来看,继承是由属性点号运算启动的,由此触发实例,类以及任何超类中变量名搜索。

    从设计师的角度看,继承是一种定义集合成员关系的方式:类定义了一组内容属性,可由更具体的集合(子类)继承和定制。

    子类和超类的继承是1对1的关系.

    PizzaRobot是一种Chef,Chef是一种Employee.以OOP术语来看,我们称这些关系为“是一个连接”(is a):机器人是个主厨,主厨是一个员工。

    class Employee:

            def __init__(self,name,salary=0):

                    self.name=name

                    self.salary=salary

            def giveRaise(self,percent):

                    self.salary=self.salary+(self.salary*percent)

            def work(self):

                    print self.name,"does stuff"

            def __repr__(self):

                    return "<Employee:name=%s,salary=%s>" % (self.name,self.salary)

    class Chef(Employee):

            def __init__(self,name):

                    Employee.__init__(self,name,5000)

            def work(self):

                    print self.name,"make food"

    class Server(Employee):

            def __init__(self,name):

                    Employee.__init__(self,name,40000)

            def work(self):

                    print self.name,"interface with customer"

    class PizzaRobot(Chef):   

            def __init__(self,name):#有点想不明白,既然继承就够了,为什么还要在这里构造

                    Chef.__init__(self,name) #Chef.__init__(self,name) =》Employee.__init__(self,name,5000)=>__init__(self,name,salary=0)

            def work(self):

                    print self.name,"make pizza"

    if __name__=='__main__':

            bob=PizzaRobot('bob')

            print bob

            bob.work()

            bob.giveRaise(0.20)

            print bob;print

    # python employees.py  

    <Employee:name=bob,salary=5000>

    bob make pizza

    <Employee:name=bob,salary=6000.0>

    理解有问题的地方

    class PizzaRobot(Chef):   

            def __init__(self,name):#有点想不明白,既然继承就够了,为什么还要在这里构造,下面拿掉这里做对比

                    Chef.__init__(self,name)

    #Chef.__init__(self,name) =》Employee.__init__(self,name,5000)=>__init__(self,name,salary=0) 连接过程

            def work(self):

                    print self.name,"make pizza"

    下面拿掉PizzaRobot类中的def __init__(self,name):

    # python employees.py

    <Employee:name=bob,salary=5000>

    bob make pizza

    <Employee:name=bob,salary=6000.0

    结果一样,那这个函数还有什么必要做构造,直接继承就可以了

    导入进行其他测试

    >>> import employees

    >>> obj=employees.Employee(employees.Employee.__name__)

    >>> obj.work

    Employee does stuff

    if下面增加如下代码:

            for klass in Employee,Chef,Server,PizzaRobot:

                    obj=klass(klass.__name__)

                    obj.work()

    代码对应的结果

    #python employees.py

    Employee does stuff

    Chef make food

    Server interface with customer

    PizzaRobot make pizza

    4、类和组合:”有一个“关系 (has a)

    从程序员的角度来看,组合设计到把其他对象嵌入到容器对象内,并使其实现容器方法。

    对设计师来说,组合是另一种表示问题领域中的关系的方法。

    但是组合不是集合的成员关系,而是组件,也是整体的组成部分。

    组合也反映了个组成部分之间的关系,通常称为“有一个”(has a)关系。Python中,“组合”(聚合)就是指内嵌对象集合体。

    组合类一般都提供自己的接口,并通过内嵌的对象来实现接口。

    现在,我们有了员工,把他们放到披萨店。我们的披萨店是一个组合对象,有烤炉,也有服务员和主厨这些员工。当顾客来下单

    时,店里的组件就开始行动:服务员接下订单,主厨制作披萨等。。pizzashop.py模拟

    from employees import PizzaRobot,Server

    class Customer:

            def __init__(self,name):

                    self.name=name

            def order(self,server):

                    print self.name,"oders from",server

            def pay(self,server):

                    print self.name,"pays for item to",server

    class Oven:

            def bake(self):

                    print "oven bakes"

    class PizzaShop:

            def __init__(self):

                    self.server=Server('Pat')

                    self.chef=PizzaRobot('Bob')

                    self.oven=Oven()

            def order(self,name):

                    customer=Customer(name)

                    customer.order(self.server)

                    self.chef.work()

                    self.oven.bake()

                    customer.pay(self.server)

    if __name__=="__main__":

            scene=PizzaShop()

            scene.order('lily')

            print '...'

            scene.order('kelly')

    # python pizzashop.py

    lily oders from <Employee:name=Pat,salary=40000>

    Bob make pizza

    oven bakes

    lily pays for item to <Employee:name=Pat,salary=40000>

    ...

    kelly oders from <Employee:name=Pat,salary=40000>

    Bob make pizza

    oven bakes

    kelly pays for item to <Employee:name=Pat,salary=40000>

    PizzaShop类是容器和控制器,其构造器会创建employees模块中员工类的实例,并将其嵌入。当模块的自我检查程序代码

    调用PizzaShop的order方法时,内嵌的对象会按顺序进行工作。注意:每份订单创建了Customer对象,而且把内嵌的Server

    对象传递给Customer方法,顾客是流动的,但是服务员是披萨店的组成部分。另外,员工也涉及到了继承关系,组合和继承

    是互补工具。

    if检测改写一下,通过参数传递客户给模块

    if __name__=="__main__":

            try:

                    customer=getargv[1]

            except except IndexError:

                    print "please give argv as customer!"

            else:

                    scene=PizzaShop()

                    scene.order(customer)

                    print '...'

    # python pizzashop.py tom

    tom oders from <Employee:name=Pat,salary=40000>

    Bob make pizza

    oven bakes

    tom pays for item to <Employee:name=Pat,salary=40000>

    ...

    5、重访流处理器

    # vim streams.py

    class Processor:

            def __init__(self,reader,writer):

                    self.reader=reader

                    self.writer=writer

            def process(self):

                    while 1:

                            data=self.reader.readline()

                            if not data:break #这里有错误是not

                            data=self.converter(data)

                            self.writer.write(data)  #这里有错误write

            def converter(self,data)

                    assert 0,'converter must be defined

    # vim converter.py                    

    from streams import Processor

    from sys import stdout as output

    class Uppercase(Processor):

                    def converter(self,data):

                            return data.upper()

    if __name__=="__main__":

            Uppercase(openfile('/etc/rc.conf'),output).process()

    错误from sys import open as openfile

    ofreebsd# python converter.py

    Traceback (most recent call last):

      File "converter.py", line 2, in <module>

        from sys import open as openfile

    open是内置函数无需导入、

    6、类和持续性

    持续性:保证数据的持续性,将数据保存在文件或者数据库,继续保存。

    pickle和shelve模块和类实例结合起来使用效果很好,通过单个步骤存储到硬盘上。

    >>> from pizzashop import PizzaShop

    >>> import pickle

    >>> obj=PizzaShop()

    >>> obj.server

    <Employee:name=Pat,salary=40000>

    file=open('pizzashop_data','w')

    >>> pickle.dump(obj.server,file) #写入

    >>> file.close()

    # cat pizzashop_data

    (iemployees

    Server

    p0

    (dp1

    S'salary'

    p2

    I40000

    sS'name'

    p3

    S'Pat'

    p4

    读取

    >> objread=pickle.load(file)      

    >>> print objread

    <Employee:name=Pat,salary=40000>ile=open('pizzashop_data','r')

    pickle机制把内存中的对象转换成序列化的字节流,可以保存在文件中。

    Shelve会自动把对象pickle生成按键读取的数据库,而此数据库导出类似于字典的接口.

    >>> from pizzashop import PizzaShop

    >>> obj=PizzaShop()

    >>> obj.server

    <Employee:name=Pat,salary=40000>

    >>> import shelve

    >>> dbase=shelve.open('datafile')

    >>> dbase['key']=obj.server 写入

    >>> dbase.sync

    <bound method DbfilenameShelf.sync of {'key': <Employee:name=Pat,salary=40000>}>

    # cat datafile.db

    >>> dbase['name']='diege' #写入

    >>> dbase.sync         

    <bound method DbfilenameShelf.sync of {'name': 'diege', 'key': <Employee:name=Pat,salary=40000>}>

    >>> dbase.close()

    # cat datafile.db

    a


    ?

          


    (?n} 


    ¤?¤S'diege'

    p1

    .name(iemployees

    Server

    p1

    (dp2

    S'salary'

    p3

    I40000

    sS'name'

    p4

    S'Pat'

    p5

    读取

    >>> import shelve

    >>> shtest=shelve.open('datafile')

    >>> shtest['key']

    <Employee:name=Pat,salary=40000>

    >>> shtest['name']      

    'diege'

    二、OOP和委托

    所谓的委托,通常就是指控制器对象内嵌其他对象,而把运算请求传给那些对象。控制器负责管理工作。

    在Python中,委托通常是以__getattr__钩子方法实现的,因为这个方法会拦截对不存在属性的读取,包装类(代理类)可以使用

    __getattr__把任意读取转发给包装的对象。包装类包有被包括对象的接口。而且自己也可以增加其他运算。

    class wrapper:

             def __init__(self,object):

                       self.wrapped=object

             def __getattr__(self,attrname):               #__getattr__点号运算,这里重载内置getattr方法打印传入类执行的方法,并把属性请求传入给对象,使用对象默认的方法。委托

                       print 'Trace:',attrname

                       return getattr(self.wrapped,attrname)

    __getattr__会获得属性名称字符串。这个程序代码利用getattr内置函数,以变量名字符串从包裹对象取出属性:getattr(X,Z)

    就像X.N,只不过N是表达式,可以在运行时计算出字符串,而不是变量。getattr(X,Z)类似于X.__dict__[N].

    可以使用这个模块包装类的做法,管理人和带有属性的对象的存取:列表,字典甚至是类和实例。

    在这里wrapper类只是在每个属性读取时打印跟踪信息,【并把属性请求委托给嵌入的wrapped对象。(对象自己的方法)】

    >>> from trac import wrapper

    >>> x=wrapper([1,2,3])

    >>> x.append

    Trace: append

    <built-in method append of list object at 0x2850f7ec>

    >>> x.append(4)

    Trace: append

    >>> for n in x:print n

    ...

    Trace: __iter__

    1

    2

    3

    4

    >>> x.wrapped

    [1, 2, 3, 4]

    >>> x.__dict__

    {'wrapped': [1, 2, 3, 4]}

    >>> x=wrapper({"a":1,"b":2})

    >>> x.keys()

    Trace: keys

    ['a', 'b']

    >>> x.__dict__

    {'wrapped': {'a': 1, 'b': 2}}

    >>> x        

    Trace: __repr__

    {'a': 1, 'b': 2}

    三、多重继承

    在class语句中,首行括号内可以列出一个以上的超类。当这么做时,就在使用所谓的多重继承:类和其实例继承了列出的所有超类的变量。

    搜索属性时,Python会由左到右搜索类首行中的超类,直到找到相符者。【纵向搜索】

    通常意义上讲,多重继承是模拟属于一个集合以上的对象的好办法,例如一个人可以是工程师,作家,音乐家。因为,可以继承这些集合的特性。

    多重继承最常见的用户是作为“混合”超类的通用方法。这类超类一般都称呼混合类:他们提供方法,可以通过继承将其加入应用类。

    >>> x.__class__

    <class trac.wrapper at 0x28503f8c>

    >>> x.__class__.__name__

    'wrapper'

    每个实例都有内置的__class__属性,引用了它所继承的类,而每个类都有__name__属性,用用了首行中的变量名,所以self.__class__.__name__

    是取出实例的类的名称

    >>> x.__class__.__module__      

    'trac'

    >>> x.__module__         

    'trac

    而是用self.__module__或 self.__class__.__module__则取出实例引用模块的名称

    内置id函数传回任意对象的地址(从定义上来将。这就是唯一的对象识别码),从而获得实例的内存地址。

    # vim mytool.py

    class Lister:

            def __repr__(self): #打印重载

                    return ("<Instance of %s,address %s:\n%s" %

                                    (self.__class__.__name__,

                                     id(self),

                                     self.attrnames()) ) #抽象类

            def attrnames(self):

                    result=''

                    for attr in self.__dict__.keys():

                            if attr[:2]=='__':

                                    result=result+"\tname %s=<built-in>\n" % attr

                            else:

                                    result=result+"\tname %s=%s\n" % (attr,self.__dict__[attr])

                    return result

    从这个类衍生的实例会在打印时自动显示器属性。

    >>> from mytool import Lister

    >>> dir(Lister)

    ['__doc__', '__module__', '__repr__', 'attrname']

    >>> class Diege(Lister):

    ...     def __init__(self):

    ...             self.data1='food'

    ...

    >>> y=Diege()

    >>> y

    <Instance of Diege,address 676368300:

            name data1=food

    >

    将__repr_修改为__str__

    >>> y=Diege()

    >>> y

    >>> print y

    <Instance of Diege,address 676368300:

            name data1=food

    >

    Lister类对所写的任何类都有用:即时是已经有超类的类,这里就是多重继承方便之处:把Lister加到类首行的超类列表中(将其混合进来)

    就可以获得其__repr,同时依然继承现有的超类。

    # vim Tsmixin.py

    from mytool import Lister

    class Super:

            def __init__(self):

                    self.data1='diege'

                   

    class Sub(Super,Lister):

            def __init__(self):

                    Super.__init__(self)

                    self.data2="eggs"

                    self.data3=42

    if __name__=='__main__':

            X=Sub()

            print X

    # python Tsmixin.py  

    <Instance of Sub,address 676367724:

            name data1=diege

            name data3=42

            name data2=eggs

    >

    如果稍后你决定扩展Lister的__repr__,也要打印实例继承的所有类属性,那也很安全。因为这是继承的方法,修改Lister.__repr__会自动更新每个导入类,

    并将显示器混合进来的子类的情况。

    总之,混合类相当于模块:可用户在各种客户端的方法包。以下是Lister用在不同类实例上,采用单个继承模式的情况。

    >>> class X(Lister):

    ...     pass

    ... 

    >>> t=x()

    >>> t

    <Instance of X,address 676380940:

    >

    >>> t

    <Instance of X,address 676380940:

            name a=1

            name c=3

            name b=2

    >

    OOP其实就是代码重用,而混合是强大的工具。多重继承是高级功能,如果用的过度或太随意,就变得很复杂。

    四、类是对象:通用对象的工厂

    类是对象,因此它很容易在程序中进行传递,保存在数据库结构中。也可以把类传给产生任意种类对象的函数。

    这类函数在OOP设计领域偶尔称为工厂。

    工厂式的函数或程序代码,在一些情况下很方便,因为他们可以让我们取出并传入没有预先在程序代码中硬编码的类。实际上,这些类在

    编写程序时可能还不存在。抽象类。

    >>> def factory(aClass,*args):

    ...     return apply(aClass,args)

    ...

    >>> class Spam:

    ...     def doit(self,message):

    ...             print message

    ...

    >>> class Person:                   

    ...     def __init__(self,name,job):

    ...             self.name=name

    ...             self.job=job

    >>> object1=factory(Spam)

    >>> object2=factory(Person,"diege","lily")

    这里定义了一个对象生成器函数,称为factory.它预期传入的是类对象(任何对象都行),还有该类构造器的一个或多个参数。

    这个函数使用apply调用该函数并返回实例。

    可以改进之处就是,在构造器调用中支持关键词参数。函数factory能够通过**agrs参数手机参数。当第三个参数传给apply时:

    def factory(aClass,*args,**kagrs):

    ...     return apply(aClass,kargs)

    在Python中一切都是对象。

    五、方法是对象:绑定或无绑定

    方法也是一种对象,很像函数。类方法能有实例或类来读取。实际上Python中就有两种方式。

    无绑定类方法对象:无self

             通过对类进行点号运算从而获取类的函数属性,会传回无绑定(unboud)方法对象。调用该方法时,【必须明确提供实例对象】作为第一个参数。

    绑定实例方法对象:self+函数对

             通过对实例进行全运算从而获取类的函数属性,会传回绑定(bound)方法对象。Python在绑定方法对象中自动把实例和函数打包,所以,不用传递

    实例去调用该方法。实例已拥有该方法。

    这两种方法都是功能齐全的对象,可四处传递,保持在列表内等。执行时两者都需要第一参数的实例(也就是self的值)

    调用绑定方法对象时,Python会自动提供实例来创建绑定方法对象的实例。也就是说绑定方法对象通常都可以和简单函数对象互换,而且对原本就是针对

    函数而编写的接口而言,非常用有。

    >>> class Spam:

    ...     def doit(self,message):

    ...             print message

    >>> object1=Spam()

    >>> object1.doit('hellow world')           

    hellow world

    可以帮这个绑定方法赋值给另一个变量名,然后像简单函数那样进行调用。

    >>> object1=Spam()

    >>> x=object1.doit

    >>> x('hello world')

    hello world

    六、类和模块

    都是命名空间

    模块

                       * 是数据/逻辑套件

                       * 由Python文件或C扩展编写成

                       * 通过导入使用

                       *实现新的对象

                       *由class语句创建

                       *通过调用使用

                       *总是存在于模块中。

    类支持其他模块不支持的功能。例如,运算符重载,产生多个实例以及继承。

    七、类陷阱

    修改类属性的副作用

    多重继承:顺序很重要

    类,方法以及嵌套作用域

    小结:

    委托:把对象包装在代理类中

    组合:控制嵌入的对象

    继承:从其他类中获取行为

    多重继承,绑定方法,工厂函数

  • 相关阅读:
    20181120-1 每周例行报告
    20181113-2 每周例行报告
    20181030-4 每周例行报告
    20181023-3 每周例行报告
    第六周例行报告
    软件功能说明书final修订
    第十二周——例行报告
    PSP总结报告
    第十一周——例行报告
    PSP Daily软件beta版本——基于NABCD评论,及改进建议
  • 原文地址:https://www.cnblogs.com/diege/p/2698425.html
Copyright © 2011-2022 走看看