Python3 面向对象

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。

如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

接下来我们先来简单的了解下面向对象的一些基本特征。


面向对象技术简介

  • 类(Class): 

    用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 实例变量:定义在方法中的变量,只作用于当前实例的类。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟”是一个(is-a)”关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 方法:类中定义的函数。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

对象可以包含任意数量和类型的数据。

类定义

语法格式如下:

class ClassName:
     <statement-1>
     .
     .
     .
     <statement-N>

类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。

类对象

类对象支持两种操作:属性引用和实例化。

属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name

类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:

#!/usr/bin/python3  class MyClass:
     """一个简单的类实例"""
     i = 12345
     def f(self):
 
   return 'hello world'  
# 实例化类 x = MyClass()  
# 访问类的属性和方法 print("MyClass 类的属性 i 为:", x.i) print("MyClass 类的方法 f 输出为:", x.f())

实例化类:


# 实例化类 x = MyClass() 
# 访问类的属性和方法

以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。

执行以上程序输出结果为:

MyClass 类的属性 i 为: 12345 MyClass 类的方法 f 输出为: hello world

很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 __init__() 的特殊方法(构造方法),像下面这样:

def __init__(self):
     self.data = []

类定义了 __init__() 方法的话,类的实例化操作会自动调用 __init__() 方法。所以在下例中,可以这样创建一个新的实例:

x = MyClass()

当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:


>>> class Complex: ...
     def __init__(self, realpart, imagpart): ...
 
   self.r = realpart ...
 
   self.i = imagpart ... 
>>> x = Complex(3.0, -4.5) 
>>> x.r, x.i (3.0, -4.5)

类的方法

在类地内部,使用def关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数:

#!/usr/bin/python3  #类定义 class people:
     #定义基本属性
     name = ''
     age = 0
     #定义私有属性,私有属性在类外部无法直接进行访问
     __weight = 0
     #定义构造方法
     def __init__(self,n,a,w):
 
   self.name = n
 
   self.age = a
 
   self.__weight = w
     def speak(self):
 
   print("%s 说: 我 %d 岁。" %(self.name,self.age))  
# 实例化类 p = people('W3Cschool',10,30) p.speak()

执行以上程序输出结果为:

W3Cschool 说:  10 岁。

继承

Python 同样支持类的继承,如果一种语言不支持继承就,类就没有什么意义。派生类的定义如下所示:

class DerivedClassName(BaseClassName1):
     <statement-1>
     .
     .
     .
     <statement-N>

需要注意圆括号中基类的顺序,若是基类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找基类中是否包含方法。

BaseClassName(示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

class DerivedClassName(modname.BaseClassName):

实例

#!/usr/bin/python3  #类定义 class people:
     #定义基本属性
     name = ''
     age = 0
     #定义私有属性,私有属性在类外部无法直接进行访问
     __weight = 0
     #定义构造方法
     def __init__(self,n,a,w):
 
   self.name = n
 
   self.age = a
 
   self.__weight = w
     def speak(self):
 
   print("%s 说: 我 %d 岁。" %(self.name,self.age))  #单继承示例 class student(people):
     grade = ''
     def __init__(self,n,a,w,g):
 
   #调用父类的构函
 
   people.__init__(self,n,a,w)
 
   self.grade = g
     #覆写父类的方法
     def speak(self):
 
   print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
    s = student('ken',10,60,3) s.speak()

执行以上程序输出结果为:

ken 说:  10 岁了,我在读 3 年级

多继承

Python同样有限的支持多继承形式。多继承的类定义形如下例:

class DerivedClassName(Base1, Base2, Base3):
     <statement-1>
     .
     .
     .
     <statement-N>

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

#!/usr/bin/python3  #类定义 class people:
     #定义基本属性
     name = ''
     age = 0
     #定义私有属性,私有属性在类外部无法直接进行访问
     __weight = 0
     #定义构造方法
     def __init__(self,n,a,w):
 
   self.name = n
 
   self.age = a
 
   self.__weight = w
     def speak(self):
 
   print("%s 说: 我 %d 岁。" %(self.name,self.age))  #单继承示例 class student(people):
     grade = ''
     def __init__(self,n,a,w,g):
 
   #调用父类的构函
 
   people.__init__(self,n,a,w)
 
   self.grade = g
     #覆写父类的方法
     def speak(self):
 
   print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))  #另一个类,多重继承之前的准备 class speaker():
     topic = ''
     name = ''
     def __init__(self,n,t):
 
   self.name = n
 
   self.topic = t
     def speak(self):
 
   print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))  #多重继承 class sample(speaker,student):
     a =''
     def __init__(self,n,a,w,g,t):
 
   student.__init__(self,n,a,w,g)
 
   speaker.__init__(self,n,t)  test = sample("Tim",25,80,4,"Python") test.speak()   #方法名同,默认调用的是在括号中排前地父类的方法

执行以上程序输出结果为:

我叫 Tim,我是一个演说家,我演讲的主题是 Python

方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:

#!/usr/bin/python3  class Parent:
 
  
# 定义父类
    def myMethod(self):
       print ('调用父类方法')  class Child(Parent): 
# 定义子类
    def myMethod(self):
       print ('调用子类方法')  c = Child()
 
    
# 子类实例 c.myMethod()
 
   
# 子类调用重写方法

执行以上程序输出结果为:

调用子类方法

类属性与方法

类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时self.__private_attrs

类的方法

在类地内部,使用def关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数

类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。在类的内部调用 

slef.__private_methods

实例如下:

#!/usr/bin/python3  class JustCounter:
     __secretCount = 0  
# 私有变量
     publicCount = 0
    
# 公开变量
      def count(self):
 
   self.__secretCount += 1
 
   self.publicCount += 1
 
   print (self.__secretCount)  counter = JustCounter() counter.count() counter.count() print (counter.publicCount) print (counter.__secretCount)  
# 报错,实例不能访问私有变量

执行以上程序输出结果为:

1 2 2 Traceback (most recent call last):   File "test.py", line 16, in <module>
     print (counter.__secretCount)  
# 报错,实例不能访问私有变量 AttributeError: 'JustCounter' object has no attribute '__secretCount'

