day07_20170611_类与对象

一、练习

练习1、对象个数计功能(每次实例化计一次数)

class Foo:
    count =0
    def __init__(self,name):
        print(self.count)
        #count+=1#全局的count
        #self.count=10#对象自己
        Foo.count+=1
        self.name = name
obj1=Foo('egon1')#Foo.count+=1 Foo.count=1
obj2=Foo('egon2')#Foo.count+=1 Foo.count=2
print(Foo.count)
print(obj1.count)
print(obj2.count)

练习2、定义一个学生类

class Student:
    def __init__(self,ID,name,age):
        self.id = ID
        self.name = name
        self.age = age
s1 = Student(1,'egon',18)
s2 = Student(2,'alex',10000)
print(s1.id)
print(s1.name)
print(s1.age)

练习3、定义英雄联盟里面的两类英雄

class Garen:
    camp = 'Demacia'
    def __init__(self,nickname,life_value=200,aggressivity=100):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity

    def attack(self,enemy):
        enemy.life_value -= self.aggressivity#攻击的时候别人减血,本身减攻击力

练习4、两个对象之间的交互

#对象之间的交互
class Garen:
    camp = 'Demacia'
    def __init__(self,nickname,life_value=200,aggressivity=100):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity

    def attack(self,enemy):
        enemy.life_value -= self.aggressivity#攻击的时候别人减血,本身减攻击力

class Riven:
    camp = 'Noxus'
    def __init__(self,nickname,life_value=100,aggressivity=100):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity

    def attack(self,enemy):
        enemy.life_value -= self.aggressivity#攻击的时候别人减血,本身减攻击力

g = Garen('ddd')
r = Riven('333')
print(r.life_value)
g.attack(r)#发送了一条消息,称为向g发送了attack
print(r.life_value)

二、继承

part1:什么是继承?

继承是一种创建新类的方式,新建的类可以继承一个或多个父类,父类又可以称为基类或超类,新建的类称为派生类或子类。

#继承的格式
class ParentClass1:#定义子类
    pass
class ParentClass2:#定义子类
    pass
class SubClass1(ParentClass1):#继承一个父类
    pass
class SubClass2(ParentClass1,ParentClass2):#继承多个父类
    pass
#怎么查看继承的关系?
print(SubClass1.__bases__)#输出结果:(<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__)#输出结果:(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

Python2和Python3的区别:
Python2中类分为两种,分别是:新式类与经典类;python3中类全都是新式类。

Python2中新式类与经典类格式如下代码:

#Foo以及它的子类都叫新式类
class Foo(object):#代表Foo继承了object类
    pass

#经典类
class Bar:#后面不带括号的没有继承的都称之为经典类
    pass

Python3中新式类的格式如下代码:

#在Python3中,类名后不写东西,默认继承的object类
class Foo:
    pass
print(Foo.__bases__)#输出结果:(<class 'object'>,)

part2:怎么寻找继承关系?

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

抽象即抽取类似或者说是比较像的部分。抽象分为两个层次:

1.将奥巴马和梅西这俩对象比较像的部分抽取成类;

2.将人,猪,狗这三个类比较像的部分抽取成父类。

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类。

在现实中是从下而上的找继承关系,但是在程序中要先有父类,然后才能有子类继承,那么来定义一下:

创建人、猪、狗的类,如下:

class people:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('say hello')

class pig:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('哼哼哼')


class Dog:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    def eat(self):
        print('eating')

    def talk(self):
        print('汪汪汪')

结果发现类都重复了,那么就可以如下操作:

class Animal:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('%s正在叫'%self.name)
class People(Animal):
    pass
class Pig(Animal):
    pass
class Dog(Animal):
    pass
peo1 = People('alex',18,'male')#这样会触发People自己的init方法  People.__init__(),实际上调的是父类的init
pig1 = Pig('wupeiqi',20,'female')
dog1 =Dog('yuanhao',30,'male')

peo1.talk()
pig1.talk()
dog1.talk()

继承的好处:减少代码的冗余

但是这样下面的子类就都是一样的了,如果不想一样,比如people有学历属性:

class Animal:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('%s正在叫'%self.name)
class People(Animal):#子类调父类的方法
    def __init__(self,name,age,sex,education):
        Animal.__init__(self,name,age,sex)
        self.education =education
class Pig(Animal):
    pass
class Dog(Animal):
    pass
peo1 = People('alex',18,'male','小学肄业')#这样会触发People自己的init方法  People.__init__(),实际上调的是父类的init
pig1 = Pig('wupeiqi',20,'female')
dog1 =Dog('yuanhao',30,'male')
print(peo1.education)#输出结果:小学肄业

在子类定义新的属性,覆盖掉父类的属性,称为派生。

talk方法,人,猪,狗叫的都是不同的,所以如下代码:

class Animal:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('%s正在叫'%self.name)
class People(Animal):#子类调父类的方法
    def __init__(self,name,age,sex,education):
        Animal.__init__(self,name,age,sex)
        self.education =education
    def talk(self):
        Animal.talk(self)#说明people即可用自己的方法,同时也可以用父类的方法
        print('%s say hello'%self.name)
class Pig(Animal):
    pass
class Dog(Animal):
    pass
peo1 = People('alex',18,'male','小学肄业')#这样会触发People自己的init方法  People.__init__(),实际上调的是父类的init
pig1 = Pig('wupeiqi',20,'female')
dog1 =Dog('yuanhao',30,'male')
print(peo1.education)#输出结果:小学肄业
peo1.talk()#talk先从自己的对象里面找,找不到到peo1的类里面找,找到了就不会去父类里面找了
pig1.talk()
dog1.talk()

 看下面代码执行的结果:

class Parent:
    def foo(self):#3)在父类里面找
        print('Parent.foo')#4)第一步s.foo()实际上触发的是下面的print
        self.bar()#5)紧接着执行这步,self.bar就是s.bar。调的是哪个bar函数呢?先从对象找,对象没有,去对象的类去找
    def bar(self):#)不会执行这个函数
        print('Parent.bar')
