4 面向对象

面向过程:核心是过程,过程指的是解决问题的步骤,似一条流水线,机械式的思维方式

优点:复杂的问题流程化,进而简单化

缺点:可扩展性差

面向对象编程有三大特性:封装、继承、多态

1 对象的概念

面向对象:的核心是“对象”二字,而对象的精髓在于“整合“

所有的程序都是由”数据”与“功能“组成,因而编写程序的本质就是定义出一系列的数据,然后定义出一系列的功能来对数据进行操作。

面向对象编程就是要造出一个个的对象,把原本分散开的相关数据与功能整合到一个个的对象里,这么做既方便使用,也可以提高程序的解耦合程度,进而提升了程序的可扩展性(需要强调的是,软件质量属性包含很多方面,面向对象解决的仅仅只是扩展性问题)

面向对象:核心就是对象二字,对象就是特征与技能的结合体
优点:可扩展性强
缺点:编程复杂度高
应用场景:用户需求经常变化,(比如:互联网应用,游戏,企业内部应用)
类的定义:类就是一系列对象相似的特征与技能的结合体
强调:站的角度不同,得到的类也不同
在现实世界中:一定是先有对象,后有类
在程序中:一定先定义类,后调用类来产生对象

2 类与对象

类即类别/种类,是面向对象分析和设计的基石,如果多个对象有相似的数据与功能,那么该多个对象就属于同一种类。有了类的好处是:我们可以把同一类对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。

综上所述,虽然我们是先介绍对象后介绍类,但是需要强调的是:在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制,面向对象编程最终的核心仍然是去使用对象。


3 属性访问

类有两种属性:数据属性和函数属性,可以通过dict访问属性的值

Student.school # 访问数据属性,等同于Student.__dict__['school']
# 类与对象都可以调数据属性和函数属性

操作对象的属性也是一样

stu1.name # 查看,等同于obj1.__dict__[‘name']
stu1.course=’python’ # 新增,等同于obj1.__dict__[‘course']='python'
stu1.age=38 # 修改,等同于obj1.__dict__[‘age']=38
del obj1.course # 删除,等同于del obj1.__dict__['course']

3-1 属性查找顺序

对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。对象在访问属性时,会优先从对象本身的__dict__中查找,未找到,则去类的__dict__中查找,然后再去父类中找……

  • 类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址

    # id都一样
    print(id(Student.school)) # 4301108704
    print(id(stu1.school)) # 4301108704
    
  • 类中定义的函数是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

    Student.choose(stu1) # 李建刚 is choosing a course
    
  • 但其实类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同

    print(id(Student.choose)) # 4335426280
    print(id(stu1.choose)) # 4300433608
    
  • 绑定到对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将’谁’本身当做第一个参数自动传入(方法__init__也是一样的道理)

    stu1.choose()  # 等同于Student.choose(stu1)
    stu2.choose()  # 等同于Student.choose(stu2)
    

注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。


3-2 __init__使用

__init__方法,用来为对象定制对象自己独有的特征

class LuffyStudent:
    school = 'luffycity'
    def __init__(self, name, sex, age):
        self.Name = name
        self.Sex = sex
        self.Age = age

# 加上__init_方法后,实例化的步骤
# 1、先产生一个空对象stu1
# 2、LuffyStudent.__init__(stu1, '刘大帅', '男', 18)
stu1 = LuffyStudent('刘大帅', '男', 18)  # 实例化
print(stu1.__dict__)  # 查
print(stu1.Name)  # 查
stu1.Name = '鲁大师'  # 改
del stu1.Name  # 删
stu1.class_name = 'python开发'  # 增

4 继承

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

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承
    pass

class SubClass2(ParentClass1,ParentClass2): #多继承
    pass

通过类的内置属性__bases__可以查看类继承的所有父类

print(SubClass1.__bases__)

子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题.

父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的

class Foo:
    def __f1(self):  # 变形为_Foo__f1
        print('Foo.f1')
    def f2(self):
        print('Foo.f2')
        self.__f1()  # 变形为self._Foo__f1,因而只会调用自己所在的类中的方法

