Python面向对象编程进阶


Python全面采用了面向对象的思想,是真正的面向对象的编程语言.一切皆为对象

  • OOP(Object oriented programming)面向对象
  • 一种针对大型软件程序的编程思想
  • 特点
    • 扩展性强,可读性好
    • 使编程就和堆积木一样
    • 将数据(属性)和操作方法(函数)全部封装到对象中
    • 组织代码的方式更加接近人的思维

面向对象与面向过程

  • 区别

    • 面向过程思维

      • "执行者"思维,适合编写小规模程序
      • 我们首先思考"按照什么步骤"实现,一步一步最终完成,适合一些简单的事情,不需要协作关注"程序的逻辑流程"
      • 例如:
      • 开车 (发动车->挂挡->踩油门->走喽)
      • 做饭 (买菜->洗菜->切菜->开火炒菜->开饭了)
    • 面向对象思维

      • "设计者"思维,适合编写大型程序

      • 更加关注"对象之间的关系"

      • 我们首先思考"怎么设计这个东西"

      • 然后我们把这个东西拆分成一个个的物体object(对象)

      • 然后通过分工协作来完成,每个类完成一个部分

      • 例如造车\飞机\轮船\火箭,我们必须先要确定这些复杂的设备是由什么构成的。

        汽车 发动机 ->发动机厂生产 轮胎 ->轮胎厂生产 座椅 ->座椅厂生产 挡风玻璃 ->玻璃厂生产

  • 相同点

    • 都是解决问题的思维方式
    • 都是代码的组织方式
    • 人天然就有的能力,一点都不神奇小朋友都能搞明白的问题
  • 配合

    • 宏观面向对象,微观面向过程
    • 简单问题还得面向过程
    • 复杂问题,就得面向对象了
    • 面向对象是宏观把握,从整体上分析系统.
    • 具体到实现部分的微观操作(方法)需要使用面向过程的思路去处理
    • 面向对象和面向过程其实是相辅相成,面向对象离不开面向过程

面向对象哲学

一切皆对象(object) 东西,物体,目标

对象是数据和操作的封装

  • 数据:Value() (属性) 人类(年龄、性别、姓名、身高、体重) 变量
  • 操作:行为,功能,技能 (方法)(吃、发声)

对象是独立的,但是对象之间可以相互作用

目前面向对象是最接近人类认知的编程范式。

面向对象的三要素

1.封装

  • 组装:将数据和操作组装到一起。

    • 将和对象的属性和操作这些属性的方法放入到同一个类中。
  • 隐藏数据:对象只暴露一些接口,通过接口访问对象。

    • 例如:汽车驾驶员通过方向盘、油门、刹车、挂挡来操作汽车,可以不需要了解汽车的机动原理。

2.继承

  • 子类继承父类之后,子类自动拥有类父类的所有公开的属性和方法。

  • 多复用,继承来就不需要自己写了。

  • 多继承少修改,OCP原则,使用继承改变,体现个性。

3.多态

  • 鸭子类型实现多态,一个动物,走起路来向鸭子,叫声也像鸭子,那么我们就认为它是鸭子。(不同的类中,使用相同的方法命名类似的功能。)

  • 面向对象编程最灵活的地方,动态绑定

  • 其他语言中子类继承父类后,同样的方法实现不同的功能。

  • 为了实现接口。

  • json dump() load() dumps() loads()

  • pickle dump() load() dumps() loads()

USB (多态)
鼠标
键盘
网卡
画图板
... 

list.pop(index)
dict.pop(key)

类的定义

1.类的概念

  • 类是对象的模板(描述),类是一类特殊的对象。
  • 是对一群具有相同特征或行为的事物的一个统称,是抽象(把像的东西给提取处理)的,不能直接使用(就好比,飞机图纸不能直接飞上天)
  • 特征:被称为属性 (被封装到类中的局部变量)
  • 行为:被称为方法 (被封装到类中的函数)
  • 类 就相当于制造飞机时的图纸,是一个模板 ,是负责创建对象的

class 类名:定义属性或方法

class 类名:
    语句块

1.必须使用class关键字

2.类名必须是使用大写驼峰命名

  • 大驼峰命名法:
    • 1.每个单词的首字母大写
    • 2.单词与单词之间没有下划线

3.类名:满足这类事物的名字

  • 类名的确定: 名词提炼法
  • 分析整个业务流程,出现的名词,通常就是找到的类名

4.属性: 这个类创建出来的对象有什么样的特征

5.方法:这个类创建出来的对象有什么样的行为

  • 方法的定义格式和函数的几乎一样
  • 区别在于第一个参数必须是self

6.类定义完成后,就产生了一个类对象,绑定到了ClassName上了。

class MyClass:
    """
    这是一个类
    """
    x = "abc"  # 类属性

    def foo(self):  # 类属性foo,也是方法
        return "My Class"

print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
class Student(object):
    # __init__是一个特殊方法用于在创建对象时进行初始化操作
    # 通过这个方法我们可以为学生对象绑定name和age两个属性
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self, course_name):
        print(f'{self.name}正在学习{course_name}.')

    # PEP 8要求标识符的名字用全小写多个单词用下划线连接
    # 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
    def watch_movie(self):
        if self.age < 18:
            print(f'{self.name}只能观看《熊出没》.')
        else:
            print(f'{self.name}正在观看XXX电影.')

def main():
    # 创建学生对象并指定姓名和年龄
    stu1 = Student('小明', 20)
    # 给对象发study消息
    stu1.study('Python程序设计')
    # 给对象发watch_av消息
    stu1.watch_movie()
    stu2 = Student('王大锤', 15)
    stu2.study('思想品德')
    stu2.watch_movie()


