面向对象编程

Date: 2019-05-18

Author: Sun

1. 面向对象的思想

面向过程:面向处理,更多的是从计算机角度思考,注重计算每一个步骤,程序更像是一本cpu操作手册。

面向对象:以日常生活的角度思考问题的解决,更接近人的思维方式,让人可以从更高的层面考虑系统的构建

以你请朋友吃饭

面向过程 面向对象
自己买菜 和朋友一块到饭店
自己摘菜 叫服务员点菜
自己洗菜 和朋友一块吃
自己做菜
和朋友一块吃

面向对象的优点:

  • 面向对象更加适合做应用的开发 --- 面向实体性编程为主,造飞机,造轮船
  • 面向对象可以使你的代码更加优雅和紧凑
  • 面向对象开发效率更高 ---- 将功能和数据进行隔离
  • 面向对象代码复用度更高、可维护性更好

​ 面向对象是一种思维方式,它认为万事万物皆对象,程序是由多个对象协作共同完成功能的,所以以后我们要从面向过程转向面向对象。以面向对象的方式考虑程序的构建。面向对象的核心是:类和对象

2. 类和对象

2.1 类和对象的概念

​ 类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象。

​ 对象是类的行为属性和数据属性进行具体化后的实例。

生活角度

  • 类:具有相同特征和行为的对象的集合,是一个概念
  • 对象:客观存在的一切事物,是类的实例

​ 类: 汽车 超级英雄 电脑 杯子

​ 对象: 红色的宝马 美国队长 桌上的mac pro 老王的黑色杯子

类就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象

程序角度

  • 类:用户自定义的数据类型,是模板,不占用内存
  • 对象:由类定义的变量,占用内存
类:
  成员属性(成员变量)  描述对象的静态特征,诸如,名字、身高体重
  成员方法  描述对象的动态特征,例如:吃饭、睡觉、打豆豆

类和对象之间的关系

总之,类就是创建对象的模板

2.2 类的构成

类(Class) 由3个部分构成

  • 类的名称:类名
  • 类的属性:一组数据
  • 类的方法:允许对进行操作的方法 (行为)

举例:

1)人类设计,只关心3样东西:

  • 事物名称(类名):人(Person)
  • 属性:身高(height)、年龄(age)
  • 方法(行为/功能):跑(run)、打架(fight)

2)狗类的设计

  • 类名:狗(Dog)
  • 属性:品种 、毛色、性别、名字、 腿儿的数量
  • 方法(行为/功能):叫 、跑、咬人、吃、摇尾巴

2.3 类的定义

#语法:
class  类名:
     类体

注意:

  • 类定义必须以关键字class
  • 类名要符合标识符的规范
  • 类名一般用大驼峰风格: 每个单词首字母大写,其它小写 ,例如MyBook YouMoney
  • 类体必须缩进
  • 在python3中类默认继承object,所以可以这样写 class Dog:,它等价于class Dog(object):
  • 一个文件里只放一个类

(1) 定义一个Car类

# 定义类
class Car:   #经典定义类,如果是Car(object)则为新式类
    # 方法
    def getCarInfo(self):
        print(f'车轮子个数:{self.wheelNum}, 颜色:{self.color}')

    def move(self):
        print("车正在移动...")

说明:

  • 定义类时有2种:新式类和经典类,上面的Car为经典类,如果是Car(object)则为新式类
  • 类名 的命名规则按照"大驼峰"

(2) 成员方法

成员方法其实就是函数,作用域在类内,成员方法的第一个参数必须是self,self代表当前对象,也就是调用这个方法的对象,这个参数有系统传递。

class Dog(object):
    def bark(self):  #成员方法,第一个参数必须是self,代表当前调用对象
        print('我是小可爱--丁丁')

dingding = Dog()  #实例化一个对象

#调用方法,不需要传参数,self是系统传递的
#调用形式: 对象.方法([实参])
dingding.bark()   #等价调用形式:bark(dingding)

