Python

Python基础-13面向对象

2020-05-28  本文已影响0人  Surpassme

13 面向对象

13.1 基础概念

类是具有一系列共同特征和行为的事件抽象概念集合。类所描述的概念和现实生活中的类概念非常相似。例如生物有很多种类,食物也有不同的种类,商品也有很多不同的种类。而一个类通过细分又可以划分各种小的分类,如动物就可以分为陆生动物、水生动物和两栖动物等等,被划分到同一个类,其都有着相似的特征和行为方式。

  实例对象通常被定义某一个类事物的具体个体,是该类事物的一个具体表现。如人是一个类,”张三“就是人这个类的实例对象,因为他代表了一个具体类个体。

  类变量一般是定义类中,但不在类方法中的变量,可以在整个实例化对象中公用。

  类变量或实例变量,常用于描述实例对象的特征,如张三的姓名、身高等

  以不严谨的方式来讲,就是定义在类中的函数称之为方法,常用于描述实例对象的行为,一般分为构造函数和常规函数。

  就是子类可以从父类获取父类定义的属性和方法等。

  如果父类的方法不能满足子类的要求,可以定义一个方法名与父类相同名字的方法,这个过程称之方法覆写(如Java中@Override)或方法重写

  类是一个非常抽象的概念,而实例则代表一个具体的对象,而与对象绑定的变量可称之为实例变量。

13.2 类定义

    在Python中一个简单的类定义格式如下所示:

class Person(object):
    """
    定义一个人类
    """

    # 类变量
    classVariable="sample"
    # 构造函数,也称实例化对象方法
    def __init__(self,name,age):
        self.name=name
        self.age=age
    # 类中定义的方法
    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"

  详细解释如下所示:

13.3 面向对象三大特性

    面向常见的三大特性:封装继承多态。分别如下所示:

13.3.1 封装

    封装主要将类的属性和方法打包为一个整体,对外部而言,仿佛是不可见的。其主要特点如下所示:

    单下划线和双下划线在Python变量和方法名中都有其特殊的含义,一部分仅仅作为一种约定,一部分则含有特殊意义,详细如下所示:

    前置下划线通常代表一种约定,表示仅在内部使用,通过针对编写程序员,可以简单理解为其他语言的private关键字。

    通常用于解决命名冲突,在Python中,str是一个将其他类型转换为字符串的函数,那如果写str_,则可以代表重新定义一个变量或方法,在PyQT中,这种用法比较多。

    在类环境中使用时会触发名称改写,对Python解释器有特殊含义

    前后双置下划线的方法被称之为魔法方法,表示则Python语言本身定义的特殊方法,在自定义的属性中要避免使用这种命名方式。

    通常用作临时或无意义变量名称,另外也可以表示Python REPL会话中上一个表达式的结果。

13.3.2 继承

    继承通常是指子类从父类中获取父类的数据和方法等等。其主要特点如下所示:

13.3.3 多态

    多态是指不同的对象可以执行相同的动作,但却要通过自己的实现代码来执行。国粹京剧之前都是子承父业,代代相传的艺术。假设有一对父子,父亲是非常有名的京剧艺术家,儿子长大成人,模仿父亲的戏也惟妙惟肖。有一天父亲生病,导致无法上台,而父亲的戏票已经卖出,如果退票则损失太大。因为京剧都是需要化妆后才上台,那是不是儿子可以代替父亲上台?在这里需要注意以下几点:

    儿子代替父亲出演,化妆上台后就是父亲身份了。

    儿子模仿再像也是模仿,只能以自己的理解来出演父亲的作品

    儿子经过多年学习,其实已经有了自己独特的表演模式和行为习惯,但代替父亲出演时,是不能以自己的表演方式出演的。必须按父亲的风格去表演。

13.4 创建类

    我们先来创建一个类,示例如下所示:

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

    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"
    
    def run(self):
        return f"{self.name} is running"

13.5 创建类的实例对象

    创建类的实例对象就是根据类创建具体的个体,如下所示:

person=Person("Surpass",28)

    以上就完成了根据Person类实例化一个具体的人,其名字为Surpass,年龄为28,在创建实例后,我们就可以访问类的属性和方法,通过方式为:

实例对象.[属性或方法]

13.6 使用类和实例

13.6.1 访问属性

person=Person("Surpass",28)
print(f"name is {person.name},age is {person.age}")

13.6.2 访问方法

person=Person("Surpass",28)
print(f"person info is {person.getPersonInfo()}")

13.6.3 创建多个实例

    既然类是一种事件的抽象集合,那当然也可以根据创建n个实例对象了,如下所示:

