Python之面向对象编程

面向对象编程

对象

对象是指现实中的物体或实体

面向对象

把一切看成对象(实例),让对象和对象之建立关联关系

对象的特征

  • 属性

    例如:人的姓名, 年龄, 性别等

  • 行为

    例如:人学习,吃饭,睡觉,踢球, 工作等

  • 拥有相同属性和行为的对象分为一组,即为一个类
  • 类是用来描述对象的工具,用类可以创建同类对象

类的创建

  • 创建一个类
  • 用于描述此类对象的行为和属性
  • 类用于创建此类的一个或多个对象(实例)
class 类名(继承列表):
  '''类的文档字符串'''
  实例方法定义(类内的函数称为方法method)
  类变量定义
  类方法定义
  静态方法定义

构造函数

创建这个类的实例对象,并返回此实例对象的引用关系

类名([创建传参列表])

实例(对象)说明

  • 实例有自己的作用域和名字空间,可以为该实例添加实例变量(属性)
  • 实例可以调用类方法和实例方法
  • 实例可以访问类变量和实例变量

实例方法

  • 用于描述一个对象的行为,让此类型的全部对象都拥有相同的行为
  • 实例方法实质是函数,是定义在类内的函数
  • 实例方法至少有一个形参,第一个形参代表调用这个方法的实例,一般命名为self
# 实例方法的定义
class 类名(继承列表):
  def 实例方法名(self, 参数1, 参数2, ...):
    '''实例方法的文档字符串'''
    语句块

# 实例方法的调用
实例.实例方法名(调用传参)
或
类名.实例方法名(实例, 调用传参)

实例方法示例

class Dog:
  def play(self,ball):
    print('小狗正在玩',ball)

  def eat(self,food):
    print('小狗正在吃',food)

dog1 = Dog()
dog1.play('baseball')  # 小狗正在玩 baseball
dog1.eat('sandwich')   # 小狗正在吃 sandwich

dog2 = Dog()
dog2.play('soccer')  # 小狗正在玩 soccer
dog2.eat('noddles')  # 小狗正在吃 noddles

dog3 = Dog()
Dog.play(dog3,'basketball')  # 小狗正在玩 basketball
Dog.eat(dog3,'salad')   # 小狗正在吃 salad

实例属性

实例.属性名
  • 每个实例都可以有自己的变量,此变量称为实例变量(也叫属性)
  • 用来记录对象自身的数据
  • 实例方法和实例变量(属性)可以结合在一起用
  • 首次为属性赋值则创建此属性
  • 再次为属性赋值则改变属性的绑定关系
# 此示例示意为对象添加属性
class Dog:
    pass

dog1 = Dog()
dog1.kinds = '京巴'  # 添加属性kinds
dog1.color = '白色'  # 添加属性color
dog1.color = '黃色'  # 改变属性的绑定关系

print(dog1.color, '的', dog1.kinds)  # 访问属性

dog2 = Dog()
dog2.kinds = '牧羊犬'
dog2.color = '灰色'
print(dog2.color, '的', dog2.kinds)  # 访问属性

删除属性

del 对象.实例变量名

del语句可以删除一个对象的实例变量

class Cat:
    pass

c1 = Cat()  # 创建变量
c1.color = "白色"  # 添加属性
print(c1.color)  # 白色
del c1.color  # 删除属性
print(c1.color)  # 属性错误

初始化方法

对新创建的对象添加实例变量(属性)或相应的资源

class 类名(继承列表):
    def __init__(self [,形参列表]):
        语句块
  • 初始化方法名必须为 __init__ 不可改变
  • 初始化方法会在构造函数创建实例后自动调用,且将实例自身通过第一个参数 self 传入 __init__ 方法
  • 构造函数的实参将通过 __init__方法的形参列表传入 __init__ 方法中
  • 初始化方法内部如果需要返回则只能返回None
class Student:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def show_info(self):
        print(self.name,'今年',self.age,'岁')

s1 = Student('小张',20)
s2 = Student('小李',22)

s3 = Student('wx1',23)
s3.__init__('wx',21)   #  显式调用

s1.show_info()  # 小张 今年 20 岁
s2.show_info()  # 小李 今年 22 岁
s3.show_info()  # wx 今年 21 岁