class Bar(Foo):  # 变形为_Bar__f1
    def __f1(self):
        print('Bar.f1')

b=Bar()
b.f2()  # 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法

# 输出
Foo.f2
Foo.f1

4-1 继承的原理

对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表

print(D.mro()) # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, 
 <class '__main__.A'>, <class 'object'>]

python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

1.子类会先于父类被检查 

2.多个父类会根据它们在列表中的顺序被检查 

3.如果对下一个类存在两个合法的选择,选择第一个父类

1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的 顺序依次找下去,

2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,


4-2 深度优先

在python2中,未继承object的类及其子类,都是经典类

image

如上图,查找顺序为:obj->A->B->E->G->C->F->D->object


4-3 广度优先

image

如上图,查找顺序为:obj->A->B->E->C->F->D->G->object


5 派生

子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性

class People:
    school = '清华大学'

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
        
    def teach(self):
        print('%s is teaching' % self.name)
  • 方法一:“指名道姓”地调用某一个类的函数(不依赖继承)

    class Teacher(People):
        def __init__(self, name, sex, age, title):  
            People.__init__(self, name, age, sex)
            self.title = title  # 派生
    
        def teach(self):
            People.teach(self)
    
  • 方法二:super()(依赖继承)

    调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找

    class Teacher(People):
        def __init__(self, name, sex, age, title):  # 派生
            super().__init__(name, age, sex)
            self.title = title
    
        def teach(self):
            super(Teacher, self).teach()
    

这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找


6 组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系;而组合则是一种“有”的关系

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 Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def tell_birth(self):
       print('<%s-%s-%s>' %(self.year,self.mon,self.day))

class People:
    school='清华大学'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

#Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): #老师是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老师有生日
        self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)

# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date类的功能
teacher1.birth.tell_birth()

# 重用Course类的功能
for obj in teacher1.courses: 
    obj.tell_info()

7 抽象类

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。

抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。

import abc  # 利用abc模块实现抽象类

class Animal(metaclass=abc.ABCMeta):  # 抽象类只能被继承,不能被实例化
    all_type = 'animal'

    @abc.abstractmethod  # 定义抽象方法,无需实现功能
    def run(self): 
        pass

    @abc.abstractmethod
    def eat(self):
        pass

class People(Animal):
    def run(self):  # 子类必须定义该功能
        print('people is walking')

    def eat(self):  # 子类必须定义该功能
        print('people is eating')

penple1 = People()
penple1.run()

8 多态与多态性

多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪

多态性的好处在于增强了程序的灵活性和可扩展性

说白了就是多个类中有相同的函数属性。更是可以提供一个统一的接口。

# 多态
class Animal: #同一类事物:动物
    def talk(self):
        pass
class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('喵喵喵')
class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('哼哼哼')

#实例化得到三个对象
>>> cat=Cat()
>>> dog=Dog()
>>> pig=Pig()

多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用

# 多态性
>>> cat.talk()
喵喵喵
>>> dog.talk()
汪汪汪
>>> pig.talk()
哼哼哼

更进一步,我们可以定义一个统一的接口来使用

>>> def Talk(animal):
...     animal.talk()
... 
>>> Talk(cat)
喵喵喵
>>> Talk(dog)
汪汪汪
>>> Talk(pig)
哼哼哼

8-1 鸭子类型

其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing)

“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度

#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
    def read(self):
        pass
    def write(self):
        pass

class Disk: #Disk类也有两个与文件类型同名的方法:read和write
    def read(self):
        pass
    def write(self):
        pass

Python中一切皆对象,本身就支持多态性

l = list([1,2,3])
t = tuple(('a','b'))
s = str('hello')

# 我们可以在不考虑三者类型的情况下直接使用统计三个对象的长度
s.__len__()
l.__len__()
t.__len__()

# Python内置了一个统一的接口
"""
def len(obj):
	return obj.__len__()
"""
len(s)
len(l)
len(t)

9 封装

面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起