if __name__ == '__main__':
    main()            

元类:创建类的类

类:用来创建对象的。

封装:将数据封装(存储)到对象中。将方法封装(存储)到类中。

对象

对象的产生

  • 简单数据 -- 数据量过大

  • 数组/列表 -- 单一类型数据

    • [1,2,3,4,5,6]
  • 结构体 -- 不同类型数据

    {
       int    id;
       string name;
       char   sex;
    }

    struct {
        int a;
        char b;
        double c;
    } s1;
  • 对象 -- 将不同类型数据(属性)和方法(函数)放在一起

对象包含

  • id(身份识别码)

  • type(对象的类型)

  • value(对象的值)

    • (1)属性 attribute

    • (2)方法 method

我们要通过类创建好实例对象后,才能使用类定义的实例属性和方法. 函数和类也是对象,属于python的一等公民 1.可以赋值给一个变量 2.可以添加到列表,元组,集合,字典对象中 3.可以作为参数传递给函数 4.可以作为函数的返回值

属性和方法

self

在类中定义的方法必须有个额外的第一个参数,就是self,表示创建的类实例本身 属性就是在类中定义的变量

  • 实例属性

    • 从属于对象的属性也称实例变量

    • 一般在__init__()中定义

    • self.实例属性=初始化

    • 调用

    • 创建实例对象后

    • obj = 类名()

    • obj.实例属性=值

    • 类内部 self.实例属性名

  • 私有属性

    • 实现"封装",隐藏数据,只能在类的内部调用
    • 两个下滑线__开头的属性是私有属性(private),其他为公共属性(public).
    • 不是特别严格,主要靠自觉
  • 类属性

    • 属于类的属性,所有对象共享
    • 定义
      • 使用类名.类变量名来读写
    • 特殊属性
      • obj.__dict__对象的属性字典 (对象中的所有属性)
      • obj.__class__对象的所属的类
      • class.__bases__类的基类元组(多继承)
      • class.__base__类的基类
      • class.__mro__类的层次结构
      • class.__subclasssess__()子类列表
  • 方法

    • 方法是行为,是被所有对象共享的.
    • 特殊方法(魔术方法)
      • 创建函数__new__()用于创建对象,一般不需要我们自定义
      • 构造函数__init__()对象的初始化,对象建立后,自动执行,初始化当前对象的相关属性,无返回值
      • 析构函数__del__() 当类被销毁时,执行的操作。
      • 回调函数__call__()表示对象可以和函数一样被调用.
      • 对象描述__str__()__repr__()
# __new__() 方法是在类准备将自身实例化时调用; 
def __new__(cls,*args,**kwargs):
    return object.__new__(cls)
# 对象建立后,自动执行,初始化当前对象的相关属性,无返回值
def __init__(self,参数列表):
    # 需要被初始化的属性或方法
    pass
# 析构函数
def __del__(self):
    pass
# 当类被销毁时,执行的操作
# 一般用来释放对象占用的资源
# 如打开的文件或链接的网络
# 一般不用自定义析构函数

垃圾回收机制

python实现自动的垃圾回收机制,当对象没有被引用时(引用数为0),由垃圾回收器调用__del__方法 del 销毁对象时,会调用__del__方法

回调函数__call__()

表示对象可以和函数一样被调用. 调用函数本质是调用了__call__方法 在Python中,函数其实是一个对象,这个对象的类中定义了__call__() 案例

对象描述__str__()__repr__()

用于描述对象的信息,也就是print(对象)的结果

Python 魔法方法

基础:

如果你想… 所以,你写… Python调用…
初始化一个实例 x = MyClass() x.__init__()
作为一个字符串的"官方"表示 repr(x) x.__repr__()
作为一个字符串 str(x) x.__str__()
作为字节数组 bytes(x) x.__bytes__()
作为格式化字符串 format(x, format_spec) x.__format__(format_spec)
  • __init__()方法在创建实例后调用.如果你想控制创建过程,请使用__new__()方法
  • 按照惯例, __repr__() 应该返回一个有效的Python表达式的字符串
  • __str__()方法也被称为你的print(x)

迭代相关

如果你想… 所以,你写… Python调用…
遍历一个序列 iter(seq) seq.__iter__()
从迭代器中获取下一个值 next(seq) seq.__next__()
以相反的顺序创建一个迭代器 reversed(seq) seq.__reversed__()
  • __iter__()无论何时创建新的迭代器,都会调用该方法.
  • __next__()每当你从迭代器中检索一下个值的时候,都会调用该方法
  • __reversed__()方法并不常见.它需要一个现有序列并返回一个迭代器,该序列是倒序的顺序.

属性

如果你想… 所以,你写… Python调用…
得到一个属性 x.my_property x.__getattribute__('my_property')
获得一个属性 x.my_property x.__getattr__('my_property')
设置一个属性 x.my_property = value x.__setattr__('my_property', value)
删除一个属性 del x.my_property x.__delattr__('my_property')
列出所有属性和方法 dir(x) x.__dir__()
  • 如果你的类定义了一个__getattribute__()方法,Python将在每次引用任何属性或方法名时调用它.
  • 如果你的类定义了一个__getattr__()方法,Python只会在所有普通地方查找属性后调用它.如果一个实例x定义了一个属性 color, x.color将不会调用x.__getattr__('color'); 它将简单地返回已经定义的x.color值.
  • __setattr__()只要你为属性指定值,就会调用该方法.
  • __delattr__()只要删除属性,就会调用该方法.
  • __dir__()如果您定义一个__getattr__() 或者 __getattribute__() 方法,该方法很有用.通常情况下,调用dir(x)只会列出常规属性和方法.