class Sub(Parent):#6)s.bar所在的类
    def bar(self):#7)执行下面的打印
        print('Sub.bar')#)8打印
s = Sub()#2)自己的类里面也没有,那只能去父类里面找
s.foo()#1)先找s.foo,先从对象里面找有没有foo这个字典,对象里面没有,那就去自己的类里面找
输出结果:
Parent.foo
Sub.bar

为什么说优先从对象自身找呢?没有的时候再去类里面找!

class Sub:
    def __init__(self):
        self.bar = 123
    def bar(self):
        print('Sub.bar')
s = Sub()
print(s.__dict__)#2)输出结果{'bar': 123},说明这里面有bar这个key,有就不会去类里面找,所以下面的s.bar就相当于12.bar()
s.bar()#1)首先在自己的字典里面找叫bar这个key ,s.__dict__['bar']

继承是为了减少代码冗余,那他表示的是什么情况下我才能解决这个代码冗余的过程呢?继承是父子的关系,看下面的代码:

class Animal:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('eating')
    def talk(self):
        print('%s正在叫'%self.name)
class People(Animal):
    def __init__(self,name,age,sex,education):
        Animal.__init__(self,name,age,sex)
        self.education =education
    def talk(self):
        Animal.talk(self)
        print('%s say hello'%self.name)
peo1 = People('alex',18,'male','小学肄业')
print(isinstance(peo1,People)) #isinstance表示peo1是People的实例
print(isinstance(peo1,Animal))#peo1也是Animal的实例

继承反映的是一种什么是什么的关系,这种关系可以用继承解决代码的冗余。

组合也可以解决代码冗余问题,但是组合反映的是一种什么有什么的关系。组合与重用见下面代码:

class People:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
class Date:
    def __init__(self,year,mon,day):
        self.year = year
        self.mon = mon
        self.day = day
    def tell(self):
        print('%s-%s-%s' %(self.year,self.mon,self.day))
class Teacher(People):
    def __init__(self,name,age,sex,salary,year,mon,day):
        self.name = name
        self.age = age
        self.sex = sex
        self.salary = salary
        #老师有生日,这是组合的关系
        self.birth = Date(year,mon,day)# Date(year,mon,day)这个得出来就是date的一个对象,存给老师的生日。这种方式就叫做组着,你的一个属性来自于另一个类的对象
class Student(People):
    def __init__(self, name, age, sex, year, mon, day):
        self.name = name
        self.age = age
        self.sex = sex
        self.birth = Date(year, mon, day)#重用
t = Teacher('egon',18,'male',3000,1995,12,30)
print(t.birth)#输出的是<__main__.Date object at 0x0072C110> 是一个对象
t.birth.tell()#直接调用输出结果1995-12-30

比如学生类,学生有课程,课程可以单独定义成一个类,可以用组合的关系实现代码的冗余;学生有老师,老师有自己教的课程;学生有自己属于的学校,学生是一个类,学校也是一个类,这些都存在组合关系。一个类与另一个类进行组合,以组合的方式去建立一种关系,什么有什么的关系。通过对象.tell的方式来调用那个关系。

part3、接口与归一化设计

1.什么是接口

=================第一部分: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();
    } 
}

 java中的interface

PS:hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口

2、为何要用接口

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化的好处在于:

1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合

2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

3、模仿interface

在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念

可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py里使用zope.interface

文档https://zopeinterface.readthedocs.io/en/latest/

设计模式:https://github.com/faif/python-patterns

 也可以使用继承: 

继承的两种用途

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

class File:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass

    def write(self): #定义接口函数write
        pass


class Txt(File): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')

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

class Sata(File): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')

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

class Process(File):
    def read(self):
        print('进程数据的读取方法')

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

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

part4、抽象类

1、什么是抽象类

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

2、为什么要有抽象类

  如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类是从一堆中抽取相同的内容而来的,内容包括数据属性和函数属性。

  比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

    从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案

3、在Python中实现抽象类

#抽象类
import abc
class File(metaclass=abc.ABCMeta):
#在你每一个想要子类必须实现的方法前面加一个装饰器
    @abc.abstractmethod
    def read(self):
        #raise TypeError('类型错误')#raise表示抛出异常
        pass
    @abc.abstractmethod
    def write(self):
        pass
class Process(File):
    def read(self):
        print('进程数据的读取方法')
    def write(self):
        print('进程数据的读取方法')
p = Process()#进行实例化
p.read()

 part5、继承的实现原理

F继承了A、B、C;A继承了E,B继承了D
如果由F产生了一个对象,这个对象在找一个属性的时候,优先从哪里找?从自己对象的__dict__里面找,如果找不到,找自己的类,就是F,如果F也没有呢?从左到右去排,先去找A,如果A没有,就去找E,如果E也没有,那么这个分支结束,开始去B里面找,B没有去D,D没有去C找。我们用代码定义出这种关系:

class E:
    def test(self):
        print('from E')
class A(E):
    def test(self):
        print('from A')
class D:
    def test(self):
        print('from D')
class B(D):
    def test(self):
        print('from B')
class C:
    def test(self):
        print('from C')
class F(A,B,C):
    def test(self):
        print('from F')
f = F() #由F产生一个对象
f.test()