类的专有方法:

  • __init__ : 

    构造函数,在生成对象时调用

  • __del__ : 

    析构函数,释放对象时使用

  • __repr__ : 

    打印,转换

  • __setitem__ : 

    按照索引赋值

  • __getitem__: 

    按照索引获取值

  • __len__: 

    获得长度

  • __cmp__: 

    比较运算

  • __call__: 

    函数调用

  • __add__: 

    加运算

  • __sub__: 

    减运算

  • __mul__: 

    乘运算

  • __div__: 

    除运算

  • __mod__: 

    求余运算

  • __pow__: 

    乘方

运算符重载

Python同样支持运算符重载,我么可以对类的专有方法进行重载,实例如下:

#!/usr/bin/python3  class Vector:
    def __init__(self, a, b):
       self.a = a
       self.b = b
     def __str__(self):
       return 'Vector (%d, %d)' % (self.a, self.b)
 
  def __add__(self,other):
       return Vector(self.a + other.a, self.b + other.b)  v1 = Vector(2,10) v2 = Vector(5,-2) print (v1 + v2)

以上代码执行结果如下所示:

Vector(7,8)

第二十回、PHP基础教程,面向对象的基本概念及类的定义

什么是面向对象

起初,“面向对象”是专指在程序设计中采用封装、继承、抽象等设计方法。可是,这个定义显然不能再适合现在情况。面向对象的思想已经涉及到软件开发的各个方面。如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现(OOP,Object Oriented Program)。

传统的程序开发存在着以下的问题:

1.软件重用性差 ,2.软件可维护性差,3.无很好的扩展性。

面向对象程序设计的要素:

1. 抽象性 , 2. 封装性 ,3.共享性 ,4. 强调对象结构而不是程序结构

面向对象的三大特点(封装,继承,多态)缺一不可。

1.1.2 什么是类

所谓类就是指由变量和作用于变量的函数组成的集合。类提供了一种对现实世界中事物的描述方式。通过有效的使用类,我们可以将描述同一对象的多个变量和函数组合在一起,作为一个整体使用,从而使编写出来的程序更直观,更合理,更易于维护。

举一个比较通俗的例子,我们可以创建一个名为Bike的类来描述现实生活中的一辆自行车。

首先,我们设定该类中的变量包括踏板$pedals,链条$chain,前轮$front wheel,后轮$rear wheel,刹车$brakes,以及手柄$handle。然后,我们创建停车Stop(),加速Accelerate(),左转TurnLeft()和右转TurnRight()等函数。这样,我们就有了一个可以描述自行车这一对象所有行为和属性的类。例如,我们可以将$front wheel 和$rear wheel变量传入TurnLeft()函数,从而得到某种输出结果。

也许有人会问上述例子虽然有趣,但是我们完全可以使用常规的变量和函数实现同样功能,为什么一定要坚持使用这么烦琐的类呢?当然,如果我们在脚本中只需要描述一辆自行车的话,定义一个类似乎没有什么特殊的价值。但是,如果当脚本需要涉及多辆自行车时呢?如果我们还是采用常规的为每辆自行车都定义变量和函数的方法,那么光是跟踪每个变量,确保正确的变量输入正确的函数就是一项相当复杂的工作。相反,如果采用类的方式,就可以有效的减少所需变量的数量。此外,一个已经定义的类还可以被包含在其它文件或脚本中,从而实现代码的重复利用。

基本语法:

class 类名

{

……

}

举例:

class myClass//定义一个名为myClass的类

{

public $name;

var $price;

//为这个类定义了两个成员属性$name和$price

function vod()//为这个类定义了一个成员方法vod

{

return “PHP进阶”;

}

}

注释:

var是PHP4中使用的变量定义形式,在PHP5中被public取代,建议使用public。

6.2.1 类的实例化

类被定义后并不能像自定义函数一样拿来直接的使用,要使用类,必需对其进行实例化,也就是将类所表现的对象进行实例化。

基本语法:

$varName=new 类名(参数列表);

//不需要向类中传递参数时,使用空的小括号,否则将实参放入小括号中

$x=$varName->name;

//使用”->”访问对象中的内容,注意这里name前没有$符号了

阶段实例:

<?php

class myClass

{

public $key;

var $name;

function vod()

{

echo “PHP高级编程进阶”;

}

}

$my=new myClass();

$my->name=”PHP”;

//给myClass类中的属性name赋值”php”

echo $my->name;

//输出属性name的值”PHP”

echo $my->vod();

//访问类中的方法vod(),输出”PHP高级编程进阶”

?>

6.2.2 通过一个类创建一个或多个对象

通过一个类创建一个或多个对象我们需要多次使用new函数。

例:

$my1=new myClass();

$my2=new myClass();

$my3=new myClass();

通过一个myClass创建了三个对象$my1、$my2、$my3。

6.2.3类中$this关键字的使用

$this关键字是用来访问当前对象中的对象属性和对象方法的系统变量。它使用在书写一个类的源码中。

例:

Class myClass

{

public $name;

public $price;

function vod()

{

$this->name;

}

……

$this-vod();

//使用$this访问本身的vod()方法

 ……

}

面向对象进阶(二)

__getattribute__

回顾__getattr__

class Foo:
    def __init__(self,x):
        self.x=x

def __getattr__(self, item):
print(‘执行的是我’)

#

return self.__dict__[item]

f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__

 

class Foo:
    def __init__(self,x):
        self.x=x
    def __getattribute__(self, item):
        print('不管是否存在,我都会执行')
f1=Foo(10)
f1.x
f1.xxxxxx

 

二者同时出现

class Foo:
    def __init__(self,x):
        self.x=x

def __getattr__(self, item):
print(‘执行的是我’)

#

return self.__dict__[item]
def __getattribute__(self, item):
print(‘不管是否存在,我都会执行’)
raise AttributeError(‘哈哈’)
f1=Foo(10)
f1.x
f1.xxxxxx
#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

 

描述符(__get__,__set__,__delete__)

1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议

__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

 

定义一个描述符

class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

 

2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行

class Foo:
    def __get__(self, instance, owner):
        print('触发get')
    def __set__(self, instance, value):
        print('触发set')
    def __delete__(self, instance):
        print('触发delete')

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name=‘egon’
f1.name
del f1.name
#疑问:何时,何地,会触发这三个方法的执行

 

描述符应用之何时?何地?