personA=Person("Surpass",28)
personB=Person("Kevin",38)
personB=Person("Lisa",20)

13.6.4 为属性指定默认值

    类中的每个属性必须有初始值,对于一些经常无需要每次赋值的属性可以指定默认值,如下所示:

class Person:

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

    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"

    def run(self):
        return f"{self.name} is running"
        
# 实例化对象
personA=Person("Surpass")

    看看上面的用法是不是跟函数使用很像。

13.6.5 修改属性值

    如果需要修改属性的值,可以使用以下方法进行修改

1.通过实例进行修改

    示例代码如下所示:

class Person:

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

    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"

    def run(self):
        return f"{self.name} is running"

person=Person("Surpass",28)
print(f"Before modify property {person.name} {person.age}")
person.name="Lisa"
person.age=20
print(f"After modify property {person.name} {person.age}")

输出结果如下所示:

Before modify property Surpass 28
After modify property Lisa 20

2.通过调用方法进行修改

    通过实例修改直接修改属性值非常方便,如下所示:

class Person:

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

    def getName(self):
        return self.name

    def setName(self,name):
        self.name=name

    def getAge(self):
        return self.age

    def setAge(self,age):
        self.age=age

    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"

    def run(self):
        return f"{self.name} is running"

person=Person("Surpass",28)
print(f"Before modify property {person.name} {person.age}")
person.setName("Lisa")
person.setAge(20)
print(f"After modify property {person.name} {person.age}")

输出结果如下所示:

Before modify property Surpass 28
After modify property Lisa 20

    熟悉Java的小伙伴同学,一眼就看出来这种跟Java很像,而更地道的Python写法如下所示:

class Person:

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

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

    @name.setter
    def name(self,value):
        if not isinstance(value,(str,)):
            raise TypeError("Expect str")
        else:
            self._name=value

    @name.deleter
    def name(self):
        raise AttributeError("Can not delete attribute")

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

    @age.setter
    def age(self,value):
        if not isinstance(value,(int,)):
            raise TypeError("Expect a numer")
        elif value<0:
            self._age=0
        else:
            self._age=value

    @age.deleter
    def age(self):
        raise AttributeError("Can not delete attribute")

    def getPersonInfo(self):
        return f"name is {self.name},age is {self.age}"

    def run(self):
        return f"{self.name} is running"

person=Person("Surpass",28)
print(f"Before modify property {person.getPersonInfo()} ")
person.name="Lisa"
person.age=-123
print(f"After modify property {person.getPersonInfo()}")
print(f"测试异常情况")
person.name=98
person.age="098"

输出结果如下所示:

Before modify property name is Surpass,age is 28 
After modify property name is Lisa,age is 0
测试异常情况
Traceback (most recent call last):
  File "C:/Users/Surpass/Documents/PycharmProjects/SADCI/TempCode/basicCode.py", line 52, in <module>
    person.name=98
  File "C:/Users/Surpass/Documents/PycharmProjects/SADCI/TempCode/basicCode.py", line 15, in name
    raise TypeError("Expect str")
TypeError: Expect str

    以上这种方法,必须getter、setter、deleter三个方法的名称必须一样,且必须先创建getter,后面的setter、deleter才能访问和使用,那如果已经事先定义好了getter、setter、deleter的方法,还有没有办法实现以上这种做法?答案当然是肯定的,如下所示:

class Person:

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

    def getName(self):
        return self._name

    def setName(self,value):
        if not isinstance(value,(str,)):
            raise TypeError("Expect str")
        else:
            self._name=value

    def delName(self):
        raise AttributeError("Can not delete attribute")

    def getAge(self):
        return self._age

    def setAge(self,value):
        if not isinstance(value,(int,)):
            raise TypeError("Expect a numer")
        elif value<0:
            self._age=0
        else:
            self._age=value

    def delAge(self):
        raise AttributeError("Can not delete attribute")

    ageProperty=property(getAge,setAge,delAge)
    nameProperty=property(getName,setName,delName)

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

person=Person("Surpass",28)
print(f"Before modify property {person.getPersonInfo()} ")
person.setName("Lisa")
person.setAge(-123)
print(f"After modify property {person.getPersonInfo()}")
print(f"测试异常情况")
person.setName(98)

输出结果如下所示:

Before modify property name is Surpass,age is 28 
After modify property name is Lisa,age is 0
测试异常情况
Traceback (most recent call last):
  File "C:/Users/Surpass/Documents/PycharmProjects/SADCI/TempCode/basicCode.py", line 49, in <module>
    person.setName(98)
  File "C:/Users/Surpass/Documents/PycharmProjects/SADCI/TempCode/basicCode.py", line 13, in setName
    raise TypeError("Expect str")