除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口


9-1 隐藏属性

Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式:

class Foo:
    __N=0 # 变形为_Foo__N

    def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__x=10 # 变形为self._Foo__x

    def __f1(self): # 变形为_Foo__f1
        print('__f1 run')

    def f2(self):  # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__f1() #变形为self._Foo__f1()

print(Foo.__N) # 报错AttributeError:类Foo没有属性__N
obj = Foo()
print(obbj.__x) # 报错AttributeError:对象obj没有属性__x

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

1、在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如Foo._A__N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。

print(Foo._Foo__N)

2、在类内部是可以直接访问双下滑线开头的属性的,比如self.__f1(),因为在类定义阶段类内部双下滑线开头的属性统一发生了变形。

>>> Foo.f2()
__f1 run

3、变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。

>>> Foo.__M=100
>>> Foo.__dict__
mappingproxy({..., '__M': 100,...})
>>> Foo.__M
100

4、子类无法覆盖父类中__开头的属性

class Foo:
    def __func(self):  # 变形后:_Foo__func
        print('from foo')

class Bar(Foo):
    def __func(self):  # 变形后:_Bar__func
        print('from bar')
        
# 在定义阶段类中的函数属性名,就已经变化了

9-2 隐藏数据属性

将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制

class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    # 查询接口
    def tell_info(self):
        print('name:<%s> age:<%s>' % (self.__name, self.__age))

    # 修改接口
    def set_info(self, name, age):
        if not isinstance(name, str):
            print('名字必须是字符串类型...')
            return
        if not isinstance(age, int):
            print('年龄必须是数值类型...')
            return

        self.__name = name
        self.__age = age

p = People('egon', 18)
p.set_info('lxx', "19")
p.tell_info()

9-3 隐藏函数属性

目的的是为了隔离复杂度,也可以说是开放接口.。

例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来;只调接口,就会全部执行起来。

class ATM:
    def __card(self):  # 插卡
        print('插卡')

    def __auth(self):  # 身份认证
        print('用户认证')

    def __input(self):  # 输入金额
        print('输入取款金额')

    def __print_bill(self):  # 打印小票
        print('打印账单')

    def __take_money(self):  # 取钱
        print('取款')

    def withdraw(self):  # 取款功能
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

obj = ATM()
obj.withdraw()

总结隐藏属性与开放接口,本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名、参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。


9-4 property装饰器

Python为了能让函数属性,能像数据属性一样的访问。

注意:

1、拥有property装饰器的函数属性不可以赋值
2、可以想数据属性一样访问。调用时可以不加括号。

以后使用可以理解为:动词不可用,名词可以用

列:(bmi就是人的名词属性)

BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为

体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86

身高或体重是不断变化的,因而每次想查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而非功能,为此Python专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果,例如

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('lxx', 65, 1.68)
print(p.bmi)  # 不用加括号了(使用者用起来就像是调数据属性一样很简洁方便)

使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能,如下

class People:
    def __init__(self, name):
        self.__name = name

    @property  # property的查询操作
    def name(self):
        return print(self.__name)

    @name.setter
    def name(self, val):
        if not isinstance(val, str):
            raise TypeError('%s 必须是字符串类型' % val)
        self.__name = val

    @name.deleter
    def name(self):
        raise PermissionError('不允许删除...')

p = People('egon')
p.name = "123"  # 修改
p.name  # 查看
del p.name  # 删除

10 绑定方法与非绑定方法

在类内部定义的函数,分为两大类:
一:绑定方法:绑定给谁,就应该由谁来调用,谁调用就会把调用者当作第一个参数自动传入
    绑定到对象的方法:在类内定义的没有被任何装饰器修饰的

    绑定到类的方法:在类内定义的被装饰器classmethod修饰的方法

二:非绑定方法(静态方法):没有自动传值这么一说。就是类中定义的普通工具
    非绑定方法:在类内定义的被装饰器staticmethod修饰的方法,不与类或者对象绑		定,没有自动传值,对象和类都可以使用