getattr()和__getattribute__()方法之间的区别很微妙但很重要.

函数类

通过定义__call__()方法,您可以创建一个可调用类的实例 - 就像函数可调用一样.

如果你想… 所以,你写… Python调用…
来"调用"像函数一样的实例 my_instance() my_instance.__call__()

行为

如果你的类作为一组值的容器 - 也就是说,如果问你的类是否"包含"一个值是有意义的 - 那么它应该定义下面的特殊方法,使它像一个集合一样.

如果你想… 所以,你写… Python调用…
序列的数量 len(s) s.__len__()
否包含特定的值 x in s s.__contains__(x)

字典(映射)

如果你想… 所以,你写… Python调用…
通过它的key来获得值 x[key] x.__getitem__(key)
通过它的key来设置一个值 x[key] = value x.__setitem__(key, value)
删除键值对 del x[key] x.__delitem__(key)
为丢失的key提供默认值 x[nonexistent_key] x.__missing__(nonexistent_key)

数字

如果你想… 所以,你写… Python调用…
x + y x.__add__(y)
x - y x.__sub__(y)
x * y x.__mul__(y)
整除 x / y x.__trueiv__(y)
x // y x.__floordiv__(v)
取余 x % y x.__mod__(y)
整除与取余 divmod(x, y) x.__divmod__(y)
平方 x ** y x.__pow__(y)
左移 x << y x.__lshift__(y)
右移 x >> y x.__rshift__(y)
按位and运算 x & y x.__and__(y)
按位xor或运算 x ^ y x.__xor__(y)
按位or运算 x | y x.__or__(y)

上述一组特殊方法采用第一种方法:给定x / y,它们提供了一种方法让x说"我知道如何用y整除自己".以下一组特殊方法解决了第二种方法:它们为y提供了一种方法来说"我知道如何成为分母,并将自己整除x".

如果你想… 所以,你写… Python调用…
x + y x.__radd__(y)
x - y x.__rsub__(y)
x * y x.__rmul__(y)
整除 x / y x.__rtrueiv__(y)
x // y x.__rfloordiv__(v)
取余 x % y x.__rmod__(y)
整除与取余 divmod(x, y) x.__rdivmod__(y)
平方 x ** y x.__rpow__(y)
左移 x << y x.__rlshift__(y)
右移 x >> y x.__rrshift__(y)
按位and运算 x & y x.__rand__(y)
按位xor或运算 x ^ y x.__rxor__(y)
按位or运算 x | y x.__ror__(y)

可是等等!还有更多!如果你正在进行"就地"操作,如x /= 3则可以定义更多特殊的方法.

如果你想… 所以,你写… Python调用…
x + y x.__iadd__(y)
x - y x.__isub__(y)
x * y x.__imul__(y)
整除 x / y x.__itrueiv__(y)
x // y x.__ifloordiv__(v)
取余 x % y x.__imod__(y)
整除与取余 divmod(x, y) x.__idivmod__(y)
平方 x ** y x.__ipow__(y)
左移 x << y x.__ilshift__(y)
右移 x >> y x.__irshift__(y)
按位and运算 x & y x.__iand__(y)
按位xor或运算 x ^ y x.__ixor__(y)
按位or运算 x | y x.__ior__(y)

还有一些"单个数"数学运算可以让你自己对类似数字的对象进行数学运算.

如果你想… 所以,你写… Python调用…
负数 -x x.__neg__()
正数 +x x.__pos__()
绝对值 abs(x) x.__abs__()
~x x.__invert__()
复数 complex(x) x.__complex__()
整数 int(x) x.__int__()
浮点数 float(x) x.__float__()
四舍五入到最近的整数 round(x) x.__round__()
四舍五入到最近的n位数 round(x, n) x.__round__(n)
最小整数 math.ceil(x) x.__ceil__()
最大整数 math.floor(x) x.__floor__()
截断x到0的最接近的整数 math.trunc(x) x.__trunc__()
数字作为列表索引 a_list[x] a_list[x.__index__()]

比较

如果你想… 所以,你写… Python调用…
等于 x == y x.__eq__(y)
不等于 x != y x.__ne__(y)
小于 x < y x.__lt__(y)
小于等于 x <= y x.__le__(y)
大于 x > y x.__gt__(y)
大于等于 x >= y x.__ge__(y)
布尔 if x: x.__bool__()

序列化

如果你想… 所以,你写… Python调用…
对象副本 copy.copy(x) x.__copy__()
深拷贝 copy.deepcopy(x) x.__deepcopy__()
序列化一个对象 pickle.dump(x, file) x.__getstate__()
序列化一个对象 pickle.dump(x, file) x.__reduce__()
序列化一个对象 pickle.dump(x, file, protocol_version) x.__reduce_ex__(protocol_version)
取出恢复后的状态 x = pickle.load(fp) x.__getnewargs__()
取出恢复后的状态 x = pickle.load(fp) x.__setstate__()

with 语句

with块限定了运行时上下文;在执行with语句时,"进入"上下文,并在执行块中的最后一个语句后"退出"上下文.

如果你想… 所以,你写… Python调用…
进入with语句块 with x: x.__enter__()
退出with语句块 with x: x.__exit__(exc_type, exc_value, traceback)

真正深奥的东西