TypeError: Expect str

    以上通过方法来修改属性的思想就是面向对象中的封装思想。但在Python,如果一个类并不存在某个属性,通过对象.属性名可以添加一个新的属性,但这种做法破坏了面向对象中的封装特性,因此不建议使用。如下所示:

class Person:

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

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

    @name.setter
    def name(self,value):
        if not isinstance(value,(str,)):
            raise TypeError("Expect str")
        else:
            self._name=value

    @name.deleter
    def name(self):
        raise AttributeError("Can not delete attribute")

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

    @age.setter
    def age(self,value):
        if not isinstance(value,(int,)):
            raise TypeError("Expect a numer")
        elif value<0:
            self._age=0
        else:
            self._age=value

    @age.deleter
    def age(self):
        raise AttributeError("Can not delete attribute")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

person=Person("Surpass",28)
# 通过实例直接增加一个新的属性,不推荐
person.country="China"
print(f"add new person property : {person.country}")

输出结果如下所示:

add new person property : China

13.7 属性

    属性常用于描述类的特征,可以通过类中定义的方法访问,也可以通过类或实例进行访问。因因此属性又可分为类属性实例属性

13.7.1 实例属性

    实例属性,通俗来讲是通过self.变量名称定义的属性,有时也称实例变量,属于特定的实例对象。示例如下代码如下所示:

class Person:

    def __init__(self,name,age):
        # 实例属性赋值
        self._name=name
        self._age=age

    # 获取实例属性值
    def getName(self):
        return self._name


# 实例属性需要与实例对象进行绑定后才能访问
person=Person("Surpass",28)
print(f"{person.getName()}")

13.7.2 类属性

    Python允许声明属于类本身的变量称之为类属性或类变量,类属性无需实例化对象即可,如下所示:

class Person:
    # 类属性
    classVar="类属性"
    def __init__(self,name,age):
        # 实例属性赋值
        self._name=name
        self._age=age

    # 获取实例属性值
    def getName(self):
        return self._name


# 实例属性需要与实例对象进行绑定后才能访问
person=Person("Surpass",28)
# 访问实例属性-通过实例
print(f"访问实例属性-通过实例:{person.getName()}")
# 访问类属性-通实例
print(f"访问类属性-通实例:{person.classVar}")
# 访问类属性
print(f"访问类属性:{Person.classVar}")

输出结果如下所示:

访问实例属性-通过实例:Surpass
访问类属性-通实例:类属性
访问类属性:类属性

13.7.3 私有属性

    Python没有Java/C#语言的权限控制字段,如public/protected/private等等,通常的约定是以单下划线开头定义的属性为私有属性,如下所示:

class Person:
    # 类私有属性
    _classVar="类属性"
    def __init__(self,name,age):
        # 实例属性赋值
        self._name=name
        self._age=age

13.7.4 特殊属性

    Python对象中包含很多开始和结尾以双下划线的开始和结束的方法,称为特殊属性,常见的特殊属性如下所示:

特殊方法 含义 示例
obj. __dict__ 对象的属性字典 int.__dict__
instance.__class__ 对象所属的类 int.__class__
class.__bases__ 类的基类元组 int.__bases__
class.__base__ 类的基类 int.__base__

13.7.5 小结

13.8 继承

    在创建一个类,并不需要每次都新建。如果已经现成的类,其已有功能能满足你的要求,那么则可以使用继承来达到目的。一个类继承另一个类时,将自动获取另一个类的属性和方法。原有的类称为父类,新的类称为子类。子类除了继承父类的属性和方法,还可以定义自己的属性和方法,也可以重写父类的方法。

13.8.1 构造方法_init_()

    创建子类的实例时,Python首先需要完成的任务是给父类的所有属性赋值。因此子类的方法_init_()需要父类施以援手。如下所示:

1.显式调用父类构造方法

class Person:

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

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):

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

student=Student("Surpass",25)
print(student.getPersonInfo())

输出结果如下所示:

name is Surpass,age is 25

    以上代码详解如下所示:

2.显式调用父类构造方法并添加自己的属性

    示例如下所示:

class Person:

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

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):

    def __init__(self,name,age,classNumber):
        super().__init__(name,age)
        self._classNumber=classNumber

    def getStudentInfo(self):
        return f"name is {self._name},age is {self._age},classNumer is {self._classNumber}"

student=Student("Surpass",25,"Class-NO.1")
print(student.getStudentInfo())

输出结果如下所示:

name is Surpass,age is 25,classNumer is Class-NO.1