析构方法

class 类名(继承列表):
   def __del__(self):
       语句块
  • 析构方法在对象销毁时被自动调用
  • 清理此对象所占用的资源
  • Python不建议在析构方法内做任何事情,因为对象销毁的时间难以确定

预置实例属性

  • __dict__ 此属性绑定一个存储此实例自身实例变量(属性)的字典
  • __class__ 此属性用来绑定创建此实例的类

用于类的函数

  • isinstance(obj, class_or_tuple) 返回这个对象obj 是否某个类class或某些类的实例,如果是则返回True, 否则返回False
  • type(obj) 返回对象的类型
class Dog:
    def __init__(self,kind):
        self.kind = kind

class Cat:
    pass

dog1 = Dog('京巴')
print(dog1.__dict__)   # {'kind': '京巴'}

dog1.age = 2
print(dog1.__dict__)   # {'kind': '京巴', 'age': 2}

print(dog1.__class__)  # <class '__main__.Dog'>

print(isinstance(dog1, Dog))  # True
print(isinstance(dog1, Cat))  # False
print(isinstance(dog1, (Cat,int,list)))  # False
print(isinstance(dog1, (Cat, int, Dog))) # True

print(type(dog1))  #  <class '__main__.Dog'>
  • issubclass(cls, class_or_tuple) 判断一个类是否继承自其它的类,如果此类clsclasstuple 中的一个派生子类则返回True,否则返回False
class A:
      pass
class B(A):
      pass
class C(B):
      pass

issubclass(C, (A, B))  # True
issubclass(C, (int, str)) # False

类变量

  • 类变量是类的属性,此属性属于类
  • 用来记录类相关的数据
  • 类变量可以通过类直接访问
  • 类变量可以通过类的实例直接访问
  • 类变量可以通过此类的实例的 __class__ 属性间接访问
  • 实例变量可以通过 __class__ 属性间接修改类变量的值
  • 可以用类变量来记录对象的个数
class Human:
    count = 100

h1 = Human()

print('通过类直接访问:',Human.count)  
# 通过类直接访问: 100

print('通过类的实例直接访问:',h1.count) 
# 通过类的实例直接访问: 100

print('通过此类的实例的 __class__ 属性间接访问:',h1.__class__.count)  
# 通过此类的实例的 __class__ 属性间接访问: 100

h1.count = 200  # 给h1对象添加属性

print('通过类直接访问:',Human.count) 
# 通过类直接访问: 100

print('通过类的实例直接访问:',h1.count) 
# 通过类的实例直接访问: 200

h1.__class__.count += 1
print('通过类直接访问:',Human.count) 
# 通过类直接访问: 101

h1.count += 1
print('通过类直接访问:',Human.count) 
# 通过类直接访问: 100
print('通过类的实例直接访问:',h1.count) 
# 通过类的实例直接访问: 201

类的文档字符串

  • 类内第一个没有赋值给任何变量的字符串是类的文档字符串
  • 类的文档字符串用类的 __doc__ 属性可以访问
  • 类的文档字符串可以用 help() 函数查看

类的 __slots__ 列表

  • 限定一个类的实例只能有固定的属性(实例变量)
  • 通常为防止错写属性名而发生运行时错误
  • 含有 __slots__ 列表的类创建的实例对象没有 __dict__ 属性,即此实例不用字典来保存对象的属性(实例变量)

类方法

  • 类方法是描述类的行为的方法,类方法属于类
  • 类方法需要用 @classmethod 装饰器定义
  • 类方法至少有一个形参,第一个形参用于绑定类,约定写为 cls
  • 类和该类的实例都可以调用类方法
  • 类方法不能访问此类创建的实例的属性(只能访问类变量)
class Car:
    count = 0 # 类变量

    @classmethod   # 类方法
    def getTotalCount(cls):
        return cls.count

    @classmethod
    def updateCount(cls,value):
        cls.count += value

print(Car.getTotalCount())  # 调用类方法
Car.updateCount(1)
print(Car.getTotalCount())  # 调用类方法

c1 = Car()
c1.updateCount(100)   # 类的实例都调用类方法
print(c1.getTotalCount())
print(Car.getTotalCount())  # 调用类方法