如果你想… 所以,你写… Python调用…
一个类的构造函数 x = MyClass() x.__new__()
一个类的析构函数 del x x.__del__()
只有一组特定的属性需要定义 x.__slots__() 限定属性的使用
hash码 hash(x) x.__hash__()
获得一个属性的值 x.color type(x).__dict__['color'].__get__(x, type(x))
设置一个属性的值 x.color = 'PapayaWhip' type(x).__dict__['color'].__set__(x, 'PapayaWhip')
删除一个属性 del x.color type(x).__dict__['color'].__del__(x)
一个对象是否是你的一个类的实例 isinstance(x, MyClass) MyClass.__instancecheck__(x)
一个类是否是你的类的子类 isinstance(C, MyClass) MyClass.__subclasscheck__(C)
一个类是否是抽象基类的实例 isinstance(C, MyABC) MyABC.__subclasshook__(C)

函数和方法的区别

  • 都是用用来完成一个功能语句块,本质一样

  • 方法通过对象来调用.普通函数不需要

  • 方法定义第一个参数是self,函数不需要

实例对象的本质

a= ()
a.方法()
等价
.方法(a)
私有方法
双下划线开头的方法
只能在类的内部被调用的方法

@property装饰器

之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

class Person(object):

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

    # 访问器 - getter方法
    @property
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute


if __name__ == '__main__':
    main()

__slots__魔法属性

​ 我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。

class Person(object):

    # 限定Person对象只能绑定_name, _age和_gender属性
    __slots__ = ('_name', '_age', '_gender')

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

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True

静态方法和类方法

​ 之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 静态方法和类方法都是通过给类发消息来调用的
    if Triangle.is_valid(a, b, c):
        t = Triangle(a, b, c)
        print(t.perimeter())
        # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
        # print(Triangle.perimeter(t))
        print(t.area())
        # print(Triangle.area(t))
    else:
        print('无法构成三角形.')


if __name__ == '__main__':
    main()

​ 和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。

from time import time, localtime, sleep


class Clock(object):
    """数字时钟"""

    def __init__(self, hour=0, minute=0, second=0):
        self._hour = hour
        self._minute = minute
        self._second = second

    @classmethod
    def now(cls):
        ctime = localtime(time())
        return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

    def run(self):
        """走字"""
        self._second += 1
        if self._second == 60:
            self._second = 0
            self._minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """显示时间"""
        return '%02d:%02d:%02d' % \
               (self._hour, self._minute, self._second)


def main():
    # 通过类方法创建对象并获取系统时间
    clock = Clock.now()
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

类之间的关系

简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。

  • is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
  • has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
  • use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

​ 利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。

继承和多态

​ 可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。

​ 提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。

​ 子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为。

super

​ 在子类的函数中,若是要重用父类中某个函数的功能,可以直接通过 super 来调用父类中的函数,当然也可以通过 类名.func() 来调用,只不过这样与调用普通函数无异。这个经常使用在需要对父类的同名方法进行扩展的场景~

class Father:
    def say(self):
        print('Hello !')

    def introduce(self):
        print('Father')

class Son(Father):
    def say(self):
        super().say()
        # Father.say(self)   # 通过 类名.func(),输出结果一致
        print('你好 ~')

p = Son()
p.say()

子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。

class Person(object):
    """人"""

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

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        print('%s正在愉快的玩耍.' % self._name)

    def watch_av(self):
        if self._age >= 18:
            print('%s正在观看爱情动作片.' % self._name)
        else:
            print('%s只能观看《熊出没》.' % self._name)


class Student(Person):
    """学生"""

    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self._grade = grade

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, grade):
        self._grade = grade

    def study(self, course):
        print('%s%s正在学习%s.' % (self._grade, self._name, course))


class Teacher(Person):
    """老师"""

    def __init__(self, name, age, title):
        super().__init__(name, age)
        self._title = title

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    def teach(self, course):
        print('%s%s正在讲%s.' % (self._name, self._title, course))


def main():
    stu = Student('王大锤', 15, '初三')
    stu.study('数学')
    stu.watch_av()
    t = Teacher('小明', 38, '砖家')
    t.teach('Python程序设计')
    t.watch_av()


if __name__ == '__main__':
    main()

​ 通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。

from abc import ABCMeta, abstractmethod

# 多态与抽象类
class Pet(object, metaclass=ABCMeta):
    """宠物"""

    def __init__(self, nickname):
        self._nickname = nickname

    # 抽象方法,必须要被重写
    @abstractmethod
    def make_voice(self):
        """发出声音"""
        pass


class Dog(Pet):
    """狗"""

    def make_voice(self):
        print('%s: 汪汪汪...' % self._nickname)


class Cat(Pet):
    """猫"""

    def make_voice(self):
        print('%s: 喵...喵...' % self._nickname)


def main():
    pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
    # 接口 适配
    for pet in pets:
        pet.make_voice()


if __name__ == '__main__':
    main()

​ 在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。

综合案例

案例1:奥特曼打小怪兽

from abc import ABCMeta, abstractmethod
from random import randint, randrange

# 战斗者抽象类
class Fighter(object, metaclass=ABCMeta):
    """战斗者"""

    # 通过__slots__魔法限定对象可以绑定的成员变量
    __slots__ = ('_name', '_hp') 

    def __init__(self, name, hp):
        """初始化方法
        :param name: 名字
        :param hp: 生命值
        """
        self._name = name
        self._hp = hp

    @property
    def name(self):
        return self._name

    @property
    def hp(self):
        return self._hp

    @hp.setter
    def hp(self, hp):
        self._hp = hp if hp >= 0 else 0

    @property
    def alive(self):
        return self._hp > 0

    @abstractmethod
    def attack(self, other):
        """攻击

        :param other: 被攻击的对象
        """
        pass