class Foo:
    def __init__(self, name):
        self.name = name

    def tell(self):
        print('name is %s' % self.name)

    @classmethod
    def func(cls):  # cls=Foo
        print(cls)

    @staticmethod
    def func1(x, y):
        print(x+y)

总结绑定方法与非绑定方法的使用:若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。


11 反射

python是动态语言,而反射(reflection)机制被视为动态语言的关键。反射机制指的是在程序的运行状态中。对于任意一个类,都可以知道这个类的所有属性和方法;对于任意一个对象,都能够调用他的任意方法和属性。这种动态获取程序信息以及动态调用对象的功能称为反射机制

# 反射:通过字符串映射到对象的属性
class People:
    country = 'china'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s is talking" % self.name)


obj = People('egon', 14)

# 判断obj对象中,有没有obj.name的属性
print(hasattr(obj, "name"))

# 取到obj对象中的name属性。没有返回None
print(getattr(obj, 'name', None))

# 在obj对象中添加一个健值 obj.sex = 'male'
setattr(obj, 'sex', 'male')

# 在obj对象中删除一个健值 del obj.age
delattr(obj, 'age')

# 包括类在内均可使用
print(getattr(People, 'country'))

小应用:

class Service:
    def run(self):
        while 1:
            inp = input(">>>:").strip()
            cmds = inp.split()  # cmds = ['get','a.txt]
            if hasattr(self, cmds[0]):
                func = getattr(self, cmds[0])
                func(cmds)

    def get(self, cmds):
        print('get......', cmds)

    def put(self, cmds):
        print('put......', cmds)

obj = Service()
obj.run()

12 内置方法

__xxxx__以杠杠开头或者杠杠结尾的内置方法,会在一种特定的情况下执行。

更多内置方法:http://www.cnblogs.com/linhaifeng/articles/6204014.html

1.isinstance(obj,cls)检查 obj是否是类 cls 的实例对象

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

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

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

12-1 item

item系列(类似于字典的取值,修改和删除)

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

    def __getitem__(self, item):  # item='name'
        return self.__dict__.get(item)

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __delitem__(self, key):
        del self.__dict__[key]

obj = Foo('egon')  # 实例化并不会执行

# 触发__getitem__执行,获取值
print(obj['name'])

# 触发__setitem__执行,修改/添加值
obj['sex'] = 'male'

# 触发__delitem__执行,删除值
del obj['name']

12-2 str

__str__类型(改变对象的字符串显示效果__str__)

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

    def __str__(self):
        return '{name: %s, age: %s}' % (self.name, self.age)

obj = People('alex', 19)
print(obj)

str方法定义完之后,会在打印的时候执行,且必须返回字符串类型。


12-3 del

__del__类型,回收系统资源

class Open:
    def __init__(self, filename):
        print('open file ...')
        self.filename = filename

    def __del__(self):
        print('回收系统资源:self.close()')

f = Open('settings.py')
# del f  # 如果调用,那么会先执行,不调用python默认执行。
print('____main____')
# 当main执行完后,程序结束,python会执行Open类del函数,回收系统资源

12-4 call

__call__方法,能够将对象变成一个可调用对象

class Foo:
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

obj = Foo()
obj(1, 2, 3, a=1, b=2, c=3)  # 对象在执行时,触发__call__方法

13 元类

一切皆对象,对象可以怎么用?
1.都可以被引用,x=obj
2.都可以当作函数的参数传入
3.都可以当作函数的返回值
4.都可以当作容器类的元素

诸备知识exec,exec相当于一个函数的执行
参数1:字符串形式的命令
参数2:全局作用域(字典形式),如果不指定默认就使用globals()
参数3:局部作用域(字典形式),如果丕指定默认就使用locals()

产生类的类称为元类,默认所有用class定义的类,它们的元类都是type

定义类的两种方式:

  • 方式一:class
class Chinese:  # Chinese = type(...)
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name
  • 方式二:type

    定义类的三要素(类名,类的基类们,类的名称空间)