3.隐式调用父类构造方法

    示例如下所示:

class Person:

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

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):
    pass

student=Student("Surpass",25)
print(student.getPersonInfo())

输出结果如下所示:

name is Surpass,age is 25

    通过以上几种,那可以猜测一下,是不是不管子类有没有声明构造方法,都是先调用父类构造方法呢?来看看以下示例:

1.父类和子类都有定义自己构造函数

class Person:

    def __init__(self,name,age):
        self._name=name
        self._age=age
        print("1.调用父类构造方法")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):
    def __init__(self,name,age,classNumber):
        self._name=name
        self._age=age
        self._classNumber=classNumber
        print("2.调用子类构造方法")

    def getStudentInfo(self):
        return f"name is {self._name},age is {self._age},classNumer is {self._classNumber}"

student=Student("Surpass",25,"Class-No.1")

输出结果如下所示:

2.调用子类构造方法

2.父类有定义自己构造函数,子类未定义自己的构造函数

class Person:

    def __init__(self,name,age):
        self._name=name
        self._age=age
        print("1.调用父类构造方法")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):
    pass

输出结果如下所示:

1.调用父类构造方法

3.父类和子类都有定义自己构造函数,但子类显式调用父类构造方法

class Person:

    def __init__(self,name,age):
        self._name=name
        self._age=age
        print("1.调用父类构造方法")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):

    def __init__(self,name,age,classNumber):
        super().__init__(name,age)
        self._classNumber=classNumber
        print("2.调用子类构造方法")

    def getStudentInfo(self):
        return f"name is {self._name},age is {self._age},classNumer is {self._classNumber}"

student=Student("Surpass",25,"Class-NO.1")

输出结果如下所示:

1.调用父类构造方法
2.调用子类构造方法

    通过以上代码,可以总结出以下结论:

13.8.2 子类定义属性和方法

    当一个类继承自父类后,父类的功能无法自身需求后,子类可以定义自己的属性和方法,示例如下所示:

class Person:

    def __init__(self,name,age):
        self._name=name
        self._age=age
        print("1.调用父类构造方法")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):

    def __init__(self,name,age,classNumber):
        super().__init__(name,age)
        # 子类自身定义的属性
        self._classNumber=classNumber
        print("2.调用子类构造方法")

    # 子类自身定义的方法,显式调用父类方法
    def getStudentInfo(self):
       return super().getPersonInfo()

    # 子类自身定义的方法
    def getStudentCount(self):
        studentList=[]
        studentList.append(self._name)
        return len(studentList)

输出结果如下所示:

1.调用父类构造方法
2.调用子类构造方法
调用父类方法的结果:name is Surpass,age is 25
调用子类自身的方法:student count is 1

13.8.3 覆写父类方法

    在Python中覆写父类方法非常简单,只需要让子类中方法名称与父类名称一致即可,如下所示:

class Person:

    def __init__(self,name,age):
        self._name=name
        self._age=age
        print("1.调用父类构造方法")

    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age}"

    def run(self):
        return f"{self._name} is running"

class Student(Person):

    def __init__(self,name,age,classNumber):
        super().__init__(name,age)
        # 子类自身定义的属性
        self._classNumber=classNumber
        print("2.调用子类构造方法")

    # 子类自身定义的方法
    def getStudentCount(self):
        studentList=[]
        studentList.append(self._name)
        return len(studentList)

    # 覆写父类已经定义的方法
    def getPersonInfo(self):
        return f"name is {self._name},age is {self._age},class number is {self._classNumber}"

student=Student("Surpass",25,"Class-NO.1")
print(f"调用子类自身的方法:student count is {student.getStudentCount()}")
print(f"调用子类覆写父类的方法:{student.getPersonInfo()}")

输出结果如下所示:

1.调用父类构造方法
2.调用子类构造方法
调用子类自身的方法:student count is 1
调用子类覆写父类的方法:name is Surpass,age is 25,class number is Class-NO.1

13.8.4 多重继承

    像Java/C#等语言是单继承(即一个类同时只能继承一个父类,)和多接口继承(即一个类可继承多个接口),多层继承(因为继承的父类又继承自其他父类,看起来有多个层次),在Python中支持多重继承(即一个类可以同时继承多个类),示例如下所示:

class A:
    def __init__(self):
        print("1.父类构造方法-1")

    def getMethonA(self):
        print(f"1.父类方法{self.getMethonA.__name__}")


class B:
    def __init__(self):
        print("2.父类构造方法-2")

    def getMethonB(self):
        print(f"2.父类方法{self.getMethonB.__name__}")