class Ultraman(Fighter):
    """奥特曼"""

    __slots__ = ('_name', '_hp', '_mp')

    def __init__(self, name, hp, mp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值
        :param mp: 魔法值
        """
        super().__init__(name, hp)
        self._mp = mp

    def attack(self, other):
        other.hp -= randint(15, 25)

    def huge_attack(self, other):
        """究极必杀技(打掉对方至少50点或四分之三的血)
        :param other: 被攻击的对象
        :return: 使用成功返回True否则返回False
        """
        if self._mp >= 50:
            self._mp -= 50
            injury = other.hp * 3 // 4
            injury = injury if injury >= 50 else 50
            other.hp -= injury
            return True
        else:
            self.attack(other)
            return False

    def magic_attack(self, others):
        """魔法攻击
        :param others: 被攻击的群体
        :return: 使用魔法成功返回True否则返回False
        """
        if self._mp >= 20:
            self._mp -= 20
            for temp in others:
                if temp.alive:
                    temp.hp -= randint(10, 15)
            return True
        else:
            return False

    def resume(self):
        """恢复魔法值"""
        incr_point = randint(1, 10)
        self._mp += incr_point
        return incr_point

    def __str__(self):
        return '~~~%s奥特曼~~~\n' % self._name + \
            '生命值: %d\n' % self._hp + \
            '魔法值: %d\n' % self._mp


class Monster(Fighter):
    """小怪兽"""

    __slots__ = ('_name', '_hp')

    def attack(self, other):
        other.hp -= randint(10, 20)

    def __str__(self):
        return '~~~%s小怪兽~~~\n' % self._name + \
            '生命值: %d\n' % self._hp


def is_any_alive(monsters):
    """判断有没有小怪兽是活着的"""
    for monster in monsters:
        if monster.alive > 0:
            return True
    return False


def select_alive_one(monsters):
    """选中一只活着的小怪兽"""
    monsters_len = len(monsters)
    while True:
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:
            return monster


def display_info(ultraman, monsters):
    """显示奥特曼和小怪兽的信息"""
    print(ultraman)
    for monster in monsters:
        print(monster, end='')


def main():
    u = Ultraman('迪迦', 1000, 120)
    m1 = Monster('炎魔战士', 250)
    m2 = Monster('哥布纽', 500)
    m3 = Monster('迪莫杰厄', 750)
    ms = [m1, m2, m3]
    fight_round = 1
    while u.alive and is_any_alive(ms):
        print('========第%02d回合========' % fight_round)
        m = select_alive_one(ms)  # 选中一只小怪兽
        skill = randint(1, 10)   # 通过随机数选择使用哪种技能
        if skill <= 6:  # 60%的概率使用普通攻击
            print('%s使用普通攻击打了%s.' % (u.name, m.name))
            u.attack(m)
            print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
        elif skill <= 9:  # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
            if u.magic_attack(ms):
                print('%s使用了魔法攻击.' % u.name)
            else:
                print('%s使用魔法失败.' % u.name)
        else:  # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
            if u.huge_attack(m):
                print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
            else:
                print('%s使用普通攻击打了%s.' % (u.name, m.name))
                print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
        if m.alive > 0:  # 如果选中的小怪兽没有死就回击奥特曼
            print('%s回击了%s.' % (m.name, u.name))
            m.attack(u)
        display_info(u, ms)  # 每个回合结束后显示奥特曼和小怪兽的信息
        fight_round += 1
    print('\n========战斗结束!========\n')
    if u.alive > 0:
        print('%s奥特曼胜利!' % u.name)
    else:
        print('小怪兽胜利!')


if __name__ == '__main__':
    main()

案例2:扑克游戏

import random


class Card(object):
    """一张牌"""

    def __init__(self, suite, face):
        self._suite = suite
        self._face = face

    @property
    def face(self):
        return self._face

    @property
    def suite(self):
        return self._suite

    def __str__(self):
        if self._face == 1:
            face_str = 'A'
        elif self._face == 11:
            face_str = 'J'
        elif self._face == 12:
            face_str = 'Q'
        elif self._face == 13:
            face_str = 'K'
        else:
            face_str = str(self._face)
        return '%s%s' % (self._suite, face_str)

    def __repr__(self):
        return self.__str__()


class Poker(object):
    """一副牌"""

    def __init__(self):
        self._cards = [Card(suite, face) 
                       for suite in '♠♥♣♦'
                       for face in range(1, 14)]
        self._current = 0

    @property
    def cards(self):
        return self._cards

    def shuffle(self):
        """洗牌(随机乱序)"""
        self._current = 0
        random.shuffle(self._cards)

    @property
    def next(self):
        """发牌"""
        card = self._cards[self._current]
        self._current += 1
        return card

    @property
    def has_next(self):
        """还有没有牌"""
        return self._current < len(self._cards)


class Player(object):
    """玩家"""

    def __init__(self, name):
        self._name = name
        self._cards_on_hand = []

    @property
    def name(self):
        return self._name

    @property
    def cards_on_hand(self):
        return self._cards_on_hand

    def get(self, card):
        """摸牌"""
        self._cards_on_hand.append(card)

    def arrange(self, card_key):
        """玩家整理手上的牌"""
        self._cards_on_hand.sort(key=card_key)


# 排序规则-先根据花色再根据点数排序
def get_key(card):
    return (card.suite, card.face)


def main():
    p = Poker()
    p.shuffle()
    players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
    for _ in range(13):
        for player in players:
            player.get(p.next)
    for player in players:
        print(player.name + ':', end=' ')
        player.arrange(get_key)
        print(player.cards_on_hand)


if __name__ == '__main__':
    main()

说明: 大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏,例如21点(Black Jack),游戏的规则可以自己在网上找一找。

案例3:工资结算系统

"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod


class Employee(object, metaclass=ABCMeta):
    """员工"""

    def __init__(self, name):
        """
        初始化方法

        :param name: 姓名
        """
        self._name = name

    @property
    def name(self):
        return self._name

    @abstractmethod
    def get_salary(self):
        """
        获得月薪

        :return: 月薪
        """
        pass


class Manager(Employee):
    """部门经理"""

    def get_salary(self):
        return 15000.0


class Programmer(Employee):
    """程序员"""

    def __init__(self, name, working_hour=0):
        super().__init__(name)
        self._working_hour = working_hour

    @property
    def working_hour(self):
        return self._working_hour

    @working_hour.setter
    def working_hour(self, working_hour):
        self._working_hour = working_hour if working_hour > 0 else 0

    def get_salary(self):
        return 150.0 * self._working_hour


class Salesman(Employee):
    """销售员"""

    def __init__(self, name, sales=0):
        super().__init__(name)
        self._sales = sales

    @property
    def sales(self):
        return self._sales

    @sales.setter
    def sales(self, sales):
        self._sales = sales if sales > 0 else 0

    def get_salary(self):
        return 1200.0 + self._sales * 0.05


def main():
    emps = [
        Manager('刘备'), Programmer('诸葛亮'),
        Manager('曹操'), Salesman('荀彧'),
        Salesman('吕布'), Programmer('张辽'),
        Programmer('赵云')
    ]
    for emp in emps:
        if isinstance(emp, Programmer):
            emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
        elif isinstance(emp, Salesman):
            emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
        # 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
        print('%s本月工资为: ¥%s元' %
              (emp.name, emp.get_salary()))


if __name__ == '__main__':
    main()

反射

反射自省的概念

自省:自省就是能够获得自身的结构和方法,给开发者可以灵活的调用,给定一个对象,返回该对象的所有属性和函数列表,或给定对象和该对象的函数或者属性的名字,返回对象的函数或者属性实例。 反射就是通过字字符串的形式来操作对象或者模块的成员,一种基于字符串的事件驱动。

hasattr(obj,"属性名/方法名") # 判断对象中是否存在某个方法或属性

getattr(obj,"属性名/方法名") # 通过属性名来获取数据的结果

setattr(obj,"属性名/方法名(key)",value) # 绑定或修改对象的属性

delattr(obj,"属性名/方法名(key)") # 删除属性或方法

hasattr

判断对象中是否有这个方法或变量

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)

p = Person("laowang")        
print(hasattr(p,"talk"))    # True。因为存在talk方法
print(hasattr(p,"name"))    # True。因为存在name变量
print(hasattr(p,"abc"))     # False。因为不存在abc方法或变量

getattr

获取对象中的方法或变量的内存地址

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)
p = Person("laowang")

n = getattr(p,"name")   # 获取name变量的内存地址
print(n)                # 此时打印的是:laowang

f = getattr(p,"talk")   # 获取talk方法的内存地址
f()                     # 调用talk方法

我们发现getattr有三个参数那么第三个参数是做什么用的呢?
s = getattr(p,"abc","not find")
print(s)                # 打印结果:not find。因为abc在对象p中找不到,本应该报错,属性找不到,但因为修改了找不到就输出not find

setattr

为对象添加变量或方法

def abc(self):
    print("%s正在交谈"%self.name)

class Person(object):
    def __init__(self,name):
        self.name = name

p = Person("laowang")
setattr(p,"talk",abc)   # 将abc函数添加到对象中p中,并命名为talk
p.talk(p)               # 调用talk方法,因为这是额外添加的方法,需手动传入对象


setattr(p,"age",30)     # 添加一个变量age,复制为30
print(p.age)            # 打印结果:30

delattr

删除对象中的变量。注意:不能用于删除方法

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)

p = Person("laowang")

delattr(p,"name")       # 删除name变量
print(p.name)           # 此时将报错
  • 通过__slots__魔法属性限定对象可以绑定的成员变量

类的装饰器

用装饰器增强一个类,把功能附加上去,哪个类需要,就装饰它

def printable(cls):
    # 内层函数作为是给类增加的实例方法
    def _print(self):
        print("装饰器")

    # 将内层函数和类绑定
    cls.print = _print
    return cls


@printable
class A:
    pass


a = A()
a.print()

Mixin模式

混合模式

mixin模式大致来说可以看成是多继承的一种。

​ 为什么会有多继承呢,有些时候单继承不能很好的满足我们的需求。例如: 汽车和飞机可以同属于交通工具,但是飞机可以飞,汽车不可以,所以飞行不能写进交通工具这个类里。那我们需要表示飞机能飞的属性,就要进行多继承。

​ 在java中多继承的这个操作可以用interface(接口)来完成,但是Python没有interface。 但是使用这种多继承的时候,为了防止混乱要注意分清主次(其实这种方式的多继承采用mixin的直译理解比较好,混入,补充合作完成的意思) 使用mixin模式要注意:

  • 首先它必须表示某一种功能,而不是某个物品
  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类
  • 然后,它不依赖于子类的实现
  • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。

mixin 与多继承的关系

​ mixin是多继承的一种 ​ 多继承时程序的运行顺序是怎么样的:

  • 在继承链中,从左到右按顺序查找。
class PrintableMixin:
    def print(self):
        print(f"Word print {self.content}")


class Docment:
    def __init__(self, content):
        self.content = content


class Word(PrintableMixin, Docment):
    pass


w = Word("YES")
w.print()

​ Mixin本质上是一种多继承实现。

​ Mixin体现的是一种组合的设计模式。

​ 在面向对象的设计中,一个复杂的类,往往需要很多功能来自不同的类提供,这就需要很多的类组合在一起。

​ Mixin类的使用原则:

  • Mixin类中不应该显示的出现__init__方法
  • Mixin类通常都不能独立工作,因为它是准备混入别的类中的部分功能能实现的。
  • Mixin类的祖先也应该是Mixin类。
  • 在使用Mixin类时,通常在继承位置的第一位。__mro__

实现迭代器

​ Python循环语句可以作用域任何序列类型,包括列表、元组以及字符串。

​ 实际上for循环能够作用于任何可迭代的对象,除了for语句,python中所有会从左至右的迭代工具都是如此,这些迭代工具包括:for循环、列表解析、in成员关系测试以及map内置函数等… ​ 这里就涉及到很重要的一个概念-----可迭代对象,除此之外还有一个与它很类似的概念,叫做迭代对象,很多人经常分不清楚他们。

​ 迭代对象是指实现了__iter____next__方法的对象,而可迭代对象可以只实现__iter__方法,也可以两个都实现。有的可迭代对象的迭代对象就是它本身。说了那么多,不如我们直接自己实现一下:

class MyRange(object):
    def __init__(self, n):
        self.idx = 0
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.n:
            val = self.idx
            self.idx += 1
            return val
        else:
            raise StopIteration()

myRange = MyRange(3)
for i in myRange:
    print(i) 

练习:

使用面向对象实现LinkedList链表

单向链表实现append、iternodes、迭代器

双向链表实现append、pop、insert、remove、iternode、迭代器

img

img

class SingleNode:
    """这是单向链表的节点"""

    def __init__(self, value, _next=None):
        self.value = value
        self.next = _next

# 实现单向链表
class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
        self.length = 0
        self.n = 0
        self.temp = None

    def append(self, value):
        node = SingleNode(value)
        if self.head is None:
            self.head = node
        else:
            self.tail.next = node
        self.tail = node
        self.length += 1

    def iternodes(self):
        data = self.head
        while data:
            yield data
            data = data.next

    def __iter__(self):
        return self

    def __next__(self):
        if self.n < self.length:
            if self.temp is None:
                self.temp = self.head
            else:
                self.temp = self.temp.next
            self.n += 1
            return self.temp.value
        else:
            self.temp = None
            self.n = 0
            raise StopIteration


lls = LinkedList()
lls.append(10)
lls.append(2)
lls.append(33)
lls.append(4)
for i in lls:
    print(i)

print([i for i in lls])
  • 双向链表
class Node:
    """这是一个节点"""

    def __init__(self, value, _next=None, prev=None):
        self.value = value
        self.next = _next
        self.prev = prev

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        return self.__str__()


class LinkedList:
    def __init__(self):
        self.data = []
        self.head = None
        self.tail = None
        self.length = 0
        self.n = 0
        self.temp = None

    def append(self, value):
        node = Node(value)
        self.data.append(node)
        if self.head is None:
            self.head = node
        else:
            self.tail.next = node
            node.prev = self.tail
        self.tail = node
        self.length += 1

    def pop(self):
        self.data.pop()
        tail = self.tail
        prev = tail.prev
        if prev is None:
            self.head = self.tail = None
        else:
            self.tail = prev
            prev.next = None
        self.length -= 1
        return tail.value

    def getitem(self, index):
        if isinstance(index, int):
            if index < 0:
                index = abs(index) - 1
                data = self.iternodes(reverse=True)
            else:
                data = self.iternodes()
            current = None

            for i, node in enumerate(data):
                if i == abs(index):
                    current = node
                    break
            if current:
                return current

    def iternodes(self, reverse=False):
        data = self.tail if reverse else self.head
        while data:
            yield data
            data = data.prev if reverse else data.next

    def insert(self, index, value):
        node = self.getitem(index)
        if node is None:
            self.append(value)
            return

        prev = node.prev
        new_node = Node(value, _next=node, prev=prev)
        if prev is None:
            self.head = new_node
        else:
            prev.next = new_node
            node.prev = new_node
        self.data.insert(index, new_node)
        self.length += 1

    def remove(self, item):
        if self.tail is None:
            return None
        for i, v in enumerate(self.iternodes()):
            if item == v.value:
                data = v
                break
        else:
            return None
        prev = data.prev
        _next = data.next
        if prev is None and _next is None:
            self.head = None
            self.tail = None
        elif prev is None:
            self.head = _next
            _next.prev = None
        elif _next is None:
            prev.next = _next
        else:
            prev.next = _next
            _next.prev = prev
        self.length -= 1
        del data

    def __len__(self):
        return self.length

    def __iter__(self):
        return self

    def __next__(self):
        if self.n < self.length:
            if self.temp is None:
                self.temp = self.head
            else:
                self.temp = self.temp.next
            self.n += 1
            return self.temp
        else:
            self.temp = None
            self.n = 0
            raise StopIteration

    def __getitem__(self, item):
        return self.data[item]

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


lls = LinkedList()
lls.append(10)
lls.append(2)
lls.append(33)
lls.append(4)
lls.insert(-1, 3000)

for i in enumerate(lls):
    print(i)
print("=" * 50)
# lls.remove(10)
lls.remove(33)
# print(v)
for i in enumerate(lls):
    print(i)

上下文管理操作

当一个对象同时实现__enter__()__exit__()方法,它就属于上下文管理对象了。

方法 意义
__enter__ 进入于此对象相关的上下文。返回值作为as字句绑定的对象。
__exit__ 退出此上下文时,执行的方法。

参数:

  • __enter__没有参数
  • __exit__参数
    • exc_type :退出的异常类型
    • exc_val:异常的值
    • exc_tb:异常的追踪信息

Metaclass元类

0 元类type

元类是python高阶语法. 合理的使用可以减少大量重复性的代码.

元类实际上做了以下三方面的工作:

  • 干涉创建类的过程
  • 修改类
  • 返回修改之后的类

为什么使用元类?

为什么要使用元类这种模糊且容易出错的功能? 一般情况下,我们并不会使用元类,99%的开发者并不会用到元类,所以一般不用考虑这个问题。 元类主用用于创建API,一个典型的例子就是Django的ORM(对象关系映射)。 它让我们可以这样定义一个类:

class Person(models.Model):
  name = models.CharField(max_length=30)
  age = models.IntegerField()

guy = Person(name='bob', age='35')
print(guy.age)
guy.save()

返回的结果是int类型而不是IntegerField对象。这是因为models.Model使用了元类,它会将Python中定义的字段转换成数据库中的字段。 通过使用元类,Django将复杂的接口转换成简单的接口。

原型:type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)

以下两种写法都可以:

type('Class',(object,),dict(hello=fun()))

type('Class',(object,),{"hello":fun()})

1、class 自定义的类名称

2、(object,)是继承类,的元组,如果只有一个就写这种形势(object,);多个(object,xxxx,)

3、dict(hello=fun()) 或 {"hello":fun()} 第三个参数,是一个字典等号左是 自定义的方法名,右侧是已写好的方法名,这个要注意,有参数且没有默认值的情况下,要加括号;

def fun():
    print('hello world!')


if __name__=="__main__":
    Hello = type('Hello',(object,),dict(hello=fun()))
    tc = Hello()
    tc.hello

引用:

h 相当于接收Hello类;tc = h()实例化类;tc.hello方法,调用的其实是我们定义的fun方法。

Hello = type('Hello',(object,),dict(f1=fun)
tc = Hello()
tc.f1

type()动态创建类后,还可以添加更多的方法和属性:

def add(x,y):
    return x + y

Hello.add = add(1, 2)

调用:

print(tc.add)

1 自定义元类

​ 元类是用来创建其他的类,它的实例就是其他的类。 执行类定义时,解释器会先寻找类属性__metaclass__,如果此属性中未定义,则向上查找其父类中的__metaclass__ type__init__函数有三个位置参数,分别为:类名称,父类名称元祖,类属性字典;可直接使用type()快速创建类对象: People = type('People',(object,),dict(show = fn, setage = fn1, age = None)) 也可子类化type类后,创建元类,再创建类对象:

from time import ctime

class MetaC(type):
    def __init__(self, *args):
        super().__init__(*args)
        print('调用类的init')

    def __call__(self, *args, **kwargs):
        print('调用类的call')
        _instance = super().__call__(*args, **kwargs)
        print('call return %s' %_instance)
        return _instance

class Foo(metaclass=MetaC):
    # __metaclass__ = MetaC

    def __init__(self, version=None):
        print('调用对象的init')

    def __new__(cls, *args, **kwargs):
        print('调用对象的new')
        _instance = super().__new__(cls)
        print('new return %s' %_instance)
        return _instance

foo = Foo('hello')

运行结果:

>>
调用类的init
调用类的call
调用对象的new
new return <__main__.Foo object at 0x0000018D2F57EF98>
调用对象的init
call return <__main__.Foo object at 0x0000018D2F57EF98>

2 单例模式的实现

下面是几种单例模式的实现方法,有些会用到上述的元类知识。

2.1 装饰器实现单例

import weakref
frome functools import wraps

def single_obj(cls):
    #实例缓存为弱引用,实例不被使用时会释放该实例
    _spam_cache = weakref.WeakValueDictionary()
    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in _spam_cache :
            _instance = cls(*args, **kwargs)
            _spam_cache [cls] = _instance
            return _instance
        else:
            return _spam_cache [cls]
    return wrapper

@single_obj
class A():
    def __init__(self, version):
        self.version = version
        print(self.version)

a1 = A(1.3)
a2 = A(1.2)
print('a1 id:',id(a1))
print('a2 id:',id(a2))

2.2 利用元类,类的__call__方法实现单例

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
            return self._instance
        else:
            return self._instance

class B(metaclass=Singleton):
    def __init__(self, name):
        self.name = name
        print(name)

b1 = B('what')
b2 = B('hell')
print(b2.name)
print('b1 id:',id(b1))
print('b2 id:',id(b2))

2.3 通过__new__实现单例(此方法不可行)

class C(object):
    _instance = None

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

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

c1 = C('dsfsfd')
c2 = C('java')

print('c1 id:',id(c1))
print('c2 id:',id(c2))
  • 若采用上述方法,无论类是否实例化,__init__都会被调用,实例对象会被修改;

总结

面向对象

  • 是一种高级的编程思想。
  • 把一切事物当成对象来处理。
  • 是一种“设计者”的思维。是一种分工的思想,适合大型项目的开发。

面向对象的三要素:

  • 封装
    • 把属性和方法封装到类中,把数据封装到对象中。
    • 类可以隐藏私有属性和私有方法,可以通过接口(公共的方法)操作。
  • 继承
    • 一种高级代码复用的方式。
    • 子类(衍生类)可以继承父类(基类)的所有公共的属性的和方法
    • 建议使用继承重写方法的方式代替直接修改父类。
    • 重载super()
  • 多态
    • 同一个类的子类使用相同方法实现不同效果。
    • 使用抽象类实现的。abc.ABCMeta
    • 抽象类的所有子类都要实现抽象方法。
    • 实现公共接口。

补充的要素:

  • 抽象
    • 提取特征(属性和方法)。用于创建类。
  • 组合
    • 通过多继承来实现的。

反射/自省

  • 动态的使用对象的方法。
  • 在不修改现有类的前提下,在程序运行过程中可以自由的获取、修改、删除对象的属性和方法。