#描述符Str
class Str:
    def __get__(self, instance, owner):
        print('Str调用')
    def __set__(self, instance, value):
        print('Str设置...')
    def __delete__(self, instance):
        print('Str删除...')

#描述符Int
class Int:
def __get__(self, instance, owner):
print(‘Int调用’)
def __set__(self, instance, value):
print(‘Int设置…’)
def __delete__(self, instance):
print(‘Int删除…’)

class People:
name=Str()
age=Int()
def __init__(self,name,age): #name被Str类代理,age被Int类代理,
self.name=name
self.age=age

#何地?:定义成另外一个类的类属性

#何时?:且看下列演示

p1=People(‘alex’,18)

#描述符Str的使用
p1.name
p1.name=‘egon’
del p1.name

#描述符Int的使用
p1.age
p1.age=18
del p1.age

#我们来瞅瞅到底发生了什么
print(p1.__dict__)
print(People.__dict__)

#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)

 

3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()

class Foo:
    def __set__(self, instance, value):
        
print('set')
    def __get__(self, instance, owner):
        
print('get')

 

二 非数据描述符:没有实现__set__()

class Foo:
    def __get__(self, instance, owner):
        print('get')

 

4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
    1.类属性
    2.数据描述符
    3.实例属性
    4.非数据描述符
    5.找不到的属性触发__getattr__()
 

5 描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
牛刀小试

class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

class People:
name=Str(‘name’)
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
p1=People(‘egon’,18,3231.3)
#调用
print(p1.__dict__)
p1.name

#赋值
print(p1.__dict__)
p1.name=‘egonlin’
print(p1.__dict__)

#删除
print(p1.__dict__)
del p1.name
print(p1.__dict__)

 

拔刀相助

class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

class People:
name=Str(‘name’)
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary

#疑问:如果我用类名去操作属性呢
People.name #报错,错误的根源在于类去操作属性时,会把None传给instance

#修订__get__方法
class Str:
def __init__(self,name):
self.name=name
def __get__(self, instance, owner):
print(‘get—>’,instance,owner)
if instance is None:
return self
return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

class People:
name=Str(‘name’)
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
print(People.name) #完美,解决

 

磨刀霍霍

class Str:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
if not isinstance(value,self.expected_type): #如果不是期望的类型,则抛出异常
raise TypeError(‘Expected %s’ %str(self.expected_type))
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

class People:
name=Str(‘name’,str) #新增类型限制str
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary

p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常

 

大刀阔斧

class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
if not isinstance(value,self.expected_type):
raise TypeError(‘Expected %s’ %str(self.expected_type))
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

class People:
name=Typed(‘name’,str)
age=Typed(‘name’,int)
salary=Typed(‘name’,float)
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary

p1=People(123,18,3333.3)
p1=People(‘egon’,’18’,3333.3)
p1=People(‘egon’,18,3333)

 

大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑

class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

def __set__(self, instance, value):
print(‘set—>’,instance,value)
if not isinstance(value,self.expected_type):
raise TypeError(‘Expected %s’ %str(self.expected_type))
instance.__dict__[self.name]=value
def __delete__(self, instance):
print(‘delete—>’,instance)
instance.__dict__.pop(self.name)

def typeassert(**kwargs):
def decorate(cls):
print(‘类的装饰器开始运行啦——>’,kwargs)
for name,expected_type in kwargs.items():
setattr(cls,name,Typed(name,expected_type))
return cls
return decorate
@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(…)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary

print(People.__dict__)
p1=People(‘egon’,18,3333.3)

 


 

面向对象进阶(一)

isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查obj是否是类 cls 的对象

class Foo(object):
   pass  
obj = Foo()  
isinstance(obj, Foo)

 

issubclass(sub, super)检查sub类是否是 super 类的派生类

class Foo(object):
    pass
class Bar(Foo):
    pass
issubclass(Bar, Foo)

 

反射

1.什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
 

2.py
thon面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
hasattr(object,name)
getattr(object, name, default=None)
setattr(x, y, v)
delattr(x, y)
 

四个方法的使用演示

class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

def sell_house(self):
print(‘%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼’ %self.name)
def rent_house(self):
print(‘%s 黑中介租房子啦,傻逼才租呢’ %self.name)

b1=BlackMedium(‘万成置地’,‘回龙观天露园’)

#检测是否含有某属性
print(hasattr(b1,‘name’))
print(hasattr(b1,‘sell_house’))

#获取属性
n=getattr(b1,‘name’)
print(n)
func=getattr(b1,‘rent_house’)
func()


# getattr(b1,’aaaaaaaa’)
#报错

print(getattr(b1,‘aaaaaaaa’,‘不存在啊’))

#设置属性
setattr(b1,‘sb’,True)
setattr(b1,‘show_name’,lambda self:self.name+‘sb’)
print(b1.__dict__)
print(b1.show_name(b1))

#删除属性
delattr(b1,‘addr’)
delattr(b1,‘show_name’)
delattr(b1,‘show_name111’)#不存在,则报错

print(b1.__dict__)

 

类也是对象

class Foo(object):
    staticField = "old boy"
    def __init__(self):
        self.name = 'wupeiqi'
    def func(self):
        return 'func'
    @staticmethod
    def bar():
        return 'bar'

print getattr(Foo, ‘staticField’)
print getattr(Foo, ‘func’)
print getattr(Foo, ‘bar’)

 

反射当前模块成员

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import sys
def s1():
print ‘s1’
def s2():
print ‘s2’
this_module = sys.modules[__name__]
hasattr(this_module, ‘s1’)
getattr(this_module, ‘s2’)

 

导入其他模块,利用反射查找该模块是否存在某个方法

def test():
    print('from the test')

 

"""
程序目录:
    module_test.py
    index.py
当前文件:
    index.py"""
import module_test as obj
#obj.test()
print(hasattr(obj,'test'))
getattr(obj,'test')()

 

3.为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
 

egon还没有实现全部功能

class FtpClient:
    'ftp客户端,但是还么有实现具体的功能'
    def __init__(self,addr):
        print('正在连接服务器[%s]' %addr)
        self.addr=addr

 

不影响lili的代码编写

#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):
    func_get=getattr(f1,'get')
    func_get()
else:
    print('---->不存在此方法')
    print('处理其他的逻辑')

 