静态方法

  • 静态方法不属于类,也不属于类的实例,它相当于定义在类内普通函数,只是它的作用域属于类
  • 静态方法需要用 @staticmethod 来定义
  • 静态方法可以通过类和类的实例来调用
class A:
    @staticmethod
    def myadd(x,y):
        return  x + y

print(A.myadd(1,2))   # 3
a = A()
print(a.myadd(3,4))   # 7

面向对象高级特性

继承与派生

  • 继承是指从已有的类中衍生出新类,新类具有原类的行为,并能扩展新的行为
  • 派生就是从一个已有类中衍生(创建)新类,在新类上可以添加新的属性和行为

继承和派生的目的

  • 继承是延续旧类的功能
  • 派生是为了在旧类的基础上添加新的功能

继承和派生的作用

  • 用继承派生机制,可以将一些共有功能加在基类中,实现代码的共享
  • 在不改变基类的基础上改变原有功能

继承/派生的名词

  • 基类(base class) / 超类(super class) / 父类(father class)
  • 派生类(derived class) / 子类(child class)

单继承

单继承是指派生类由一个基类衍生出来的类

class 类名(基类名):
      语句块
  • 任何类都直接或间接的继承自object
  • object类是一切类的超类(祖类)
  • __base__ 属性用来记录此类的基类
class Human:  # 相当于 class Human(object): 
    def say(self,that):
        print('说',that)

    def walk(self,km):
        print('走了',km,'km')

class Student(Human):
    def study(self,subject):
        print('学习',subject)

h1 = Human()
h1.say('今天天气真热')
h1.walk(5)

s1 = Student()
s1.say('学习很累')   # 调用父类的方法
s1.walk(3)          # 调用父类的方法
s1.study('数学')

print(Student.__bases__)  # (<class '__main__.Human'>,)
print(Human.__bases__)    # (<class 'object'>,)

覆盖

覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类实例调用该方法时,实例调用的是子类中的覆盖版本的方法,这种现象叫做覆盖

  • 子类对象显式调用基类方法:基类名.方法名(实例, 实际调用传参)
class Human:
    def eat(self):
        print('Human类中的eat()方法')

class Student(Human):
    def eat(self):
        print('Student类中的eat()方法')

h1 = Human()
h1.eat()    # Human类中的eat()方法

s1 = Student()
s1.eat()    # Student类中的eat()方法

# 子类对象显式调用基类方法
Human.eat(s1)  # Human类中的eat()方法

# 使用 super() 方法调用父类方法
super(Student,s1).eat()

super 函数

  • super(type, obj) 返回绑定超类的实例
  • super() 返回绑定超类的实例,必须在方法内调用
  • 显式调用基类的初始化方法
    当子类中实现了 __init__ 方法时,基类的 __init__ 方法并不会被自动调用,此时需要显式调用
class Human:
    def __init__(self, n, a):
        '''此方法为人的对象添加,姓名和年龄属性'''
        self.name = n
        self.age = a
    def infos(self):
        print("姓名:", self.name)
        print("年龄:", self.age)

class Student(Human):
    def __init__(self, n, a, s=0):
        super().__init__(n, a)  # 显式调用基类的初始化方法
        self.score = s

    def infos(self):
        super().infos()
        print("成绩:", self.score)


s1 = Student('小张', 18, 100)
s1.infos()

h1 = Human('小赵', 20)
h1.infos()

封装

  • 封装是指隐藏类的实现细节,让使用者不用关心这些细节
  • 封装的目的是让使用者尽可能少的实例变量(属性)进行操作

私有属性

类中以双下划线 __ 开头,不以双下划线结尾的标识符为私有成员,在类的外部无法直接访问

class A:
    def __init__(self):
        self.__p1 = 100

    def test(self):
        print(self.__p1)
        self.__m1()

    def __m1(self):
        print('m1')

a = A()
# print(a.__p1)  # 访问失败
a.test()
# a.__m1()       # 访问失败

多态

  • 多态是指在继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖版本方法的现象叫多态
  • 多态调用的方法与对象相关,不与类型相关
  • Python的全部对象都只有"运行时状态(动态)", 没有"C++/Java"里的"编译时状态(静态)"
class Shape:
    def draw(self):
        print('Shape.draw被调用')