class_name = 'Chinese'
class_bases = (object,)
class_body = """
country = 'China'

def __init__(self, name, age):
    self.name = name
    self.age = age

def talk(self):
    print('%s is talking' % self.name)
"""
class_dic = {}
exec(class_body, globals(), class_dic)

Chinese1 = type(class_name, class_bases, class_dic)

13-1 自定义元类控制类的创建

一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名的首字母必须大写...')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必须有注释,且不能为空...')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)


class Chinese(object, metaclass=Mymeta):
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)

# Chinese = Mymeta(class_name, class_bases, class_dic)

13-2 自定义元类控制类的实例化

元类内部有一个__call__方法,会在调用类时触发

class Foo:
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

obj=Foo()
#1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
#2、调用obj的返回值就是__call__方法的返回值
res=obj(1,2,3,x=1,y=2)

由上例得知,调用一个对象,就是触发对象所在类中的__call__方法的执行,如果把Chinese也当做一个对象,那么在Chinese这个对象的类中也必然存在一个__call__方法

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名的首字母必须大写...')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必须有注释,且不能为空...')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # print(self)  # self = Chinese
        # print(args)  # args = ('egon',)
        # print(kwargs)  # kwargs = {'age': 18}

        # 第一件事:先造一个空对象obj
        obj = object.__new__(self)
        # 第二件事:初始化obj
        self.__init__(obj, *args, **kwargs)
        # 第三件事:返回obj
        return obj

class Chinese(object, metaclass=Mymeta):
    """
    中国人
    """
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)


obj = Chinese('egon', age=18)  # Chinese.__call__(Chinese, 'egon', 18)

print(obj.__dict__)

# 调用Chinese就是在调用Chinese类中的__call__方法
# 然后将Chinese传给self,溢出的位置参数传给*,溢出的关键字参数传给**
# 调用Chinese的返回值就是调用__call__的返回值

13-3 单例模式

"""
x1 = int(1)
x2 = int(1)
指向同一块内存地址
"""
int就是一种优化机制。指向同一块内存地址

单例模式是一种优化方式:避免重复申请一样功能的内存空间

class MySQL:
    __instance = None

    def __init__(self):
        self.host = '127.0.0.1'
        self.port = 3306

    @classmethod
    def singleton(cls):
        if not cls.__instance:
            obj = cls()
            cls.__instance = obj
        return cls.__instance


    def conn(self):
        pass

    def execute(self):
        pass


obj1 = MySQL.singleton()
obj2 = MySQL.singleton()

print(obj1 is obj2)

13-4 自定义元类控制类的实例化应用

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名的首字母必须大写...')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必须有注释,且不能为空...')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
        self.__instance = None

    def __call__(self, *args, **kwargs):
        if not self.__instance:
            obj = object.__new__(self)
            self.__init__(obj)
            self.__instance = obj

        return self.__instance


class Mysql(object, metaclass=Mymeta):
    """
    中国类
    """
    def __init__(self):
        self.host = '127.0.0.1'
        self.port = 3306

    def conn(self):
        pass

    def execute(self):
        pass


obj1 = Mysql()
obj2 = Mysql()

print(obj1 is obj2)

13-5 元类的属性查找

class Mymeta(type): 
    n=444

class Bar(object):
    n=333

class Foo(Bar):
    n=222
    
class StanfordTeacher(Foo,metaclass=Mymeta):
    n=111
    
print(StanfordTeacher.n)
自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为StanfordTeacher->Foo->Bar->object->Mymeta->type

14 面向对象的软件开发

1、面向对象分析(OOA)

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

建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。

2、面向对象设计(OOD)

根据面向对象分析阶段形成的需求模型,对每一部分,分别进行具体的设计。

首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。

在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述

3、面向对象编程(OOF)

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

4、面向对象测试(OOT)

在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。

面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。

5、面向对象维护(OOSM)

正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。

由于使用了面向对象的方法开发程序,使用程序的维护比较容易。

因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。

在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。

现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。

posted @ 2022-09-22 16:33  角角边  Views(68)  Comments(0)    收藏  举报