深入理解Python中的面向对象

前言

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。对象是特征和技能的结合,其中特征和技能分别对应对象的数据属性和方法属性。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

1、类和对象的概念

1>把一类事物的静态属性和动态可以执行的操作组合在一起所得到的这个概念就是类
2>类的一个个体就是对象,对象是具体的,实实在在的事物
3>对象是特征与技能的结合体,其中特征和技能分别对应对象的数据属性和方法属性
4>对象(实例)本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
在类内部定义的属性属于类本身的,由操作系统只分配一块内存空间,大家公用这一块内存空间
5>创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性:而类中有两种属性:数据属性和函数属性,其中类的数据属性是共享给所有对象的,而类的函数属性是绑定到所有对象的。
6>创建一个对象(实例)就会创建一个对象(实例)的名称空间,存放对象(实例)的名字,称为对象(实例)的属性
7>在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常。
8>类的数据属性是大家共有的,而且大家的内部地址是一样的,用的就是一个类的函数属性是绑定到大家身上的,内部地址不一样,绑定方法指的是绑定到对象身上。
绑定方法:绑定到谁的身上,就是给谁用的,谁来调用就会自动把自己当做第一个参数传入。
定义在类内部的变量,是所有对象共有的,id全一样,定义在类内部的函数,是绑定到所有对象的,是给对象来用的,obj.fun()会把obj本身当做一个参数来传递。
9.在类内部定义的函数虽然可以由类来调用,但是并不是为了给类用的,在类内部定义的函数的目的就是为了绑定到对象身上的。
10.在类的内部来说,__init__是类的函数属性,但是对于对象来说,就是绑定方法。
11.命名空间的问题:先从对象的命名空间找,随后在从类的命名空间找,随后在从父类的命名空间找。
示例程序1:编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象。

class Student():
    #在类内部定义的属性属于类本身的,由操作系统只分配一块内存空间,大家公用这一块内存空间。
    count = 0
    def __init__(self,name,age):
        self.name = name
        self.age = age
        Student.count +=1


if __name__ == '__main__':
    student1 = Student("lidong",25)
    print(student1.__dict__)
    student2 = Student("wangwu",28)
    print(student2.__dict__)
    print(Student.count)

运行结果为:

{'age': 25, 'name': 'lidong'}
{'age': 28, 'name': 'wangwu'}
2

Process finished with exit code 0

示例程序2:(对象之间的交互,重点)

#对象之间的交互问题(面向对象之间互相交互)
class Garen:
    camp = "Demacia"
    #定义一个对象的时候,指定了这个对象的生命值和杀伤力
    def __init__(self,nickname,life_value=200,aggre_value=100):
        self.nickname = nickname
        self.life_value = life_value
        self.aggre_value = aggre_value
    def attack(self,enemy):
        enemy.life_value = enemy.life_value - self.aggre_value

class Riven:
    camp = "Demacia"
    # 定义一个对象的时候,指定了这个对象的生命值和杀伤力
    def __init__(self, nickname, life_value=100, aggre_value=200):
        self.nickname = nickname
        self.life_value = life_value
        self.aggre_value = aggre_value
    def attack(self, enemy):
        #python为弱类型语言
        enemy.life_value = enemy.life_value - self.aggre_value

g = Garen("盖伦")
r = Riven("瑞文")
print("盖伦的生命值是%s"%g.life_value)
print("瑞文的生命值是%s"%r.life_value)

g.attack(r)
print("瑞文的生命值是%s"%r.life_value)

运行结果:

盖伦的生命值是200
瑞文的生命值是100
瑞文的生命值是0

Process finished with exit code 0

初始化构造函数__init_的作用:
所谓初始化构造函数就是在构造对象的同时被对象自动调用,完成对事物的初始化,一个类只要生成一个类对象,它一定会调用初始化构造函数.
特点:
1>一个类中只能有一个初始化构造函数
2>不能有返回值
3>可以用它来为每个实例定制自己的特征

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


if __name__ == '__main__':
    #在构造对象的时候会自动调用初始化构造函数
    student = Student("Alex",100)

运行结果:

Alex 100

Process finished with exit code 0

2、self关键字的用法

为了辨别此时此刻正在处理哪个对象,self指针变量指向当前时刻正在处理的对象,即构造出来的对象。
在构造方法中self代表的是:self指针变量指向当前时刻正在创建的对象
构造函数中self.name = name 的含义:将局部变量name的数值发送给当前时刻正在创建的对象中的name成员

3、继承的概念