class Point(Shape):
    def draw(self):
        print('正在画一个点')

class Circle(Point):
    def draw(self):
        print('正在画一个圆')

def my_draw(s):
    s.draw()   

s1 = Circle()
s2 = Point()
my_draw(s1)   # 正在画一个圆
my_draw(s2)   # 正在画一个点

多继承

class 类名(基类名1, 基类名2, ....):
        语句块
  • 多继承是指一个子类继承自两个或两个以上的基类
  • 一个子类同时继承自多个父类,父类中的方法可以同时被继承下来
class Car:
    def run(self, speed):
        print("汽车以", speed, '公里/小时的速度行驶')

class Plane:
    def fly(self, height):
        print("飞机以海拔", height, '的高度飞行')

class PlaneCar(Car, Plane):
    '''PlaneCar同时继承自汽车类和飞机类'''

p1 = PlaneCar()
p1.fly(10000)  # 飞机以海拔 10000 的高度飞行
p1.run(300)    # 汽车以 300 公里/小时的速度行驶
  • 如果两个父类中有同名的方法,而在子类中又没有覆盖此方法时,调用结果难以确定,所以要谨慎使用多继承,一般不推荐使用多继承
class A:
    def m(self):
        print("A.m()被调用")

class B:
    def m(self):
        print('B.m()被调用')

class AB(A, B):
    pass

ab = AB()
ab.m()  # 不会报错,但结果难以确定

继承的MRO(Method Resolution Order)问题

  • 类内的__mro__ 属性用来记录继承方法的查找顺序
  • super() 查找的规则是按照 __mro__ 的顺序查找
class A:
    def m(self):
        print("A.m")
class B(A):
    def m(self):
        print("B.m")
class C(A):
    def m(self):
        print("C.m")
class D(B, C):
    '''d类继承自B,C'''
    def m(self):
        print("D.m")

d = D()
d.m()   # D.m
for x in D.__mro__:
    print(x)
# <class '__main__.D'>
# <class '__main__.B'>
# <class '__main__.C'>
# <class '__main__.A'>
# <class 'object'>

函数重写

重写是在自定义的类内添加相应的方法,让自定义的类生成的对象(实例)像内建对象一样进行内建的函数操作

对象转字符串函数重写

  • repr(obj) 返回一个能代表此对象的表达式字符串,通常:eval(repr(obj)) == obj
  • str(obj) 通过给定的对象返回一个字符串(这个字符串通常是给人看的)
repr() 函数的重写方法:
      def __repr__(self):
           return 能够表达self内容的字符串

str() 函数的重写方法:
      def __str__(self):
           return 人能看懂的字符串
  • str(obj) 函数优先调用obj.__str__()方法返回字符串
  • 如果obj没有__str__()方法,则调用obj.__repr__()方法返回的字符串
  • 如果obj没有__repr__()方法,则调用object类的__repr__() 实例方法显示 <xxxx> 格式的字答鼓足
# 此示例示意一个自定义的数字类型重写 repr和 str的方法
class MyNumber:
    def __init__(self, value):
        self.data = value
    def __str__(self):
        print("__str__被调用")
        return "数字: %d" % self.data
    def __repr__(self):
        print("__repr__被调用")
        return 'MyNumber(%d)' % self.data

n1 = MyNumber(100)
# print(str(n1))  # 调用 n1.__str__(self)
print(repr(n1))
print(n1)

数值转换函数的重写

  • def __complex__(self) 函数调用complex(obj)
  • def __int__(self) 函数调用int(obj)
  • def __float__(self) 函数调用float(obj)
  • def __bool__(self) 函数调用bool(obj)
'''此示例示意自定义的类MyNumber能够转成为数值类型'''
class MyNumber:
    def __init__(self, v):
        self.data = v
    def __repr__(self):
        return "MyNumber(%d)" % self.data
    def __int__(self):
        '''此方法用于int(obj) 函数重载,必须返回整数
        此方法通常用于制订自义定对象如何转为整数的规则
        '''
        return 100

n1 = MyNumber(200)
print(type(n1))
n = int(n1)
print(type(n))
print(n)