好处二:动态导入模块(基于反射当前模块成员)

import importlib
importlib.import_module('模块名','包名')

 
 

__setattr__,__delattr__,__getattr__

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

def __getattr__(self, item):
print(‘—->
from getattr:你找的属性不存在’
)
def __setattr__(self, key, value):
print(‘—->
from setattr’
)

# self.key=value
#这就无限递归了,你好好想想


# self.__dict__[key]=value
#应该使用它

def __delattr__(self, item):
print(‘—->
from delattr’
)

# del self.item
#无限递归了

self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__)
# 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值

f1.z=3
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__[‘a’]=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx

 

二次加工标准类型(包装)

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

class List(list):
#继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上类型检查'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)
    @property
    def mid(self):
        '新增自己的属性'
        index=len(self)//2
        return self[index]
l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111')
#报错,必须为int类型
print(l.mid)
#其余的方法都继承listl.insert(0,-123)
print(l)
l.clear()
print(l)

二次加工标准类型(基于继承实现)

 

class List(list):
    def __init__(self,item,tag=False):
        super().__init__(item)
        self.tag=tag
    def append(self, p_object):
        if not isinstance(p_object,str):
            raise TypeError
        super().append(p_object)
    def clear(self):
        if not self.tag:
            raise PermissionError
        super().clear()
l=List([1,2,3],False)
print(l)
print(l.tag)
l.append('saf')
print(l)

# l.clear()
#异常
l.tag=True
l.clear()
练习(clear加权限限制)

 

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
授权示范一

import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        self.file=open(filename,mode,encoding=encoding)
    def write(self,line):
        t=time.strftime('%Y-%m-%d %T')
        self.file.write('%s %s' %(t,line))

def __getattr__(self, item):
return getattr(self.file,item)

f1=FileHandle(‘b.txt’,‘w+’)
f1.write(‘你好啊’)
f1.seek(0)
print(f1.read())
f1.close()

 

授权示范二

#我们来加上b模式支持
import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        if 'b' in mode:
            self.file=open(filename,mode)
        else:
            self.file=open(filename,mode,encoding=encoding)
        self.filename=filename
        self.mode=mode
        self.encoding=encoding

def write(self,line):
if ‘b’ in self.mode:
if not isinstance(line,bytes):
raise TypeError(‘must be bytes’)
self.file.write(line)

def __getattr__(self, item):
return getattr(self.file,item)

def __str__(self):
if ‘b’ in self.mode:
res=“<_io.BufferedReader name=’%s’>” %self.filename
else:
res=“<_io.TextIOWrapper name=’%s’ mode=’%s’ encoding=’%s’>” %(self.filename,self.mode,self.encoding)
return res
f1=FileHandle(‘b.txt’,‘wb’)

# f1.write(‘你好啊啊啊啊啊’)
#自定制的write,不用在进行encode转成二进制去写了,简单,大气

f1.write(‘你好啊’.encode(‘utf-8’))
print(f1)
f1.close()

 


 

面向对象之继承

初识继承

什么是继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”遗传”父类的属性,从而解决代码重用问题(比如练习7中Garen与Riven类有很多冗余的代码)
python中类的继承分为:单继承和多继承

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass

 
查看继承

SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

 

提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

ParentClass1.__bases__
(<class 'object'>,)
ParentClass2.__bases__
(<class 'object'>,)

 

继承与抽象(先抽象再继承)

继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
面向对象之继承
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
面向对象之继承
 

继承与重用性

使用继承来重用代码比较好的例子

==========================第一部分
例如
  猫可以:喵喵叫、吃、喝、拉、撒
  狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
#猫和狗有大量相同的内容
class 猫:
    def 喵喵叫(self):
        print '喵喵叫'
    def(self):
        
# do something
    def(self):
        
# do something
    def(self):
        
# do something
    def(self):
        
# do something
class 狗:
    def 汪汪叫(self):
        print '喵喵叫'
    def(self):
        
# do something
   def(self):
        
# do something
    def(self):
        
# do something
    def(self):
        
# do something
==========================第二部分
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
  动物:吃、喝、拉、撒
     猫:喵喵叫(猫继承动物的功能)
     狗:汪汪叫(狗继承动物的功能)
伪代码如下:
class 动物:
    def(self):
        
# do something
    def(self):
        
# do something
    def(self):
        
# do something
    def(self):
        
# do something

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
    def 喵喵叫(self):
        print '喵喵叫'     

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
    def 汪汪叫(self):
        print '喵喵叫'
==========================第三部分
#继承的代码实现
class Animal:
    def eat(self):
        print("%s 吃 " %self.name)
    def drink(self):
        
print ("%s 喝 " %self.name)
    def shit(self):
        
print ("%s 拉 " %self.name)
    def pee(self):
        
print ("%s 撒 " %self.name)
class Cat(Animal):
   def __init__(self, name):
        self.name = name
        self.breed = '猫'
    def cry(self):
        print('喵喵叫')
class Dog(Animal):
    def __init__(self, name):
        self.name = name
        self.breed='狗'
    def cry(self):
        print('汪汪叫')

#
########
# 执行
#########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()

 

在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value
    def move_forward(self):
        print('%s move forward' %self.nickname)
    def move_backward(self):
        print('%s move backward' %self.nickname)
    def move_left(self):
        print('%s move forward' %self.nickname)
    def move_right(self):
        print('%s move forward' %self.nickname)
    def attack(self,enemy):
        enemy.life_value-=self.aggressivity
class Garen(Hero):
    pass
class Riven(Hero):
    pass
g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

”’
运行结果
”’

 

提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找…直到最顶级的父类。
 

重点!!!:再看属性查找

class Foo:
    def f1(self):
        print('Foo.f1')
    def f2(self):
        print('Foo.f2')
        self.f1()
class Bar(Foo):
    def f1(self):
        print('Foo.f1')

b=Bar()
b.f2()

 

 派生

当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

class Riven(Hero):
    camp='Noxus'
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)

 

在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值