class C:
    def __init__(self):
        print("3.父类构造方法-3")

    def getMethonC(self):
        print(f"3.父类方法{self.getMethonC.__name__}")


class D(A,B,C):

    def getMethonD(self):
        print(f"4.子类方法{self.getMethonD.__name__}")

d=D()
d.getMethonA()
d.getMethonB()
d.getMethonC()
d.getMethonD()

输出结果如下所示:

1.父类构造方法-1
1.父类方法getMethonA
2.父类方法getMethonB
3.父类方法getMethonC
4.子类方法getMethonD

    使用多重继承有好也有坏

1.优点是可以调用多个父类的方法
2.缺点是如果继承的多个父类又属于同一个父类,则会出现二义性,另外也会造成逻辑上的复杂性增加,因此不推荐使用多重继承

13.9 方法

    方法是与类相关的函数,Python用于定义一个方法格式如下所示:

def 方法名称(self,参数1,参数2...):
    """函数描述"""
    函数体
    return 返回值

    方法的定义也前面的函数是一致,唯一的区别,是参数第一个位置必须是self,在Python方法常被分为构造方法普通方法类方法静态方法

13.9.1 构造方法

    构造方法通常用于在实例化一个对象前,给对象传递一些必须的属性值,通常以__init__()做为标识符,一般格式如下所示:

class Person:
    # 构造方法
    def __init__(self,name,age):
        self._name=name
        self._age=age

13.9.2 普通方法

    除了构造方法、类方法和静态方法之外的方法称之为普通方法,这种方法一般由用户自行定义的,示例如下所示:

class Person:
    # 构造方法
    def __init__(self,name,age):
        self._name=name
        self._age=age

    # 普通方法
    def getName(self):
        return self._name

person=Person("Surpass",28)
print(f"访问普通方法:{person.getName()}")

输出结果如下所示:

访问普通方法:Surpass

13.9.3 类方法

    类方法与类属性非常相似,是一种属于类的方法,不需要实例化就可以访问的一种方法,但需要使用装饰器@classmethon,其语法格式如下所示,注意与普通方法的区别在于第一个参数:

@classmethod
def 方法名称(cls,参数1,参数2...):
   函数体
   return 返回值

    示例代码如下所示:

class Person:
    className="这是类属性"
    # 构造方法
    def __init__(self,name,age):
        self._name=name
        self._age=age

    # 普通方法
    def getName(self):
        return self._name

    # 类方法,可以访问类属性,但不能访问对象属性
    @classmethod
    def getPersonInfo(cls,hello="Hello,"):
        return f"{hello} {cls.className} "


person=Person("Surpass",28)
print(f"访问普通方法:{person.getName()}")
print(f"访问类方法:{Person.getPersonInfo('Welcome, ')}")

输出结果如下所示:

访问普通方法:Surpass
访问类方法:Welcome,  这是类属性

13.9.4 静态方法

    静态方法即不需要传入self参数、也不需要传入cls参数,从面使得调用静态方法并不能获取得类中定义的属性和其他方法,但并不会影响类对象和实例对象的状态。静态方法有点像附属于类的对象的工具,与普通方法不同,调用静态方法时,只能通过类对象或实例对象,而不能脱离类对象使用,即静态方法被束缚在类对象中。其语法格式如下所示:

@staticmethod
def 方法名称(参数1,参数2...):
   函数体
   return 返回值

    示例代码如下所示:

class Person:
    className="这是类属性"
    # 构造方法
    def __init__(self,name,age,height,weight):
        self._name=name
        self._age=age
        self._height=height
        self._weight=weight

    # 普通方法
    def getName(self):
        return self._name

    # 类方法,可以访问类属性,但不能访问对象属性
    @classmethod
    def getPersonInfo(cls,hello="Hello,"):
        return f"{hello} {cls.className} "

    # 静态方法,不可以访问类属性
    @staticmethod
    def getBMI(h,w):
        return w/((h/100)**2)

    # 普通方法
    def showBMI(self):
        bmi=self.getBMI(self._height,self._weight)
        return bmi

person=Person("Surpass",28,170,59)
print(f"访问普通方法:{person.getName()}")
print(f"访问类方法:{Person.getPersonInfo('Welcome, ')}")
print(f"访问静态方法:{Person.getBMI(170,59)}")
print(f"通过实例对象访问:{person.showBMI()}")

输出结果如下所示:

访问普通方法:Surpass
访问类方法:Welcome,  这是类属性
访问静态方法:20.41522491349481
通过实例对象访问:20.41522491349481

13.9.5 小结

本文地址:https://www.jianshu.com/p/f88330bc552d

上一篇 下一篇

猜你喜欢

热点阅读