注意:

  • self参数在调用的时候不必传值,由系统传值
  • self只能在实例方法中使用
  • 方法和函数的区别:
    • 方法作用域属于类,所以即便和普通函数重名,也不会被覆盖
    • 方法的第一个参数必须是self,但函数不要求
    • 方法必须通过对象调用,而函数不需要
  • 方法的第一个参数self其实可以使任何合法标识符,不过一般约定俗成都是self

2.4 创建对象

​ 上节2.3中定义了一个Car类;就好比有车一个张图纸,那么接下来就应该把图纸交给生成工人们去生成了。

python中,可以根据已经定义的类去创建出一个个对象。

创建对象的格式为:

对象名 = 类名()

创建对象:

# 定义类
class Car:
    # 移动
    def move(self):
        print('车在奔跑...')

    # 鸣笛
    def toot(self):
        print("车在鸣笛...嘟嘟..")

# 创建一个对象,并用变量BMW来保存它的引用
BMW = Car()
BMW.color = '黑色'
BMW.wheelNum = 4 #轮子数量
BMW.move()
BMW.toot()
print(BMW.color)
print(BMW.wheelNum)

说明:

  • BMW = Car(),这样就产生了一个Car的实例对象,此时也可以通过实例对象BMW来访问属性或者方法
  • 第一次使用BMW.color = '黑色'表示给BMW这个对象添加属性,如果后面再次出现BMW.color = xxx表示对属性进行修改
  • BMW是一个对象,它拥有属性(数据)和方法(函数)

另外说明:

#语法:  对象  = 类名([实参])
dingding = Dog()  #实例化一个对象
print(dingding)   #<__main__.Dog object at 0x00000000023F40B8>
print(type(dingding)) #<class '__main__.Dog'>

#查看对象的类名
print(dingding.__class__)

2.5 成员属性

成员属性描述的是对象的静态特征,比如说狗名字、品种等,其实就是一个变量,作用域属于类,不会和类外的全局变量冲突。python中成员属性可以动态添加,也可以在构造函数中添加。成员属性属于对象,每个对象的成员属性都不同

  • 给对象动态添加的属性只属于这个对象,其它对象没有该属性
  • 使用__ slots__限制属性的动态绑定:
  • 在构造函数中添加的属性属于所有对象(重点)

理解self

  • 所谓的self,可以理解为自己
  • 可以把self当做C++中类里面的this指针一样理解,就是对象自身的意思
  • 某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可

python中__slots__作用?

​ 当一个类需要创建大量实例时,可以通过__slots__声明实例所需要的属性,如果超过了此属性范围进行对象属性赋值,就会限制,起到保护数据作用。

例如,

class Foo(object):

__slots__ = ['foo']

这样做带来以下优点:

  1. 更快的属性访问速度
  2. 通过取消__dict__的使用减少内存消耗
  3. 保护数据安全性

​ Python内置的字典本质是一个哈希表,它是一种用空间换时间的数据结构。为了解决冲突的问题,当字典使用量超过2/3时,Python会根据情况进行2-4倍的扩容。由此可预见,取消__dict__的使用可以大幅减少实例的空间消耗。

#添加属性语法:
   对象.成员属性  =  值
#引用方式:对象.成员属性

class Dog(object):

    __slots__ = ('gender','name','age','kind')  #这个类的对象这能有这些属性,同时也限制了采用__dict__属性可以用来查看实例属性
    
    def __init__(self,name,kind,age):
        self.name = name
        self.kind = kind
        self.age = age
        
    def bark(tmp):
        print('我是小可爱--丁丁')

dingding = Dog('丁丁','泰迪',3)
print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age))
dingding.gender = '公'  #动态添加的属性
#dingding.weight = 8  #不能添加这个属性,语法错误

#查看实例属性
print(dingding.__dict__)  #__dict__属性可以用来查看实例属性
print(dir(dingding))  #查看Dog的属性,包括实例属性

说明:

​ python中类如何来保护成员属性和成员方法呢?

​ 采用self.__xxx方式来定义类的成员。(两个下划线, 见 保护对象的属性)

2.6 构造和析构

(1) 构造方法

  • 目的:构造方法用于初始化对象,可以在构造方法中添加成员属性

  • 时机:实例化对象的时候自动调用

  • 参数:第一个参数必须是self,其它参数根据需要自己定义

  • 返回值:不返回值,或者说返回None,不应该返回任何其他值

    语法:
    	def __init__(self,arg1,arg2....):
    		函数体
    #参数:arg1,agr2...根据需要自己定义
    #如果自己不定义构造方法,系统自动生成一个构造函数
    def __init__(self):
      pass
    
    

注意:

  • 如果没有定义构造方法,系统会生成一个无参构造方法,如果自己定义了构造方法,则系统不会自动生成

    class 类名:
       def __init__(self):
       	    pass
    
    
  • 一个类只能有一个构造方法,如果定义多个,后面的会覆盖前面的

  • 构造函数由系统在实例化对象时自动调用,不要自己调用

    class Dog(object):
        def __init__(self,name,kind,age):
            self.name = name  #定义对象属性,这个类所有的对象都具有该属性
            self.kind = kind  #成员属性必须通过self.引用,否则是普通变量
            self.age = age
    
        def bark(tmp):
            print('我是小可爱--丁丁')
    
    dingding = Dog('丁丁','泰迪',3)
    print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age))
    
    

(2) 析构方法

  • 目的:对象销毁时,释放资源

  • 时机:对象销毁时由系统自动调用

  • 参数:除了self外,没有其他参数

  • 返回值:不返回值,或者说返回None。

  • 语法:

    def __del__(self):
        #to do
    
    

    案例

    class Dog(object):
    	#构造
        def __init__(self,name,kind,age):
            self.name = name
            self.kind = kind
            self.age = age
            
        #析构
        def __del__(self):
            print('拜拜了,二十年后又是一条好汉')
            
        def bark(tmp):
          print('我是小可爱--丁丁')
    
    dingding = Dog('丁丁','泰迪',3)
    print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age))
    del dingding  #销毁对象,自动调用析构方法
    
    

​ 当删除一个对象时,python解释器也会默认调用一个方法,这个方法为__del__()方法

3. 保护对象的属性

如果有一个对象,当需要对其进行修改属性时,有2种方法

  • 对象名.属性名 = 数据 ---->直接修改
  • 对象名.方法名() ---->间接修改

为了更好的保存属性安全,即不能随意修改,一般的处理方式为

  • 将属性定义为私有属性
  • 添加一个可以调用的方法,供调用
class People(object):

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

    def getName(self):
        return self.__name

    def setName(self, newName):
        if len(newName) >= 5:
            self.__name = newName
        else:
            print("error:名字长度需要大于或者等于5")

xiaoming = People("dongGe")
print(xiaoming.__name)

此时就会出现错误。

class People(object):
    name = 'Tom'  #公有的类属性
    __age = 12     #私有的类属性

p = People()

print(p.name)           #正确
print(People.name)      #正确
print(p.__age)            #错误,不能在类外通过实例对象访问私有的类属性
print(People.__age)        #错误,不能在类外通过类对象访问私有的类属性

4. @property装饰器

Python中有一个被称为属性函数(property)的小概念,它可以做一些有用的事情。

  • 将类方法转换为只读属性
  • 重新实现一个类的属性的setter和getter方法

​ Python内置的@property装饰器就是负责把一个方法变成属性调用的,特别在对属性数据进行校验的时候。

# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/5/28 16:23'

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

    @score.deleter
    def score(self):
        if not hasattr(self, '_score'):
            raise AttributeError('Student object has no attribute "_score"')
        del self._score

s = Student()
s.score =99
print(s.score)

del s.score

​ @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样程序运行时就减少了出错的可能性。

5. 魔法方法