class Riven(Hero):
    camp='Noxus'
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能
        self.skin=skin #新属性
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        Hero.attack(self,enemy) #调用功能
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)
r1=Riven('锐雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)

”’
运行结果
锐雯雯 is flying
比基尼

”’

 

组合与重用性

软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Equip: #武器装备类
    def fire(self):
print('release Fire skill')

class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
camp=‘Noxus’
def __init__(self,nickname):
self.nickname=nickname
self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性
r1=Riven(‘锐雯雯’)
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
release Fire skill

 

组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种’是’的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3…
 

例子:继承与组合

class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))
class Teacher(People):
    def __init__(self,name,age,sex,job_title):
        People.__init__(self,name,age,sex)
        self.job_title=job_title
        self.course=[]
        self.students=[]
class Student(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course=[]
egon=Teacher('egon',18,'male','沙河霸道金牌讲师')
s1=Student('牛榴弹',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#为老师egon添加学生s1
egon.students.append(s1)
#使用
for obj in egon.course:
    obj.tell_info()

 

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
 

接口与归一化设计

什么是接口
java中的interface

=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java
/*
* Java的Interface接口的特征:
* 1)是一组功能的集合,而不是一个功能
* 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作
* 3)接口只定义函数,但不涉及函数实现
* 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */

package com.oo.demo;

public interface IAnimal{
public void eat();

public void run();

public void sleep();

public void speak();

}

=================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口
package com.oo.demo;

public class Pig implements IAnimal{ //如下每个函数都需要详细实现
public void eat(){
System.out.println(“Pig like to eat grass”);

}

public void run(){
System.out.println(“Pig run: front legs, back legs”);

}

public void sleep(){
System.out.println(“Pig sleep 16 hours every day”);

}

public void speak(){
System.out.println(“Pig can not speak”);

}
}

=================第三部分:Person2.java
/*
*实现了IAnimal的“人”,有几点说明一下:
* 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样
* 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */

package com.oo.demo;

public class Person2 implements IAnimal{
public void eat(){
System.out.println(“Person like to eat meat”);

}

public void run(){
System.out.println(“Person run: left leg, right leg”);

}

public void sleep(){
System.out.println(“Person sleep 8 hours every dat”);

}

public void speak(){
System.out.println(“Hellow world, I am a person”);

}
}

=================第四部分:Tester03.java
package com.oo.demo;

public class Tester03{
public static void main(String[] args) {
System.out.println(“===This is a person===”);

IAnimal person = new Person2();

person.eat();

person.run();

person.sleep();

person.speak();

System.out.println(“n===This is a pig===”);

IAnimal pig = new Pig();

pig.eat();

pig.run();

pig.sleep();

pig.speak();

}
}

 

PS:hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式方法入口
 
为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
 

归一化的好处在于:
1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
 

模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface
twisted的twistedinternetinterface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns

 

也可以使用继承:
继承的两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass
    def write(self): #定义接口函数write
        pass
class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')
    def write(self):
        print('文本数据的读取方法')
class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')
    def write(self):
        print('硬盘数据的读取方法')
class Process(Interface):
    def read(self):
        print('进程数据的读取方法')
    def write(self):
        print('进程数据的读取方法')

 

上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类
 

抽象类

什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
 

为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
 

在python中实现抽象类

#_*_coding:utf-8_*_
#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
all_type=‘file’
   @abc.abstractmethod
#定义抽象方法,无需实现功能

def read(self):
‘子类必须定义读功能’
pass

   @abc.abstractmethod
#定义抽象方法,无需实现功能

def write(self):
‘子类必须定义写功能’
pass


# class Txt(All_file):


#     pass

#

# t1=Txt()
#报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print(‘文本数据的读取方法’)

def write(self):
print(‘文本数据的读取方法’)

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print(‘硬盘数据的读取方法’)

def write(self):
print(‘硬盘数据的读取方法’)

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print(‘进程数据的读取方法’)

def write(self):
print(‘进程数据的读取方法’)

wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

 

抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
 

继承实现的原理

继承顺序
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
面向对象之继承
面向对象之继承

class A(object):
    def test(self):
        print('from A')
class B(A):
    def test(self):
        print('from B')
class C(A):
    def test(self):
        print('from C')
class D(B):
    def test(self):
        print('from D')
class E(C):
    def test(self):
        print('from E')
class F(D,E):
    
# def test(self):
    
#    
print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

 

继承原理(python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>,
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>,
<class 'object'>]

 

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
 

子类中调用父类的方法

方法一:指名道姓,即父类名.父类方法()

#_*_coding:utf-8_*_
class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power
     def run(self):
        
print('开动啦...')
class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line
    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        Vehicle.run(self)
line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()

 

方法二:super()

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

def run(self):
print(‘开动啦…’)

class Subway(Vehicle): #地铁
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
super().__init__(name,speed,load,power)
self.line=line

def run(self):
print(‘地铁%s号线欢迎您’ %self.line)
super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
pass

line13=Subway(‘中国地铁’,‘180m/s’,‘1000人/箱’,‘电’,13)
line13.run()

强调:二者使用哪一种都可以,但最好不要混合使用
 


python面向对象(二)

属性查找

类有两种属性:数据属性和函数属性

1. 类的数据属性是所有对象共享的
2. 类的函数属性是绑定给对象用的

class BeijingStudent:
    school='Beijing'
    def learn(self):
        print('is learning')
        
    def eat(self):
        print('is eating')
    
    def sleep(self):
        print('is sleeping')
s1=BeiJingStudent()
s2=BeiJingStudent()
s3=BeiJingStudent()
#类的数据属性是所有对象共享的,id都一样
print(id(BeijingStudent.school))

print(id(s1.school))
print(id(s2.school))
print(id(s3.school))

”’
4377347328
4377347328
”’

#类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样
#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
print(BeijngStudent.learn)
print(s1.learn)
print(s2.learn)
print(s3.learn)
”’
<
function BeijngStudent.learn at 0x1021329d8>
<bound method BeijngStudent.learn of <__main__.BeijngStudent object at 0x1021466d8>>
<bound method BeijngStudent.learn of <__main__.BeijngStudent object at 0x102146710>>
<bound method BeijngStudent.learn of <__main__.BeijngStudent object at 0x102146748>>”’

 
python面向对象(二)
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常
 

绑定到对象的方法的特殊之处

#改写
class BeijingStudent:
    school='Beijing'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def learn(self):
        print('%s is learning' %self.name) #新增self.name

def eat(self):
print(‘%s is eating’ %self.name)

def sleep(self):
print(‘%s is sleeping’ %self.name)
s1=BeijingStudent(‘李坦克’,‘男’,18)
s2=BeijingStudent(‘王大炮’,‘女’,38)
s3=BeijingStudent(‘牛榴弹’,‘男’,78)

 

类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

BeijingStudent.learn(s1)
#李坦克 is learning
BeijingStudent.learn(s2)
#王大炮 is learning
BeijingStudent.learn(s3)
#牛榴弹 is learning

 

类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)

s1.learn()
#等同于BeijingStudent.learn(s1)
s2.learn()
#等同于BeijingStudent.learn(s2)
s3.learn()
#等同于BeijingStudent.learn(s3)

 

注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
类即类型
提示:python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类

#类型dict就是类dict
list
<class 'list'>
#实例化的到3个对象l1,l2,l3
l1=list()
l2=list()
l3=list()

#三个对象都有绑定方法append,是相同的功能,但内存地址不同
l1.append
<built-in method append of list object at 0x10b482b48>l2.append
<built-in method append of list object at 0x10b482b88>l3.append
<built-in method append of list object at 0x10b482bc8>
#操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3
l1.append(3)
l1
[3]
l2
[]
>>>l3
[]
#调用类list.append(l3,111)等同于l3.append(111)
list.append(l3,111) #l3.append(111)
l3
[111]

 

对象之间的交互

class Garen:        #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;


    camp='Demacia'  #所有玩家的英雄(盖伦)的阵营都是Demacia;


    def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;


        self.nickname=nickname  #为自己的盖伦起个别名;


        self.aggressivity=aggressivity #英雄都有自己的攻击力;


        self.life_value=life_value #英雄都有自己的生命值;


    def attack(self,enemy):   #普通攻击技能,enemy是敌人;


        enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。

 

我们可以仿照garen类再创建一个Riven类

class Riven:
    camp='Noxus'  #所有玩家的英雄(锐雯)的阵营都是Noxus;


    def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54;


        self.nickname=nickname  #为自己的锐雯起个别名;


        self.aggressivity=aggressivity #英雄都有自己的攻击力;


        self.life_value=life_value #英雄都有自己的生命值;


    def attack(self,enemy):   #普通攻击技能,enemy是敌人;


        enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。

 

实例出俩英雄

g1=Garen('草丛伦')
r1=Riven('锐雯雯')

 

交互:锐雯雯攻击草丛伦,反之一样

g1.life_value
455
r1.attack(g1)
g1.life_value
401

 

补充:

garen_hero.Q()称为向garen_hero这个对象发送了一条消息,让他去执行Q这个功能,类似的有:
  garen_hero.W()
  garen_hero.E()
  garen_hero.R()

 


 

python面向对象(一)

什么是面向对象的程序设计及为什么要有它

面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么……面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。
优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
 

面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
python面向对象(一)

类与对象

类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看
在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
按照上述步骤,我们来定义一个类
 
在现实世界中:先有对象,再有类

对象1:李坦克
    特征:
        学校=北京大学
        姓名=李坦克
        性别=男
        年龄=18
    技能:
        学习
        吃饭
        睡觉

对象2:王大炮
特征:
学校=beijing
姓名=北京大学
性别=女
年龄=38
技能:
学习
吃饭
睡觉

现实中的北京大学学生类
相似的特征:
学校=北京大学
相似的技能:
学习
吃饭
睡觉

 

在程序中:先定义类,后产生对象

#在程序中,务必保证:先定义(类),后使用(产生对象)
PS:
  1. 在程序中特征用变量标识,技能用函数标识
  2. 因而类中最常见的无非是:变量和函数的定义

#程序中的类
class BeiJingStudent:
school=‘oldboy’
def learn(self):
print(‘is learning’)
def eat(self):
print(‘is eating’)
def sleep(self):
print(‘is sleeping’)
#注意:
1.类中可以有任意python代码,这些代码在类定义阶段便会执行
2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过BeiJingStudent.__dict__查看
3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
4.点是访问属性的语法,类中定义的名字,都是类的属性

#程序中类的用法
.:专门用来访问属性,本质操作的就是__dict__
BeiJingStudent.school #等于经典类的操作BeiJingStudent.__dict__[‘school’]
BeiJingStudent.school=‘Oldboy’ #等于经典类的操作BeiJingStudent.__dict__[‘school’]=’Oldboy’
BeiJingStudent.x=1 #等于经典类的操作BeiJingStudent.__dict__[‘x’]=1
del BeiJingStudent.x #等于经典类的操作BeiJingStudent.__dict__.pop(‘x’)

#程序中的对象
#调用类,或称为实例化,得到对象
s1=BeiJingStudent()
s2=BeiJingStudent()
s3=BeiJingStudent()

#如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__
#注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值
class BeiJingStudent:
……
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
……
s1=BeiJingStudent(‘李坦克’,‘男’,18) #先调用类产生空对象s1,然后调用BeiJingStudent.__init__(s1,’李坦克’,’男’,18)
s2=BeiJingStudent(‘王大炮’,‘女’,38)
s3=BeiJingStudent(‘牛榴弹’,‘男’,78)
#程序中对象的用法
#执行__init__,s1.name=’牛榴弹’,很明显也会产生对象的名称空间
s2.__dict__
{‘name’: ‘王大炮’, ‘age’: ‘女’, ‘sex’: 38 }

s2.name #s2.__dict__[‘name’]
s2.name=‘王三炮’ #s2.__dict__[‘name’]=’王三炮’
s2.course=‘python’ #s2.__dict__[‘course’]=’python’
del s2.course #s2.__dict__.pop(‘course’)

 

细说__init__方法

#方式一、为对象初始化自己独有的特征
class People:
    country='China'
    x=1
    def run(self):
        print('----->', self)


# 实例化出三个空对象

obj1=People()
obj2=People()
obj3=People()


# 为对象定制自己独有的特征

obj1.name=‘egon’
obj1.age=18
obj1.sex=‘male’

obj2.name=‘lxx’
obj2.age=38
obj2.sex=‘female’

obj3.name=‘alex’
obj3.age=38
obj3.sex=‘female’


#
print(obj1.__dict__)


#
print(obj2.__dict__)


#
print(obj3.__dict__)


#
print(People.__dict__)

#方式二、为对象初始化自己独有的特征
class People:
country=‘China’
x=1
def run(self):
print(‘—–>’, self)


# 实例化出三个空对象

obj1=People()
obj2=People()
obj3=People()


# 为对象定制自己独有的特征

def chu_shi_hua(obj, x, y, z): #obj=obj1,x=’egon’,y=18,z=’male’
obj.name = x
obj.age = y
obj.sex = z

chu_shi_hua(obj1,‘egon’,18,‘male’)
chu_shi_hua(obj2,‘lxx’,38,‘female’)
chu_shi_hua(obj3,‘alex’,38,‘female’)

#方式三、为对象初始化自己独有的特征
class People:
country=‘China’
x=1

def chu_shi_hua(obj, x, y, z): #obj=obj1,x=’egon’,y=18,z=’male’
obj.name = x
obj.age = y
obj.sex = z

def run(self):
print(‘—–>’, self)

obj1=People()

#
print(People.chu_shi_hua)

People.chu_shi_hua(obj1,‘egon’,18,‘male’)

obj2=People()
People.chu_shi_hua(obj2,‘lxx’,38,‘female’)

obj3=People()
People.chu_shi_hua(obj3,‘alex’,38,‘female’)


# 方式四、为对象初始化自己独有的特征

class People:
country=‘China’
x=1

def __init__(obj, x, y, z): #obj=obj1,x=’egon’,y=18,z=’male’
obj.name = x
obj.age = y
obj.sex = z

def run(self):
print(‘—–>’, self)

obj1=People(‘egon’,18,‘male’) #People.__init__(obj1,’egon’,18,’male’)
obj2=People(‘lxx’,38,‘female’) #People.__init__(obj2,’lxx’,38,’female’)
obj3=People(‘alex’,38,‘female’) #People.__init__(obj3,’alex’,38,’female’)


# __init__方法


# 强调:


#   1、该方法内可以有任意的python代码


#   2、一定不能有返回值

class People:
country=‘China’
x=1

def __init__(obj, name, age, sex): #obj=obj1,x=’egon’,y=18,z=’male’

# if type(name) is not str:


#     raise TypeError(‘名字必须是字符串类型’)

obj.name = name
obj.age = age
obj.sex = sex

def run(self):
print(‘—–>’, self)


# obj1=People(‘egon’,18,’male’)

obj1=People(3537,18,‘male’)


#
print(obj1.run)


# obj1.run()
#People.run(obj1)


#
print(People.run)

 

补充
1. 站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析
2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类……
3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
 

类的特殊属性

#python为类内置的特殊属性
类名.__name__
# 类的名字(字符串)
类名.__doc__
# 类的文档字符串
类名.__base__
# 类的第一个父类(在讲继承时会讲)
类名.__bases__
# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__
# 类的字典属性
类名.__module__
# 类定义所在的模块
类名.__class__
# 实例对应的类(仅新式类中)

类的特殊属性(了解即可)

 

数据与专门操作该数据的功能组合到一起

#1、在没有学习类这个概念时,数据与功能是分离的
def exc1(host,port,db,charset):
    conn=connect(host,port,db,charset)
    conn.execute(sql)
    return xxx

def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx

#每次调用都需要重复传入一堆参数
exc1(‘127.0.0.1’,3306,‘db1’,‘utf8’,‘select * from tb1;

‘)
exc2(‘127.0.0.1’,3306,‘db1’,‘utf8’,‘存储过程的名字’)

#2、我们能想到的解决方法是,把这些变量都定义成全局变量
HOST=‘127.0.0.1
PORT=3306
DB=‘db1’
CHARSET=‘utf8’

def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx

def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx

exc1(HOST,PORT,DB,CHARSET,‘select * from tb1;

‘)
exc2(HOST,PORT,DB,CHARSET,‘存储过程的名字’)

#3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了

class MySQLHandler:
def __init__(self,host,port,db,charset=‘utf8’):
self.host=host
self.port=port
self.db=db
self.charset=charset
def exc1(self,sql):
conn=connect(self.host,self.port,self.db,self.charset)
res=conn.execute(sql)
return res

def exc2(self,sql):
conn=connect(self.host,self.port,self.db,self.charset)
res=conn.call_proc(sql)
return res

obj=MySQLHandler(‘127.0.0.1′,3306,‘db1’)
obj.exc1(‘select * from tb1;

‘)
obj.exc2(‘存储过程的名字’)

#改进
class MySQLHandler:
def __init__(self,host,port,db,charset=‘utf8’):
self.host=host
self.port=port
self.db=db
self.charset=charset
self.conn=connect(self.host,self.port,self.db,self.charset)
def exc1(self,sql):
return self.conn.execute(sql)

def exc2(self,sql):
return self.conn.call_proc(sql)

obj=MySQLHandler(‘127.0.0.1’,3306,‘db1’)
obj.exc1(‘select * from tb1;

‘)
obj.exc2(‘存储过程的名字’)

 


第六十九讲 细说List(1)

五一小长假想必大家都回来了,不知道哪天上一讲留给大家的问题是不是都解决了呢?其实这个问题不难,如果我们用C的过程式来设计,这个问题也不难决解,当然为了前进的车轮,这一点我就不说了,如果有朋友用C来写了这个程序,没关系,正好可以和我们今天的实现来对比一下,哪一个更为方便一些,我想大家看完之后一切都会明了。

从面向对象的原则出发,我们可以用类来表示概念,所以,我们可以定义一个抽象类:
———————————-
class Shape{
public:

Shape();



const std::string GetName() const{

return m_name;


}

virtual const double GetArea() const = 0;



virtual copy() const = 0;



virtual ~Shape();


protected:

std::string m_name;


};


———————————–

这个抽象类应该是很好理解的吧,GetName()正是我们希望得到的一个形状的名称,又由于每个形状返回的都是m_name,所以我们将他声明为protected,接下来我们可以定义我们的Circle和Rectangle了。
———————————-
class Circle:public Shape{
public:

Circle():mRadiu(1){

m_name = "

Circle"

;


}


const double GetArea() const{

return PI*mRadiu*mRadiu;


}

void SetRadiu(const double&

d){

mRadiu = d;


}


Shape* copy() const{

return new Circle(*this);


}
private:


double mRadiu;



};


———————————–

也没啥好说的,当然我们得定义好PI:
———————————–
const double PI = 3.1415926;


———————————–
将这句代码放在我们的class的最前面。
我们再来定义一个Rectangle:
————————————
class Rectangle:public Shape{
public:

Rectangle():m_Length(1),m_Width(1){

m_name = "

Rectangle"

;


}

const double GetArea() const{

return m_Length*m_Width;


}

void SetRect(const double&

l,const double&

w){

m_Length = l;


m_Width = w;


}

Shape* copy() const{

return new Rectangle(*this);


}

private:

double m_Length,m_Width;



};


————————————–

我们上面这个class有个地方不好,如果要拿出去给别人用的话,因为我们的SetRect()这个函数不够人性化,通常我会选择先初始化长再初始化宽,但是别人会这样做吗?所以这个方法不人性化,那么怎么定义一个让人易用的接口呢?我们将初始化的顺序交给使用该类的人。所以我们修改一下SetRect,我们让他成为两个函数:
—————————————–
Rectangle&

SetLength(const double l) const;


Rectangle&

SetWidth(const double w) const;


——————————————

有这两个函数后,我们可以按随意的顺序来初始化长宽,而且我们还可以连写:
————————————-
Rectangle r;


r.SetLength(7).SetWidth(6);


————————————–

通常情况下,我们都认为这个图形库算是成型了(当然我们可以添加其他的图形进来),至少有了上面的这些之后,我们就可以使用了:
———————————
Circle c;


Rectangle r;


———————————

当然,这样是没任何问题,但是,当我们要管理这些图形的时候会遇见问题,我们不可以这样用:
—————————-
Shape m_shape[N];


m_shape[0] = c;


m_shape[1] = r;


—————————-

为什么我们会说上面的使用是错的呢?因为Shape是个抽象类,所以是不能够有对象的,就算是有,也不是我们想要的,还记得我们以前说过的切割问题吗?当然还有一种经典也是比较传统的做法,那就是指针或者引用,当然这里我们只能用指针:
———————————
Shape* p1 = new Circle(c);


Shape* p2 = new Rectangle(r);


——————————–
我们这样使用解决了上面我们所说的问题,但是却又带来了另一个问题,Shape的指针是依附于对象的存在的,同时还带来内存管理的问题,如果还不够明白,我们再来看看下面的问题:
——————————–
Shape* m_shape[10];

//使用指针数组可以达到上面的目的
Shape* p1;


Shape* p2;


……
m_shape[i] = new Rectangle(pi);

//这两句话是为了说明问题,知道这个是怎么回事就好。
m_shape[j] = new Circle(pj);


——————————–
上面的代码看上还没什么问题,但接下来,如果我们想要让m_shape[n]指向一个我们新建的一个Shaped对象(将设我们不知道这个类型到底是Circle还是Rectangle),这个对象和m_shape[m]所指的一样,那么我们该怎么用呢?下面的两种方法都不能用的:
——————————–
if(n != m){

delete m_shape[n];



m_shape[n] = m_shape[m];


}
//下面的还是不能用//
if(n != m){

delete m_shape[n];



m_shape[n] = new Shape(m_shape[m]);


}
——————————-
第一个我们不能用,是因为这样一来我们的m_shape[n]和m_shape[m]指向同一个对象,这在一个数组里面是很危险的,第二种方法又回到我们早些时候的问题,因为Shape是个抽象类,他不该有对象,那么怎么解决这个问题呢?这就是我们要引入的代理类,我们用一个类来封装Shape的指针,也就是相当于代理了所有Shape的派生类对象,但是这之前我们还得依赖一个函数,一个复制虚函数,我们希望通过这个函数可以从任何派生类中得到Shape的指针:
———————————-
virtual Shape* copy() const = 0;


———————————-
在派生类中,这个函数的实现很简单,比如:
———————————–
Shape* Circle::copy() const{

return new Circle(*this);


}
———————————–
现在有了这个复制虚函数之后,我们可以很简单的写出一个代理类:
———————————–
class My_Shape{
public:

My_Shape();



My_Shape(const Shape&

s);

//这个函数很重要

My_Shape(const My_Shape&

ms);



My_Shape&

operator=(const Shape&

s);



My_Shape&

operator=(const My_Shape&

ms);



const std::string GetName() const;



const double GetArea() const;



~My_Shape();

private:

Shape *p;



};


———————————-
如果大家从开始都跟着我们的课堂的脚步走的话,那么现在我就不再说那么具体了,我直接贴出实现代码来:
———————————-
#include "

Shape.h"


using namespace My_Code;

My_Shape::My_Shape():p(0){
}

My_Shape::~My_Shape(){

delete p;


}

My_Shape::My_Shape(const Shape&

s):p(s.copy()){
}

My_Shape::My_Shape(const My_Shape&

ms):
p(ms.p?ms.p->copy():0){
}

My_Shape&

My_Shape::operator=(const My_Shape&

ms){

if(this != &

ms){

delete p;


p = ms.p?ms.p->copy():0;


}

return *this;


}

const std::string My_Shape::GetName() const{

if(p==0)

throw "

invalid data!!!"

;


return p->GetName();


}

const double My_Shape::GetArea() const{

if(p==0)

throw "

invalid data!!!"

;


return p->GetArea();


}

My_Shape&

My_Shape::operator=(const Shape&

s){

p = s.copy();


return *this;


}
———————————
到现在,我们可以不用担心Shape是个虚函数了的问题了,他不但可以像早些时候的那样使用,还可以和容器使用,比如我们可以这样用:
———————————
list<My_Shape>mlist
Circle c;


c.SetRadiu(10);


Rectangle r;


r.SetRect(6,5);


mlist.push_back(c);


mlist.push_back(r);


list<My_Shape>::iterator it = mlist.begin();


while(it != mlist.end()){

cout<<it->GetName()<<"

:"

<<it->GetArea()<<endl;


it++;


}
———————————
这个例子有些简单,但却实用,如果大家好好理解的话。
这就是我们上一讲留给大家的问题,大家觉得怎么样呢?是不是和想象中的有些不一样呢?还是认为是我在小题大做了呢?这个问题留给大家去思考。
list,下一讲正式开始说吧。
============================
回复D直接查看目录


原文始发于微信公众号(

C/C++的编程教室

):第六十九讲 细说List(1)

|