内建函数的重写

  • __abs__ 调用abs(obj)
  • __len__ 调用len(obj)
  • __reversed__ 调用reversed(obj)
  • __round__ 调用 round(obj)
# 自定义一个MyList类,与系统内建的类一样,
# 用来保存有先后顺序关系的数据

class MyList:
    '''自定义列表类'''
    def __init__(self, iterator=[]):
        self.data = [x for x in iterator]

    def __repr__(self):
        return "MyList(%r)" % self.data

    def __abs__(self):
        # return MyList([abs(x) for x in self.data])
        # 上一句语句可以用如下生成表达式代替已防止过多占内存
        return MyList((abs(x) for x in self.data))

    def __len__(self):
        # return self.data.__len__()
        return len(self.data)

myl = MyList([1, -2, 3, -4])
print(myl)
print(abs(myl))  # MyList([1, +2, 3, +4])
print("原来的列表是:", myl)

myl2 = MyList(range(10))
print(myl2)
print('myl2的长度是:', len(myl2))
print('myl的长度是: ', len(myl))

布尔函数的重写

def __bool__(self):
     # 代码
  • 用于bool(obj) 函数取值
  • 用于if语句真值表达式中
  • 用于while语句真值表达式中
  • 优先调用 __bool__ 方法取值
  • 如果不存在 __bool__ 方激动 ,则用 __len__() 方法取值后判断是否为零值,如果不为零返回True,否则返回False
  • 如果再没有__len__方法,则直接返回True
class MyList:
    '''自定义列表类'''
    def __init__(self, iterator=[]):
        self.data = [x for x in iterator]

    def __repr__(self):
        return "MyList(%r)" % self.data

    def __bool__(self):
        print("__bool__方法被调用!")
        return False

    # def __len__(self):
    #     print("__len__被调用")
    #     return len(self.data)

myl = MyList([1, -2, 3, -4])
print(bool(myl))  # True
if myl:
    print("myl 是真值")
else:
    print("myl 是假值")

迭代器

可以通过 next() 函数取值的对象就是迭代器

迭代器协议

  • 迭代器协议是指对象能够使用next函数获取下一项数据,在没有下一项数据时触发一个StopIterator来终止迭代的约定
  • 类内需要有 __next__(self) 方法来实现迭代器协议
class MyIterator:
    def __next__(self):
      迭代器协议的实现
      return 数据

可迭代对象

  • 指能用 iter(obj) 函数返回迭代器的对象(实例)
  • 可迭代对象内部一定要定义 __iter__(self) 方法来返回迭代器
class MyIterable:
    def __iter__(self):
      语句块
      return 迭代器

可迭代对象和迭代器的定义及使用方式

# 此示例示意可迭代对象和迭代器的定义及使用方式

class MyList:
    def __init__(self, iterator):
        '''自定义列表类的初始化方法,此方法创建一个data实例
        变量来绑定一个用来存储数据的列表'''
        self.data = list(iterator)

    def __repr__(self):
        '''此方法了为打印此列表的数据'''
        return 'MyList(%r)' % self.data

    def __iter__(self):
        '''有此方法就是可迭代对象,但要求必须返回迭代器'''
        print("__iter__方法被调用!")
        return MyListIterator(self.data)

class MyListIterator:
    '''此类用来创建一个迭代器对象,用此迭代器对象可以迭代访问
    MyList类型的数据'''
    def __init__(self, iter_data):
        self.cur = 0  # 设置迭代器的初始值为0代表列表下标
        # it_data 绑定要迭代的列表
        self.it_data = iter_data

    def __next__(self):
        '''有此方法的对象才叫迭代器, 
        此方法一定要实现迭代器协议'''
        print("__next__方法被调用!")
        # 如果self.cur已经超出了列表的索引范围就报迭代结束
        if self.cur >= len(self.it_data):
            raise StopIteration
        # 否则尚未迭代完成,需要返回数据
        r = self.it_data[self.cur]  # 拿到要送回去的数
        self.cur += 1  # 将当前值向后移动一个单位
        return r

myl = MyList([2, 3, 5, 7])
print(myl)

for x in myl:
    print(x)  # 此处可以这样做吗?

# it = iter(myl)
# x = next(it)
# print(x)
# x = next(it)
# print(x)
# x = next(it)
# print(x)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容