新式类---:广度优先。

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,执行 print(F.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.如果对下一个类存在两个合法的选择,选择第一个父类

如果继承关系图如下呢?该这么找?

 

            图一                                            图二

图一:左边的分支和右边的分支有一个共同的头,这种情况新式类和经典类就有区别了。先来看新式类:

F创建一个对象,这个对象在找属性的时候,先从本身__dict__找,如果没有就去F类找,如果没有,就去D类找,如果D没有,就去找B,如果B里面也没有,这里需要注意,这是Python2 和Python3的区别,直接找E了,如果E也没有,就去找C,如果C也没有,最终去找A。

图二:先找F ,然后是 D ,B ,E,C,H ,最后是A

图一的代码形式:图二代码省略。

class A:
    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')
f = F()
f.test()

那在Python2中新式类怎么写?加上object

class A(object):#python2中写法,继承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')
f = F()
f.test()

Python2中经典类怎么找呢?---深度优先 ,以图二为例:F--->D--->B--->A--->E--->C--->H

class A:
    def test(self):
        print('from A')
    pass
class B(A):
    def test(self):
        print('from B')
    pass
class C(A):
    def test(self):
        print('from C')
    pass
class D(B):
    def test(self):
        print('from D')
    pass
class E(C):
    def test(self):
        print('from E')
    pass
class H(A):
    def test(self):
        print('from H')
    pass
class F(D,E,H):
    def test(self):
        print('from F')
    pass
f = F()
f.test()

继承顺序总结如图:

part6、子类继承父类的方法

如下代码是我们之前调用父类的方法的情况:

class Foo():
    def test(self):
        print('from foo.test')
class Bar(Foo):
    def test(self):
        Foo.test(self)#调用父类的方法,指名道姓,即父类名.父类方法()
        print('bar')
b = Bar()
b.test()

如果父类的名称改了,怎么办?------super,Python3的写法如下:

class Foo():
    def test(self):
        print('from foo.test')
class Bar(Foo):
    def test(self):
        #Foo.test(self)#调用父类的方法,指名道姓,即父类名.父类方法()
        super().test()
        print('bar')
b = Bar()
b.test()

Python2中的写法:语法为:super(自己的类名,self)

class Foo():
    def test(self):
        print('from foo.test')
class Bar(Foo):
    def test(self):
        super(Bar,self).test()#super(自己的类名,self),这种写法在Python3中也没错,只是在Python2中注意
        print('bar')
b = Bar()
b.test()

当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

疑问:如果继承多个类的情况下,super怎么调用呢?----根据print(Bar.mro())输出的结果优先调用

class Foo1():
    def test(self):
        print('from foo1.test')
class Foo2():
    def test(self):
        print('from foo2.test')
class Bar(Foo1,Foo2):
    def test(self):
        # Foo1.test(self)#可以指定调用
        # Foo2.test(self)
        super().test()
        print('bar')
print(Bar.mro())#根据这个打印出来的顺序优先选择
b = Bar()
b.test()

三、多态与多态性

多态:同一种事物的多种形态

动物有多种形态:人,狗,猪

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

多态性:

多态性是指在不考虑实例类型的情况下使用实例。

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同

多态性分为静态多态性和动态多态性

  静态多态性:如任何类型都可以用运算符+进行运算

  动态多态性:如下

class Animal:
    def talk(self):
        print('正在叫')
class People(Animal):
    def talk(self):
        print(' say hello')
class Pig(Animal):
    def talk(self):
        print('哼哼哼')
class Dog(Animal):
    def talk(self):
        print('汪汪汪')
#多态性:三者都可以执行talk,可以理解为向peo1、dog1、pig1发送相同的一条信息,但是得出的效果各不相同。为什么会有这种效果呢?因为这三个对象都是同一种事物,只要是动物就一定有talk.
peo1 = People()
pig1 = Pig()
dog1 = Dog()
#peo1、dog1、pig1都是动物,只要是动物肯定有talk方法,于是我们可以不用考虑它们三者具体是什么类型,而直接使用
# peo1.talk() # dog1.talk() # pig1.talk() #注释掉上面三个,可以换成如下写法,更进一步,我们可以定义一个统一的接口来使用 def func(obj):#Python是弱类型语言,对参数没有类型限制,所以可以把obj换成x,这样做的好处是1、统一调用方式2、即便新增了一个类,也不影响调用,可扩展性强 obj.talk()#不同的对象传进来都可以调用这个接口 func(peo1) func(pig1) func(dog1)

为什么要用多态性(多态性的好处)

其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?

1.增加了程序的灵活性

  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

>>> class Cat(Animal): #属于动物的另外一种形态:猫
...     def talk(self):
...         print('say miao')
... 
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
...     animal.talk()
... 
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao

'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
'''

四、封装

从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的。

1)要封装什么?

  你钱包里有多少钱(数据的封装)

  你的性取向(数据的封装)

  你拍照的具体功能是怎么实现的(方法的封装)

2)为什么要封装

  封装不是单纯意义的隐藏:

  1.封装数据的主要原因是:保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)

  2.封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了)

3)封装分为两个层面

  第一个层面什么都不用做,在Python里面就已经有封装的概念了,比方说:

class Foo:
    x = 1
    def test(self):
        print('from test')
print(Foo.x)#从Foo里面拿出一个x,这本身就是一种封装。从一个名称空间里面取出一个名字来

注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口。  

  另外一个层面:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用,外部无法访问,或者留下少量接口(函数)供外部访问。在Python中怎样实现隐藏的效果呢?-------->在Python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

其实这仅仅这是一种变形操作
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class Foo:
    __x = 1
    def test(self):
        print('from test')
print(Foo.__x)
#报错AttributeError: type object 'Foo' has no attribute '__x'

__x=1这个是我类的一个数据属性,这一点没有错,print(Foo.__x)没有,那我们可以在print(Foo.__dict__)进行查看。

class Foo:
    __x = 1#从Foo.__dict__中查看,__x被转化成了_Foo__x
    def __test(self):#__test被转化成了_Foo__test
        print('from test')
print(Foo.__dict__)
#print(Foo.__x)#访问不到
#Foo.test(123)#访问不到
#所以我需要按照下面方式访问:
print(Foo._Foo__x)#可以访问到

通过说明Python没有真正意义上的隐藏,只是从语法级别来做了一个变形的操作。看下面的例子:

class People:
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex
    def tell_info(self):
        print('人的名字是:%s,人的性别是:%s,人的年龄是:%s' %(
            self.__name,
            self.__age,
            self.__sex))
p = People('alex',18,'male')#实例化一个人
p.tell_info()#可以看到这个人的信息,但是看不到内部变量是怎么存的。这就是隐藏起来了。

疑问:为什么p.tell_info()可以访问到里面的变量呢?通过print(p.__dict__)我们发现__name,__age,__sex已经被转化成了{'_People__sex': 'male', '_People__name': 'alex', '_People__age': 18},我们执行print(p.__name)还是报错的。注意:变形是指语法的变形,在检测语法的时候都会给你改名字,Python检测到函数__init__下面的 self.__name=name时候,会给你转化成self.__People__name = name ,p.tell_info()在调用的时候函数tell_info里的语法已经变换了(self.__name=name已经变成了self.__People__name=name,在定义阶段就已经变形了)。

这种自动变形的特点:

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果

2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

第三点是什么意思呢?先看下面的代码:

class Parent:
    def foo(self):
        print('from parent.foo')
        self.bar()
    def bar(self):
        print('from parent.bar')
class Sub(Parent):
    def bar(self):
        print('from SUb.bar')
s = Sub()#产生一个对象
s.foo()
'''
返回from parent.foo和from SUb.bar
s.foo()先从对象字典里面找发现没有foo,去所在的类Sub中找也没有,
去父类Parent中找到了,执行print和selt.bar()。selt.bar发现自己
所在类Sub中有,所以直接执行print
'''

如果我现在不想调自己本身的bar函数,想调用父类的bar函数,怎么修正代码呢?如下代码:

class Parent:
    def foo(self):
        print('from parent.foo')
        self.__bar()#在定义的时候会自动发生变形,转成self._Parent__bar()
    def __bar(self):#转成 _Parent__bar
        print('from parent.bar')
class Sub(Parent):
    def bar(self):
        print('from SUb.bar')
s = Sub()#产生一个对象
s.foo()#调都是父类的内容,不管子类定义了什么

那如果再次将代码改成如下:父类有个__bar,子类也有个__bar ,子类的__bar会覆盖掉父类的__bar么?---->是毫不相关的

class Parent:
    def foo(self):
        print('from parent.foo')
        self.__bar()#在定义的时候会自动发生变形,转成self._Parent__bar()
    def __bar(self):#转成 _Parent__bar
        print('from parent.bar')
class Sub(Parent):
    def __bar(self):#转成_Sub__bar,与父类的毫不相关
        print('from SUb.bar')
s = Sub()#产生一个对象
# s.bar()#用这种方式在调用就调不到了
s._Parent__bar()#调父类的bar
s._Sub__bar()#调子类的bar

这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形

 

了解内容:

python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的

其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点

python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶。

class People:
    def __init__(self,name,age):
        self.__name = name#设置为隐藏
        self.__age = age
    def tell_info(self):
        print('人的名字是:%s,人的年龄是:%s' %(
                self.__name,
                self.__age))
        '''
        外面无法访问上面的属性,但是可以开放一个接口,通过接口的方式看到。
        现在需要考虑一个问题,通过接口可以给别人看了,那有没有设置这个需求?
        有改这个需求,但是能不能让别人直接改你的名字?
        --不能,因为我要做的这件事情就是把我存储的细节把我类内部处理的细节给隐藏起来。
        所以你想改我只能给你提供一个接口,如下
       '''
    def set_info(self,x,y):#设置详细信息
        self.__name = x
        self.__age = y
p = People('egon',18)
p.tell_info()#查看详细信息
p.set_info('egon_11',100)#设置信息
p.tell_info()#设置完之后在查看设置后的信息

python对数据类型没有限制,现在再设置属性的时候你传了字符串类型的值,和整型,name是字符串型,age是整型,那问题是使用者就一定按照你的规范来么?他名字也有可能传一个整型编号进去,这样就乱了。那有什么办法限制这件事情么?Python没有自动做限制,但是现在接口是你提供的,你提供的设置之前你可以定义x只能传字符型,不是字符串类型就不让你修改,y不是整型就不让你修改。

代码修改部分如下:

 def set_info(self,x,y):#设置详细信息
        if not isinstance(x,str):
            raise TypeError('名字必须是字符串类型')
        if not isinstance(y,int):
            raise TypeError('年龄必须是整型')
        #上面俩个if都判断通过了才会执行下面的设置
        self.__name = x
        self.__age = y

4)特性(property)

 property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

为什么要用property--->将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。除此之外,看下:

ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,
但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,
但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开

python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。

property可以当做装饰器去用,如下:

class Foo:
    def test(self):
        print('from foo')
f = Foo()
f.test()

加上property:

class Foo:
    def test(self):
        print('from foo')
    test = property(test)#把对象本身当成参数传递
f = Foo()
f.test

换一种写法:

class Foo:
    @property#装饰器
    def test(self):
        print('from foo')
f = Foo()
f.test#property是数据属性,f.test调用的时候就不用加括号

也就说你一旦在一个函数之前加上一个property,你调用的时候不用加括号就直接调用了。

看下面的例子:

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
用代码实现:
class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height
    def bmi(self):#定义一个体脂函数
        return self.weight / (self.height **2)
p = People('egon',75,1.80)
print(p.bmi())

这样写有没有问题呢?bmi指数是计算出来的,随着人体重的变化,bmi会涨,每次算bmi指数的时候都需要调对象的一个绑定方法,bmi是一个名词,直接这样写会让人误解,误解为bmi为何还需要计算呢?bmi是一个特征,又要通过函数计算这个功能来得出来,这好像是冲突的概念,那怎么解决?加上一个@property,他就成了一个属性,在执行print(p.bmi),对使用者来说就像是直接使用一个属性一样,像名字、身高等那样的属性。这就提供了一种统一的访问方式。把计算的细节隐藏起来,使用者不知道这些,这个属性是通过后台复杂逻辑计算出来的。

class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height
    @property
    def bmi(self):#定义一个体脂函数
        return self.weight / (self.height **2)
p = People('egon',75,1.80)
p.height = 1.82
print(p.bmi)

例二:圆的周长和面积 

import math
class Circle:
    def __init__(self,radius): #圆的半径radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #计算面积

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #计算周长

c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''

property还有一个其他的用法:--如下代码,可以查看name,如果想要修改name,就会报错

class People:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):
        return self.__name
p = People('egon')
print(p.name)
p.name = 'egon666' #报错AttributeError: can't set attribute

那如何修正呢?只能在加装饰器

class People:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    #前面已经有一个装饰器了,基于他的基础之上,可以按照下方的:
    @name.setter
    def name(self,value):
        self.__name = value
p = People('egon')
print(p.name)
p.name = 'egon666'
print(p.name)

还是存在一个问题,这个接口是你提供的,名字是字符串类型的,你需要在你提供的接口上加上下面的判断:

class People:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('名字必须是字符串类型')
        self.__name = value
p = People('egon')
print(p.name)
p.name = 'egon666'
print(p.name)

如果有删除的需求,怎么操作呢?---提示不允许删除

class People:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('名字必须是字符串类型')
        self.__name = value
    @name.deleter
    def name(self):
        raise PermissionError('不允许的操作')
p = People('egon')
del p.name

如果一定要删除,怎么操作呢?

class People:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('名字必须是字符串类型')
        self.__name = value
    @name.deleter
    def name(self):
        del self.__name
p = People('egon')
del p.name
print(p.name)#已经被删除了,所以报错AttributeError: 'People' object has no attribute '_People__name'

删除的时候我是否可以加个权限操作呢?权限是true的情况下让你删,权限是false的情况下不让你删。如下代码:

class People:
    def __init__(self,name,permission = False):
        self.__name = name
        self.permission = permission
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('名字必须是字符串类型')
        self.__name = value
    @name.deleter
    def name(self):
        if not self.permission:
            raise PermissionError('不允许的操作')
        del self.__name
p = People('egon')
p.permission = True#设置成true就可以删除,注释掉就无权删除
del p.name

上面的代码还有一个最原始的写法:----非装饰器的写法,效果是一样的

class People:
    def __init__(self,name,permission = False):
        self.__name = name
        self.permission = permission
    def get_name(self):
        return self.__name
    def set_name(self,value):
        if not isinstance(value,str):
            raise TypeError('名字必须是字符串类型')
        self.__name = value
    def del_name(self):
        if not self.permission:
            raise PermissionError('不允许的操作')
        del self.__name
    name = property(get_name,set_name,del_name)
p = People('egon')
p.permission = True#设置成true就可以删除,注释掉就无权删除
del p.name

 五、绑定与非绑定方法

绑定是指绑在身上的,谁来调就把谁传进去,接触的第一个绑定方法是绑定在对象身上的,他一定是定义在类内部的,在类内部定义的方法如果没有被任何装饰器修饰的情况下,它就一定是给对象用的,效果就是谁来调自动把谁传进去。如下代码:

class Foo:
    def test1(self):
        print('test1',self)
    def test2(self):
        print('test2',self)
f = Foo()
f.test1()
f.test2()

f调test1就把test1传进去,调test2就是把test2传进去。那跟self有什么关系呢?--->跟self没有任何关系。类内部定义的def没有经过任何装饰的就是绑定给对象用的,也就是说类内部定义的方法默认都是绑定到对象上面给对象使用的,他有一个自动传值的功能。看下面代码去掉self:

class Foo:
    def test3():
        print('test2')
f = Foo()
f.test3()

输出结果:TypeError: test3() takes 0 positional arguments but 1 was given  也就是说:在类内部定义函数都是绑定对象身上的方法。

疑问:有绑定到对象身上的方法,那有没有绑定到类上面的方法呢?绑定方法绑定在谁身上就是谁来用,谁来用就把谁当做是第一个参数传进去,那有没有一种方法是绑定到类上的方法呢?--->有,看下面代码:

class Foo:
    @classmethod#加一个装饰器,这是一个类方法,方法是绑定到类身上的,给类来用
    def test(cls):
        print(cls)
f = Foo()
Foo.test()#类来调test,没必要传值,谁来调,就自动把自己传进来,这就是类的绑定方法
print(Foo)
#现在有个问题,我对象可不可以调用test呢?
f.test()
'''
可以调,但是我对象调是没有意义的。因为我这个是给类用的。
即使调也是把类当成第一个参数传进去。
'''

你学完了绑定到类的方法,有类的自动传值,绑定到对象上的方法,有对象的自动传值,是否存在这样的需求,我跟谁都不绑定,谁都可以来调我,谁来调我,我都没有自动传值那一说,他就是类内部的一个普通函数而已,就是一个普通工具,如下代码,我定义一个普通函数:

#下面是一个普通函数
class Foo:
    def test():
        print('test')
'''
这是什么?在类内部定义个方法都是绑定到对象上的,不管你写不写self,都是绑定到对象上的,
所以这样写就没有“跟谁都不绑定”这一说了。
'''

那你想要跟谁都不绑定,该怎么写呢?加一个装饰器,叫staticmethod

class Foo:
    @staticmethod#代表跟谁都不绑定,
    def test():
        print('test')
Foo.test()#没传参数,没有自动传值那一说
#你也可以写参数,写几个参数就传几个参数
class Foo:
    @staticmethod#代表跟谁都不绑定,
    def test(x,y):
        print('test',x,y)
Foo.test(1,3)
'''
我产生一个对象,对象是否可以调它。
--->也可以调,但是对象调它会有自动传值这一说么?
---没有,所以也是有几个参数就传几个参数
'''
f = Foo()
f.test(1,10)

总结:类中定义的函数分成两大类:

一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

    1. 绑定到类的方法:用classmethod装饰器装饰的方法。

                为类量身定制

                类.boud_method(),自动将类当作第一个参数传入

              (其实对象也可调用,但仍将类当作第一个参数传入)

    2. 绑定到对象的方法:没有被任何装饰器装饰的方法。

               为对象量身定制

               对象.boud_method(),自动将对象当作第一个参数传入

             (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

二:非绑定方法:用staticmethod装饰器装饰的方法

        1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

    注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说

一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

    1. 绑定到类的方法:用classmethod装饰器装饰的方法。

                为类量身定制

                类.boud_method(),自动将类当作第一个参数传入

              (其实对象也可调用,但仍将类当作第一个参数传入)

    2. 绑定到对象的方法:没有被任何装饰器装饰的方法。

               为对象量身定制

               对象.boud_method(),自动将对象当作第一个参数传入

             (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

二:非绑定方法:用staticmethod装饰器装饰的方法

        1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

    注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说

class Foo:
    def test1(self):#没有加任何修饰就是绑定给对象的,但实际上类也能看到,通过类看是函数的属性
        pass
    @classmethod
    def test2(cls):
        print(cls)
    @staticmethod
    def test3():
        pass
f = Foo()
print(f.test1)
'''输出结果:<bound method Foo.test1 of <__main__.Foo object at 0x005BA790>>
这是绑定到对象上的一个方法'''
print(Foo.test2)
'''输出结果:<bound method Foo.test2 of <class '__main__.Foo'>>
这是绑定到类上面的一个结果,绑定到Foo这个类身上
'''
print(Foo.test3)
print(f.test3)
'''
输出结果都是:<function Foo.test3 at 0x007D1150>
普通函数没有自动传值一说,只有绑定方法才有自动传值一说
'''

看下面一个连接数据库操作的代码:

class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        print('connecting,,,')
    def select(self):#绑定到对象的方法
        print(self)
        print('select function')
conn = MySQL('192.168.1.3',3306)
conn.select()
输出结果:
connecting,,,
<__main__.MySQL object at 0x005C5CB0>
select function

那什么时候定义成类方法呢?--把类自动传进去。上面函数中conn = MySQL('192.168.1.3',3306),这个连接数据库是用户自己传入的ip和端口,也可以从配置文件传入.现在就让你提供一种默认的用户连接数据库方法,不用穿ip地址和端口,自己到一个地方拿就可以了。

新建settings.py文件夹。内容如下:

HOST = '127.0.0.1'
PORT = 3306

代码如下:

import settings#从文件中读取,首先导入这个模块
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        print('connecting,,,')
    @classmethod
    def from_conf(cls):
        #cls()#类() 相当于 MySQL(),需要穿host和port,从哪里面读呢?从配置文件中,注释此句,如下:
        return cls(settings.HOST,settings.PORT)#相当于MySQL('127.0.0.1',3306),把实例化的结果返回
    def select(self):#绑定到对象的方法
        print(self)
        print('select function')
#conn = MySQL('192.168.1.3',3306)#这个调的是__init__(self,host,port)
conn2 = MySQL.from_conf()#z这个调的是绑定到类上面的方法,会自动把类本身也就是MySQL传进去
#conn.select()

有没有这样的需求,不跟任何绑定,不跟类绑定也不跟对象绑定,就是既不需要类里面的参数也不需要对象里面的参数:

额外篇:一般id的生成代码如下

import hashlib
import time #可以用时间来做这个hash对象,因为时间是变化的
#定义一个产生id的方法
def create_id():
    #m = hashlib.md5(str(time.time()).encode('utf-8'))#时间不是字符串类型要进行转换
    m = hashlib.md5(str(time.clock()).encode('utf-8'))#取代上一句time.time()不够精确
    return m.hexdigest()
print(create_id())
print(time.time())#哈希值是time.time(),短时间打印也是一样的值
'''打印一下,生成一个id号,但存在一个问题,短时间内打印多次得出的是同一个id,
说明不够精确'''
time.clock()#这个返回的是cpu的一个真实的时间
print(time.clock())
print(time.clock())
print(time.clock())

非绑定方法的应用:

import settings
import hashlib
import time
class MySQL:
    def __init__(self,id,host,port):#增加一个id属性,每创建一个数据库连接,我标识一下
        self.id = self.create_id()#self可以调create_id,他是非绑定的
        self.host = host
        self.port = port
        print('connecting,,,')
    @staticmethod
    def create_id():#非绑定方法,不依赖于任何类和对象,只是放在类中当做一个工具使用
        m = hashlib.md5(str(time.clock()).encode('utf-8'))
        return m.hexdigest()
    @classmethod
    def from_conf(cls):
        return cls(id,settings.HOST,settings.PORT)
    def select(self):
        print(self)
        print('select function')
conn1 = MySQL.from_conf()
conn2 = MySQL.from_conf()
conn3 = MySQL.from_conf()
conn4 = MySQL.from_conf()
print(conn1.id)
print(conn2.id)
print(conn3.id)
print(conn4.id)

staticmethod与classmethod的区别:

首先看下面的代码,不管我用classmethod还是staticmethod都能达到连接数据库的效果:

import settings
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        print('connecting...')
    #用下面的方法可以连接数据库
    # @classmethod
    # def from_conf(cls):
    #     return cls(settings.HOST,settings.PORT)
    #用注释掉上面的,用下面的也可以达到效果
    @staticmethod
    def from_conf():
        return MySQL(settings.HOST,settings.PORT)
conn = MySQL.from_conf()
print(conn.host)

来单独看下staticmethod:

import settings
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        print('connecting...')
    @staticmethod
    def from_conf():
        return MySQL(settings.HOST,settings.PORT)
# conn = MySQL.from_conf()
# print(conn.host)
class Mariab(MySQL):#Mariab也是数据库,跟MySQL很像,那么就可以用继承关系解决重用问题
    pass
conn1 = Mariab.from_conf()#创建一个连接,可以调到from_conf,先从本身conn1中找from_conf,没有去父类MySQL中找,可以找到
#我用Mariab去实例化,得到的是Mariab的对象conn1,但现在conn1调的是MySQL实例化的对象,看下面打印
print(conn1)#输出结果:<__main__.MySQL object at 0x007BAFB0> 调的是MySQL实例化的对象

上面代码看似没问题,先来补充一个知识点:

用命令窗口实例化 l = list([1,2,3]) 然后打印:print(l) 按理应该显示:<__main__.MySQL object at 0x007BAFB0> 结果显示[1,2,3],这是为什么呢?

看下面的代码:

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
       # 如果你想在打印对象的时候能够显示一些有用的信息,你必须在对象的类内部去重写一个方法叫:
    def __str__(self):
        #首先这个__str__(self)是默认就有的,只不过他默认是什么都不干,所以你看到的结果是<__main__.People object at 0x00829710>
        #如果你重写了,注意这里应该返回一个值,
        print('run __str__')#如果触发了就会打印这个run
        return 'hhaahaah'
p = People('egon',18)
p1= People('egon',18)
print(p)#执行这个会触发类内部绑定str的方法
print(p1)#也会返回hhaahaah

如果return换个值:

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __str__(self):
        return 'name:%s age:%s'
p = People('egon1',18)
p1= People('egon2',28)
print(p)#打印是就触发谁里面的绑定方法,返回一个字符串
print(p1)

回想刚才我们遗留的问题,这样就继续用@staticmethod就不适用了:

import settings
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        #print('connecting...')
    @staticmethod#这个方法本身是可以实现这个结果,但是有子类继承,这就产生冲突了。我们想做的是谁来调用from_conf就把他传进去,所以这种staticmethod就不适用了,只能用classmethod
    def from_conf():
        return MySQL(settings.HOST,settings.PORT)
    def __str__(self):#在父类里面定义一个str
        return '就不告诉你'
class Mariab(MySQL):#子类继承了父类
    def __str__(self):
        return '这是子类的方法'
conn1 = Mariab.from_conf()#子类去调父类的from_conf()方法,由父类MySQL产生的conn1这个对象
print(conn1)#我本来打印conn1,很明显,我想要做的事情是触发类Mariab中的__str__方法,但是实际上触发的是类MySQL中的__str__,问题是conn1就是父类MySQL产生的

需要换成classmethod:

import settings
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        #print('connecting...')
    @classmethod
    def from_conf(cls):#2)cls为Mariab
        return cls(settings.HOST, settings.PORT)#3)也就是由Mariab去进行实例化 ,Mariab也找init,他自己没有,所以去父类找
    def __str__(self):
        return '就不告诉你'
class Mariab(MySQL):
    def __str__(self):
        return 'host:%s port:%s' %(self.host,self.port)
conn1 = Mariab.from_conf()#1)Mariab在调from_conf的时候,会把Mariab当做第一个参数传入到2)中
print(conn1)#这个时候触发的就是自己的方法

见如下练习题:

定义MySQL类

  1.对象有id、host、port三个属性

  2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一

  3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化

  4.为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象

创建唯一id之UUID
原文链接:http://www.cnblogs.com/dkblog/archive/2011/10/10/2205200.html
 Python官方Doc:《20.15. uuid — UUID objects according to RFC 4122》
    UUID的算法介绍:《A Universally Unique IDentifier (UUID) URN Namespace》

概述:

    UUID是128位的全局唯一标识符,通常由32字节的字符串表示。
    它可以保证时间和空间的唯一性,也称为GUID,全称为:
            UUID —— Universally Unique IDentifier      Python 中叫 UUID
            GUID —— Globally Unique IDentifier          C#  中叫 GUID

    它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。
    UUID主要有五个算法,也就是五种方法来实现:

       1、uuid1()——基于时间戳

               由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性,
               但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。

       2、uuid2()——基于分布式计算环境DCE(Python中没有这个函数)

                算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。
                实际中很少用到该方法。

      3、uuid3()——基于名字的MD5散列值

                通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,
                和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。    

       4、uuid4()——基于随机数

                由伪随机数得到,有一定的重复概率,该概率可以计算出来。

       5、uuid5()——基于名字的SHA-1散列值

                算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法

使用方面:

    首先,Python中没有基于DCE的,所以uuid2可以忽略;
    其次,uuid4存在概率性重复,由无映射性,最好不用;
    再次,若在Global的分布式计算环境下,最好用uuid1;
    最后,若有名字的唯一性要求,最好用uuid3或uuid5。

编码方法:

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

    import uuid

    name = "test_name"
    namespace = "test_namespace"

    print uuid.uuid1()  # 带参的方法参见Python Doc
    print uuid.uuid3(namespace, name)
    print uuid.uuid4()
    print uuid.uuid5(namespace, name)

代码如下:

#settings.py内容
'''
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'E:\CMS\aaa\db'
'''
import settings
import uuid
import pickle
import os
class MySQL:
    def __init__(self,host,port):
        self.id=self.create_id()
        self.host=host
        self.port=port

    def save(self):
        if not self.is_exists:
            raise PermissionError('对象已存在')
        file_path=r'%s%s%s' %(settings.DB_PATH,os.sep,self.id)
        pickle.dump(self,open(file_path,'wb'))

    @property
    def is_exists(self):
        tag=True
        files=os.listdir(settings.DB_PATH)
        for file in files:
            file_abspath=r'%s%s%s' %(settings.DB_PATH,os.sep,file)
            obj=pickle.load(open(file_abspath,'rb'))
            if self.host == obj.host and self.port == obj.port:
                tag=False
                break
        return tag
    @staticmethod
    def get_obj_by_id(id):
        file_abspath = r'%s%s%s' % (settings.DB_PATH, os.sep, id)
        return pickle.load(open(file_abspath,'rb'))

    @staticmethod
    def create_id():
        return str(uuid.uuid1())

    @classmethod
    def from_conf(cls):
        print(cls)
        return cls(settings.HOST,settings.PORT)

# print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.save()

conn1=MySQL('127.0.0.1',3306)
conn1.save() #抛出异常PermissionError: 对象已存在


obj=MySQL.get_obj_by_id('7e6c5ec0-7e9f-11e7-9acc-408d5c2f84ca')
print(obj.host)

六、面向对象的软件开发

面向对象的软件工程包括下面几个步骤:

 1.面向对象分析(object oriented analysis ,OOA)

  软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。

2.面向对象设计(object oriented desigh,OOD)

  根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述

3.面向对象测试(object oriented programming,OOP)

  根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python

4.面向对象测试(object oriented test ,OOT )

  在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。 面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。

5.面向对象维护(object oriented soft maintenance,OOSM)

  正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。由于使用了面向对象的方法开发程序,使用程序的维护比较容易。因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。

 七、小白容易犯的错误

1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)

2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序

好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...

3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼

这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。

你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划,请看第八节

4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。

八、Python中关于OOP的常用术语

抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。

对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。 

封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

合成

合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。

派生/继承/继承结构

派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。

泛化/特化

基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。

多态与多态性

多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气

多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

自省/反射

自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__

补充:

__init__不能有返回值,它只是一步初始化操作而已,他是在已经创建出对象的基础上为对象.方式添加属性,他只负责初始化属性,不负责创建对象。

class Foo:
    def __init__(self):
        return 123#跑出异常,不能有有返回值
f = Foo()
#报错:TypeError: __init__() should return None, not 'int'

__str__:必须有返回值,而且是字符串类型

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)

九、反射

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
print(Chinese.country)#Chinese.country中的country不是字符串,
# 本质上是去调Chinese.__dict__下面的,把country当做字符串传进去Chinese.__dict__['country']
#对于一个对象来说呢?调自己的属性怎么调?
p = Chinese('egon',18)
print(p.name)#p.name中的name不是字符串,本质是p.__dict__['name']

反射指的就是通过字符串的方式去寻找对象内部的一个属性。Python里面一切皆对象,类本身也是一个对象,所以可以统一归为我要找一个对象,可以用什么.什么的方式去找,这种方式就是调一个属性,而我也可以用另外一种方式,就是不用属性了,用字符串的方式去找,后面这种方式就是字符串,是他最根本的原理。Python中的一切事物都是对象(都可以使用反射)。四个可以实现自省的函数:

1)hasattr(object,name):判断object中有没有一个name字符串对应的方法或属性

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
p = Chinese('egon',18)
#判断对象p有没有name这个属性
print(hasattr(p,'name'))#name是以字符串的形式,True #print(hasattr(p,name))#这个就会报错NameError: name 'name' is not defined # 判断类有没有country这个属性 print(hasattr(Chinese,'country'))#True

2)setattr(x,y,v)给谁设置。

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
p = Chinese('egon',18)
p.x = 1#把x = 1设置进去
print(p.__dict__)
print(p.x)#设置进去之后进行调用
#上面是通过属性去设置,通过属性去调用。那还有一种方式不通过属性设置,
setattr(p,'x',123123)#注意设置的属性一定是字符串形式,设置的值就随意了
print(p.__dict__)#看一下有没有设置成功,看一下他的名称空间里面有没有
print(p.x)#在进行下调用他的属性

3)getattr(object,name,default=None)

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
p = Chinese('egon',18)
#print(getattr(p,'x'))#在p里面找到x属性的这个值,没有报异常:AttributeError: 'Chinese' object has no attribute 'x'
print(getattr(p,'x','not exist'))#捕获异常

4)delattr(x,y)#通过字符串的形式去删

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
p = Chinese('egon',18)
delattr(Chinese,'country')
print(Chinese.country)#删除了在打印提示AttributeError: type object 'Chinese' has no attribute 'country'

Python中一切皆对象,之前学过的模块也都是对象,那可以反射当前模块到底有没有这个名字。比如说当前模块有很多名字,你要查一下是否有Chinese这个名字,如果有获取到,该怎么写呢?

class Chinese:
    country = 'Chinese'
    def __init__(self,name,age):
        self.name = name
        self.age = age
import sys
m = sys.modules[__name__]#获取当前模块名,赋值给m
#print(m)
#判断有没有chinese这个属性,然后把它拿到并打印出来
if hasattr(m,'Chinese'):
    res = getattr(m,'Chinese')
    #print(res)
    obj = res('egon',18)
    print(obj.name)

为什么要用反射呢?

好处一:实现可插拔机制

有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

如下是egon还没有实现全部功能ftpclient.py:

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

不影响lili的代码编写ftpserver.py:

import ftpclient
f1 = ftpclient.FtpClient('192.168.1.1')#使用我的客户端进项连接服务器的操作
if hasattr(f1,'get'):
    func_get=getattr(f1,'get')
    func_get()
print('------->不存在此方法')
print('处理其他的逻辑')

如果不是两个人操作,一个人也可也完成:

class FtpCLient:
    def __init__(self,host):
        self.host = host
        print('connecting...')
    def run(self):#接收用户输入的命令
        while True:
            inp = input('>>:').strip()#用户输入的命令
            inp_l=inp.split()
            if hasattr(self,inp_l[0]):
                func = getattr(self,inp_l[0])
                func(inp_l)
    def get(self,arg):
        print('download file',arg[1])
f = FtpCLient('192.168.1.2')
f.run()
输出结果:

connecting...
>>:get a.txt
is download file a.txt
>>:

你在导入模块的时候有没有需求就是你的模块名也是字符串形式,通过字符串的形式导入一个模块。

m = __import__('sys')#__import__是内置方法
print(m)#输出<module 'sys' (built-in)>
print(m.path)
#这两种方式都可以导入模块,下面的用的比较多,毕竟上面的调的是内置函数
import importlib#importlib底层也是在调用__import__
m = importlib.import_module('sys')
print(m)#输出结果:<module 'sys' (built-in)>

 

--------over

转载于:https://www.cnblogs.com/zhaic/articles/7443884.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值