(1)id() 方法

​ 获取对象在内存中的地址

创建对象,并打印对象地址

dingding2 = Dog('丁丁', '泰迪', 3)
print(id(dingding2))

(2) __str__()方法

​ 自定义打印对象内容,以字符串形式打印出来

案例:

class Car:

    def __init__(self, newWheelNum, newColor):
        self.wheelNum = newWheelNum
        self.color = newColor

    def __str__(self):
        msg = "嘿。。。我的颜色是" + self.color + "我有" + int(self.wheelNum) + "个轮胎..."
        return msg

    def move(self):
        print('车在跑,目标:夏威夷')


BMW = Car(4, "白色")
print(BMW)

6. 静态方法和类方法

6.1 类方法

​ 是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。

class People(object):
    country = 'china'

    #类方法,用classmethod来进行修饰
    @classmethod
    def getCountry(cls):
        return cls.country

p = People()
print p.getCountry()    #可以用过实例对象引用
print People.getCountry()    #可以通过类对象引用

6.1 静态方法

需要通过修饰器@staticmethod来进行修饰,静态方法不需要多定义参数

class People(object):
    country = 'china'

    @staticmethod
    #静态方法
    def getCountry():
        return People.country


print People.getCountry()

综上:

​ 从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用

7. 类的继承

在现实生活中,继承一般指的是子女继承父辈的财产

在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物

# 定义一个父类,如下:
class Cat(object):

    def __init__(self, name, color="白色"):
        self.name = name
        self.color = color

    def run(self):
        print("%s--在跑"%self.name)

# 定义一个子类,继承Cat类如下:
class Bosi(Cat):

    def setNewName(self, newName):
        self.name = newName

    def eat(self):
        print("%s--在吃"%self.name)


bs = Bosi("印度猫")
print('bs的名字为:%s'%bs.name)
print('bs的颜色为:%s'%bs.color)
bs.eat()
bs.setNewName('波斯')
bs.run()

说明:

  • 私有的属性,不能通过对象直接访问,但是可以通过方法访问
  • 私有的方法,不能通过对象直接访问
  • 私有的属性、方法,不会被子类继承,也不能被访问
  • 一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用

多重继承

class base(object):
    def test(self):
        print('----base test----')
class A(base):
    def test(self):
        print('----A test----')

# 定义一个父类
class B(base):
    def test(self):
        print('----B test----')

# 定义一个子类,继承自A、B
class C(A,B):
    pass


obj_C = C()
obj_C.test()

print(C.__mro__) #可以查看C类的对象搜索方法时的先后顺序

重写父类方法

​ 所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法

class Cat(object):
    def sayHello(self):
        print("halou-----1")


class Bosi(Cat):

    def sayHello(self):
        print("halou-----2")

bosi = Bosi()

bosi.sayHello()

调用父类的方法

class Cat(object):
    def __init__(self,name):
        self.__id = id   #私有的,不被继承到子类
        self.name = name
        self.color = 'yellow'

class Bosi(Cat):

    def __init__(self,name):
        # 调用父类的__init__方法1(python2)
        #Cat.__init__(self,name)
        # 调用父类的__init__方法2
        #super(Bosi,self).__init__(name)
        # 调用父类的__init__方法3
        super().__init__(name)

    def getName(self):
        return self.name

bosi = Bosi('xiaohua')

print(bosi.name)
print(bosi.color)

8 多态

多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。

所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态

Python “鸭子类型”

class F1(object):
    def show(self):
        print 'F1.show'

class S1(F1):

    def show(self):
        print 'S1.show'

class S2(F1):

    def show(self):
        print 'S2.show'

def Func(obj):
    print obj.show()

s1_obj = S1()
Func(s1_obj) 

s2_obj = S2()
Func(s2_obj)

说明

python3的面向对象编程其实还有一些高级应用,大部分工作中不会用到

敬请期待后续课程

原文地址:https://www.cnblogs.com/sunBinary/p/10940936.html