1>一个类从已有的类那里获得其已有的属性与方法,这种现象叫做类的继承
2>方法重写指在子类中重新定义父类中已有的方法,这中现象叫做方法的重写
3>若A类继承了B类,则aa对象既是A,又是B,继承反映的是一种谁是谁的关系,只有在谁是谁的情况下,才能用继承解决代码冗余的问题。
4>寻找属性和方法的顺序问题:先从对象自己的命名空间中找,然后在自己的类中,最后在从父类当中去找
5>在python3当中,所有的类都是新式,所有的类都直接或者间接的继承了Object
6>在python中,新建的类可以继承一个或多个父类
示例代码1:继承和方法重写

class People:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def tell(self):
        print("%s-%s-%s"%(self.name,self.age,self.sex))

class Student(People):
    def __init__(self,name,age,sex,salary):
        # self.name = name
        # self.age = age
        # self.sex = sex
        People.__init__(self,name,age,sex)
        self.salary = salary

    def tell(self):
        print("%s是最棒的!"%self.name)

if __name__ == '__main__':
    student = Student("alex",20,"man",2000)
    student.tell()

运行结果:

alex是最棒的!

Process finished with exit code 0

代码示例2:属性的搜索顺序问题

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

    def tell(self):
        print("%s---%s"%(self.name,self.age))

class Student(People):
    def tell(self):
        print("呵呵!")

if __name__ == '__main__':
    student = Student("alex",20)

运行结果:

呵呵

示例代码3:

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

    def tell(self):
        print("%s---%s"%(self.name,self.age))

class Student(People):
    def tell(self):
        print("呵呵!")

if __name__ == '__main__':
    student = Student("alex",20)
    #查看Student所有的父类
    print(Student.__bases__)
    #查看最近的父类
    print(Student.__base__)
    #student既是Student类,又是People类
    print(isinstance(student,Student))
    print(isinstance(student,People))

运行结果:

呵呵!
(<class '__main__.People'>,)
<class '__main__.People'>
True
True

Process finished with exit code 0

4、组合的概念

1、一个类的属性可以是一个类对象,通常情况下在一个类里面很少定义一个对象就是它本身,实际意义很少
2、将另外一个对象作为自己的属性成员(自己的一个属性来自于另外一个对象),这就是组合
3、组合也可以解决代码冗余的问题,但是组合反应的是一种什么是什么的关系。
示例代码1:

class Date:
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day
    def tell(self):
        print("%s--%s--%s"%(self.year,self.month,self.day))

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

class Student(People):
    def __init__(self,name,age,sex,year,month,day):
        People.__init__(self,name,age)
        self.sex = sex
        #下面这一步骤就是组合
        self.birth = Date(year,month,day)

if __name__ == '__main__':
    student = Student("alex",25,"man",2015,12,31)
    print("student的birth成员指向了一个Date对象!")
    print("%s"%student.birth)
    student.birth.tell()

运行结果:

student的birth成员指向了一个Date对象!
<__main__.Date object at 0x0000000002604358>
2015--12--31

Process finished with exit code 0

5、抽象类的概念

抽象类是为了更好的对类加以分类,抽象类通常情况下是作为一个类族的最顶层的父类,如植物,并用最底层的类来描述现实世界中的具体的事物.
1>Python中抽象方法定义的方式:利用abc模块实现抽象类,在Java当中如果一个方法没有执行体就叫做抽象方法,而在Python中不是以执行体的有无作为标准,而是以一个方法是否有@abc.abstractmethod装饰器作为标准,有则是抽象方法,要想实现抽象类,可以借助abc模块。ABC是Abstract Base Class的缩写。
2>抽象方法通过子类的实现可以变成普通的方法
3>抽象方法不存在所谓重写的问题,却存在着实现的问题
4>含有抽象方法的类一定是抽象类,但是抽象类不一定含有抽象方法,此时也就没有什么意义了
5>抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
示例程序:

import abc
class File(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read(self):
        pass
    #抽象类中可以有普通方法
    def write(self):
        print("11111")

class B(File):
    #如果写pass,也是可以的,此时子类将会覆盖掉父类
    def read(self):
        pass

if __name__ == '__main__':
    bb = B()
    bb.read()
    bb.write()

运行结果

11111

Process finished with exit code 0

6、多态的概念

1、所谓多态指的是一个父类的引用既可以指向父类的对象,也可以指向子类的对象,它可以根据当前时刻指向的不同,自动调用不同对象的方法,这就是多态的概念。(当然,Python中的多态没必要理解的这么复杂,因为Python自带多态的性能)
2、多态性依赖于同一种事物的不同种形态
3、Python是一门弱类型的语言,所谓弱类型语言指的是对参数没有类型限制,而这是我们可以随意传入对象的根本原因

7、封装的概念

1、在面向对象中,所有的类通常情况下很少让外部类直接访问类内部的属性和方法,而是向外部类提供一些按钮,对其内部的成员进行访问,以保证程序的安全性,这就是封装
2、在python中用双下划线的方式实现隐藏属性,即实现封装
3、访问控制符的用法_包括两种:在类的内部与在类的外部
1>在一个类的内部,所有的成员之间彼此之间都可以进行相互访问,访问控制符__是透明的,失效的
2>在一个类的外部,通过类名对象的方式才可以访问到对象中的_成员
综上:内部之间可以直接访问,在类的外部必须换一种语法方式进行访问
4、在python当中如何实现一个隐藏的效果呢?答案:在Python里面没有真正意义上的隐藏,只能从语法级别去实现这件事。
5、在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:子类名__x,而父类中变形成了:父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

8、@property的用法

1、property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
2、将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则(使用的原因)
3、一旦给函数加上一个装饰器@property,调用函数的时候不用加括号就可以直接调用函数了
代码示例:

class Student:
    @property
    def fun(self):
        print("1111111")

if __name__ == '__main__':
    student = Student()
    student.fun 

示例2:

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

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

if __name__ == '__main__':
    student = Student("alex")
    student._Student__name = "sb_alex"
    print(student.name)

9.staticmethod与classmethod的区别

staticmethod与classmethod的区别:前者是非绑定方法,后者是绑定到类的方法。
静态方法是一类特殊的方法,有时你可能需要写一个属于这个类的方法,但是这些代码完全不会使用到实例对象本身。静态方法实际上就是普通函数,只是由于某种原因(信息局部化)需要定义在类内部。静态方法没有 self 参数。通过类直接调用,不需要创建对象,不会隐式传递self。
要在类中使用静态方法,需在类成员函数前面加上@staticmethod标记符,以表示下面的成员函数是静态函数。使用静态方法的好处是,不需要定义实例即可使用这个方法。另外,多个实例共享此静态方法。
例如:

class Pizza(object):
  @staticmethod
  def mix_ingredients(x, y):
    return x + y

  def cook(self):
    return self.mix_ingredients(self.cheese, self.vegetables)

类方法:
什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。
一个类方法就可以通过类或它的实例来调用的方法, 不管你是用类来调用这个方法还是类实例调用这个方法,该方法的第一个参数总是定义该方法的类对象。
记住:方法的第一个参数都是类对象而不是实例对象。 记住:类也是对象
按照惯例,类方法的第一个形参被命名为 cls.任何时候定义类方法都不是必须的(类方法能实现的功能都可以通过定义一个普通函数来实现,只要这个函数接受一个类对象做为参数就可以了)。
使用场景:
当方法中需要使用类对象(如访问私有类属性等)时,定义类方法。与类相关的方法。
类方法一般和类属性配合使用。类方法中能够访问类属性,但不能访问实例属性。
示例1:

class Countable:
    counter = 0
    def __init__(self):
        Countable.counter += 1
    @classmethod
    def get_counter(cls):
        return Countable.counter

示例2:

>>> class Pizza(object):
...   radius = 42
...   @classmethod
...   def get_radius(cls):
...     return cls.radius




>>> Pizza.get_radius()
42

补充的内容:

1.类:一种数据类型 ,不占内存空间 ,对象占内存空间。
2.object 基类,超类,所有类的父类,一般没有合适的父类,就写这个。
3.类里定义方法 self 代表类的实例(某个对象) 作为第一个参数哪个对象调用方法,那么对方法中的self就代表哪个对象,self 不是关键字。
4.构造函数 init() 在使用类创建对象的时候自动调用。
注意:如果不显示的写出构造函数,默认会自动添加一个空的构造函数。
5.在函数里定义的对象,会在程序结束时自动释放,这样可以用来减少内存空间的浪费。
6.__str__():在调用print打印对象时自动调用,是给用户用的,是一个描述对象的方法。

def __str__():
    return '%s-%d-%d' % (self.name,self.age,self.weight)

6.不能直接访问student.__money 是因为python解释器把__money变成了_Person__money ,但是强烈建议不这么干,不同解释器可能存在解释的变量名不一致。
7.xxx属于特殊变量 可直接访问。
_xxx 外部可直接访问,但是按照约定的规则,当我们看到这样的变量时,虽然可以访问,但是请视为私有变量,不要直接访问。
8.继承:

class Student(Person):
    def __init__(self,name,age):
        super(Student,self).__init__(name,age)

多继承:

class Child(Father,Mother):
    def __init__(self,money,faceValue,height):
        Father.__init__(self,money)
        Mother.__init__(self,faceValue)
        slef.height = height

多个父类中方法名相同,默认调用的是括号中前面的方法。

猜你喜欢

转载自blog.csdn.net/hs_blog/article/details/80868656