Python-面向对象

无常 2020-06-01 08:25:16
原文地址:https://segmentfault.com/a/1190000022391751

1. 面向对象

> 在学习类之前,我们需要了解 面向对象编程, 面向对象就是将编程当成是一个事物,对外界来说,事物是直接使用的,不用去管他内部的情况。而编程就是设置事物能够做什么事。面向对象核心是 对象,在python中一切皆对象,而在pythoh中 对象 就是一系列 数据功能 的集合。

2. 类

> 多个 对象 有相似的数据与功能,我们可以加以分类, 对一系列具有相同 特征行为 的事物的统称,是一个 抽象的概念,不是真实存在的事物。可以把 同一类对象相同的数据和方法放到类中, 不需要每个对象定义一份相同的数据和方法,每个对象只需要存放自己特有的数据与方法,这要做的好处就是极大的节省了内存空间,简单来说类就是用来存放多个对象相同的 数据功能的容器

2.1 定义类

在程序中必须先定义类, 然后再调用类产生对象(调用类拿到的返回值就是对象), 产生对象的类和对象之间存在关联的是,对象可以访问类中共有的数据与功能,所以类中的内任然是属于对象, 类起到的作用是节省空间, 减少代码冗余

语法

"""
class 类名():
    代码块
    ......
"""

示例
我们就以生活中的学生类来做示例

# 定义类
class Student:
    """
        注释

    """
    school = '清华'

    def study(self):
        print('is learning')

    def choice_course(self):
        print('choose course')

# 查看类的名称空间,可以看到类的数据,行为,文档注释,还用其他的一些属性和方法
print(Student.__dict__)
# 结果: {'__module__': '__main__', '__doc__': '\n        注释\n\n    ', 'school': '清华', 'study': <function Student.study at 0x109ab5f28>, 'choice_course': <function Student.choice_course at 0x109abb0d0>, ....}

# 访问名称空间的名字,查看属性
print(Student.school)  # 类的数据属性
# 结果: 清华
print(Student.study)  # 类的函数属性
# 结果: <function Student.study at 0x109ab5f28>
print(Student.choice_course)
# 结果: <function Student.choice_course at 0x109abb0d0>

> 学生身上有 school 这个属性,和 study, choice_course 行为

增加类的属性

# 添加数据
Student.country = 'China'
print(Student.__dict__)
# 结果: {..... 'country': 'China'}
print(Student.country)
# 结果: China

def run():
    print('this is running')

# 添加行为属性
Student.run = run
Student.run()
# 结果: this is running

修改类的属性

# 修改属性
Student.school = '北大'
print(Student.school)
# 结果: 北大

删除类的属性

del Student.country
print(Student.country)  
# 结果: AttributeError: type object 'Student' has no attribute 'country'

实例化类

# 实例化对对象
s1 = Student()
s2 = Student()
s3 = Student()

# 实例调用行为
s1.study()
s1.choice_course()
s2.study()
s2.choice_course()

> 此时 s1,和 s2 实例,s1s2拥有共同的数据和行为,而对象自己身上的自己特定数据和行为没有,需要在类中实现__init__方法

定义类

class Student:
    """
        注释
    """
    school = '清华'

    def __init__(self, name, age, sex):
        """
            stu1, name='张三', age=18, sex='male'
            在类调用时自动执行
        """
        self.name = name
        self.age = age
        self.sex = sex

    def study(self):
        print('is learning')

    def choice_course(self):
        print('choose course')

实例化

s1 = Student(name='张三', age=18, sex='male')

print(s1)
# 结果: <__main__.Student object at 0x10f8ba828>
print(s1.__dict__)
# 结果: {'name': '张三', 'age': 18, 'sex': 'male'}
print(s1.name, s1.age, s1.sex)
# 结果: 张三 18 male

s2 = Student('李四', 10, 'male')

print(s2)
# 结果: <__main__.Student object at 0x1023a88d0>
print(s2.__dict__)
# 结果: {'name': '李四', 'age': 10, 'sex': 'male'}
print(s2.name, s2.age, s2.sex)
# 结果: 李四 10 male

s3 = Student('王五', 20, 'female')

print(s3)
# 结果: <__main__.Student object at 0x1023a8908>
print(s3.__dict__)
# 结果: {'name': '王五', 'age': 20, 'sex': 'female'}
print(s3.name, s3.age, s3.sex)
# 结果: 王五 20 female

> 实例产生的过程来说,调用类会先产生一个空对象s1 然后将 s1 连同调用类时括号类的参数一起传给 Student.__init__(s1, '张三', 18, 'male'),其它实例也是一样的做法

2.2 属性的访问

在类中定义的数据和行为都是类的属性,具体可以 数据属性函数属性,可以通过 __dict__ 方法访问,python提供了专门的属性访问方法

属性访问方式

x = 1

class Student:
    """
        注释
    """
    school = '清华'

    def __init__(self, name, age, sex):
        """
            stu1, name='张三', age=18, sex='male'
            在类调用时自动执行
        """
        self.name = name
        self.age = age
        self.sex = sex

    def study(self, x, y):
        print(f'{self.name} is learning {x + y}')

    def choice_course(self):
        print('choose course')

"""
    1. 查找一个对象的属性顺序是,先找自己的名称空间obj.__dict__,
       再找类的名称空间class.__dict__
"""
# 数据属性访问方式:
stu1 = Student(name='张三', age=18, sex='male')
print(stu1.name)
# 结果: 张三
print(stu1.school)
# 结果: 北大
print(stu1.x)  
# 结果: AttributeError: 'Student' object has no attribute 'x', 类和对象的名称空间都没有 x 属性, 报错

# 函数属性反问方式:
stu1.study()

类的数据属性和函数属性
数据属性

Student.school = '北大'

print(stu1.school, id(stu1.school))
# 结果: 北大 4537981744
print(stu2.school, id(stu2.school))
# 结果: 北大 4537981744
print(stu3.school, id(stu3.school))
# 结果: 北大 4537981744

> 类的数据属性是 所有对象共享所有对象都指向同一个内存地址

函数属性

print(Student.study)
# 结果: <function Student.study at 0x106b820d0>

print(stu1.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b78860>>

print(stu2.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b78898>>

print(stu3.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b788d0>>

stu1.study(1, 3)  # 相当于 Student.study(stu1, 1, 3)
# 结果: 张三 is learning 4

stu2.study(1, 4)
# 结果: 李四 is learning 5

stu3.study(1, 5)
# 结果: 王五 is learning 6

> 类中定义的函数是 绑定给对象使用不同的对象就是不同的绑定方法, 绑定给谁,就应该由谁使用, 谁来调用 就会 把谁当作第一个参数 传给对应的函数

> 绑定到对象方法的这种 自动传值的特征决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。

2.3 继承与派生

继承 是一种创建新类的方式,在python中,新建的类 可以继承 一个或者 多个父类,新建的类可称为 子类 或者 派生类,父类又可以称为 基类超类,对象的继承指的是多个类之间的所属关系,即子类默认继承父类的所有属性和方法

class Parent1:
    pass

class Parent2:
    pass

class Sub1(Parent1):
    pass

class Sub2(Parent1, Parent2):
    pass

print(Sub1.__bases__)
# 结果: (<class '__main__.Parent1'>,)

print(Sub2.__bases__)
# 结果: (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

print(Parent1.__bases__)
# 结果: (<class 'object'>,)

print(Parent2.__bases__)
# 结果: (<class 'object'>,)

> 通过类的内置属性 __bases__ 可以查看类继承的所有父类

子类父类的属性

class People:
    school = '清华'

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

    def tell_info(self):
        print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')

class Student(People):
    def learn(self):
        print(f'{self.name} is learning')

    def tell_info(self):
        print(f'我是学生:姓名:{self.name}年龄:{self.age}性别:{self.sex}')

class Teacher(People):
    def teach(self):
        print(f'{self.name} is teaching')

stu1 = Student('action', 18, 'male')

# Student 类中有方法,不会往父类中找
stu1.tell_info()  
# 结果: 我是学生:姓名:action年龄:18性别:male

print(stu1.__dict__)
# 结果: {'name': 'action', 'age': 18, 'sex': 'male'}

print(Student.__dict__)
# 结果: {'__module__': '__main__', 'learn': <function Student.learn at 0x109eaf1e0>, 'tell_info': <function Student.tell_info at 0x109eaf2f0>, '__doc__': None}

teacher = Teacher('cross', 20, 'female')

 # Teacher 类中没有该方法,往父类中查找
teacher.tell_info() 
# 结果: 姓名:cross年龄:20性别:female

> Teacher 类 和 Student 类中并没有定义__init__方法,但是会从父类中找到__init__因而任然可以正常实例化。

class Foo:
    def f1(self):
        print('foo Foo.f1')

    def f2(self):  # self=obj
        print('from Foo.f2')
        self.f1()  # obj.f1()

class Bar(Foo):
    def f1(self):
        print('from Bar.f1')

obj = Bar()
obj.f2()

# 结果如下:
"""
from Foo.f2
from Bar.f1
"""

> 有了继承关系,python 对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找

> b.f2() 会在父类 Foo 找到 f2 先打印 from Foo.f2, 然后执行 self.f1()b.f1() 仍然按照,对象本身 -> 类Bar -> 父类Foo 的顺序依次找下去,在类Bar中找到 f1 ,因而打印结果为 from Bar.f1

继承的实现原理
对于定义的类, python都会 计算得出一个方法解析顺序(MRO)列表,该(MRO)列表就是一个简单的所有基类的 线性顺序列表

print(Bar.mro())
# 结果: [<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>]

> 在python中字类可以同时继承多个父类,在子类继承了多个父类时,经典类新式类会有不同的MRO, 分别对属性的两种查找方式,深度优先和广度优先,经典类为 深度优先,新式类为 广度优先

经典类和新式类
在python2中有 经典类新式类 的区别,经典类就是 没有继承 object 的类,以及该类的子类,新式类就是 指的就是继承 object 类的类, 以及该类子类, python3 统一为新式类。

派生与方法重写
子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先高于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的 __init__覆盖父类的 __init__

属性派生

class People:
    school = '清华'

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

    def tell_info(self):
        print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')

class Student(People):
    def __init__(self, name, age, sex, course, stu_id):
        People.__init__(self, name, age, sex)
        self.course = course
        self.stu_id = stu_id

    def learn(self):
        print(f'{self.name} is learning')

    def tell_info(self):
        print('我是学生', end='')
        People.tell_info(self)

class Teacher(People):
    def teach(self):
        print(f'{self.name} is teaching')

stu1 = Student('action', 18, 'male', 'python', 1)
print(stu1.__dict__)
# 结果: {'name': 'action', 'age': 18, 'sex': 'male', 'course': 'python', 'stu_id': 1}
stu1.tell_info()
# 结果: 我是学生姓名:action年龄:18性别:male

super方法重写父类方法

class People:
    school = '清华'

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

    def tell_info(self):
        print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')

class Student(People):
    def __init__(self, name, age, sex, course, stu_id):
        super().__init__(name, age, sex)
        self.course = course
        self.stu_id = stu_id

    def learn(self):
        print(f'{self.name} is learning')

    def tell_info(self):
        print('我是学生', end='')
        super().tell_info()

class Teacher(People):
    def teach(self):
        print(f'{self.name} is teaching')

    def tell_info(self):
        print('我是老师', end='')
        # People.tell_info(self)
        super().tell_info()

stu1 = Student('action', 18, 'male', 'python', 1)
print(stu1.__dict__)
stu1.tell_info()

> 调用 super() 会得到一个特殊的对象,该对象专用来引用父类的属性,且严格按照 MRO规定的顺序向后查找。
> 组合

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def tell_birth(self):
        print(f'出生生日: {self.year}-{self.month}-{self.day}')

class Course:
    def __init__(self, name, price, period):
        self.name = name
        self.price = price
        self.period = period

    def tell_info_course(self):
        print(f'课程详细信息: 课程名称: {self.name}, 课程价格:{self.price}, 周期: {self.period}')

class People:
    school = '清华'

    def __init__(self, name, age, sex, date_obj):
        self.name = name
        self.age = age
        self.sex = sex
        self.birth = date_obj

    def tell_info(self):
        print(f'姓名: {self.name} 年龄: {self.age} 性别: {self.sex}')

class Student(People):
    def __init__(self, name, age, sex, stu_id, date_obj):
        People.__init__(self, name, age, sex, date_obj)
        self.stu_id = stu_id
        self.courses = []

    def learn(self):
        print(f'{self.name} is learning')

    def tell_info(self):
        print('我是学生', end='')
        People.tell_info(self)

class Sale(People):
    def __init__(self, name, age, sex, kpi, date_obj):
        People.__init__(self, name, age, sex, date_obj)
        self.kpi = kpi

    def tell_info(self):
        print('我是销售', end='')
        People.tell_info(self)

class Teacher(People):
    def __init__(self, name, age, sex, level, salary, date_obj):
        People.__init__(self, name, age, sex, date_obj)
        self.level = level
        self.salary = salary
        self.courses = []

    def teach(self):
        print(f'{self.name} is teaching')

date_obj = Date(1995, 7, 23)

sale1 = Sale('张三', 20, 'male', 5000, date_obj)
sale1.tell_info()
# 结果: 我是销售姓名: 张三 年龄: 20 性别: male
sale1.birth.tell_birth()
# 结果: 出生生日: 1990-6-23

python = Course('python', 10000, '3month')
linux = Course('Linux', 5000, '2month')

stu1 = Student('王五', 18, 'female', 1, date_obj)
stu1.courses.append(python)
stu1.courses.append(linux)
print(stu1.courses)
# 结果: [<__main__.Course object at 0x108762128>, <__main__.Course object at 0x108762160>]

for course in stu1.courses:
    course.tell_info_course()

# 结果: 
"""
课程详细信息: 课程名称: python, 课程价格:10000, 周期: 3month
课程详细信息: 课程名称: Linux, 课程价格:5000, 周期: 2month
"""

teacher = Teacher('李四', 20, 'male', 3, 50000, date_obj1)
teacher.courses.append(python)
teacher.courses.append(linux)
for course in teacher.courses:
    course.tell_info_course()

# 结果: 
"""
课程详细信息: 课程名称: python, 课程价格:10000, 周期: 3month
课程详细信息: 课程名称: Linux, 课程价格:5000, 周期: 2month
"""

2.4 封装

对象 的数据和行为的 整合 就是封装,对封装到对象或者类中的属性,我们还可以严格控制对他们的访问,分两步骤实现:隐藏开放接口

隐藏属性与开放接口
python类中采用 __ 开头的方式将属性隐藏起来,(设置成私有的),但其实这仅仅只是一种变形操作,类中所有的双下划线开头的属性都会在类定义阶段,检测语法时自动编程 _类名__属性性名 的形式。

将数据隐藏起来就是为了限制类外部对数据的直接操作,然后 类内应提供相应的接口允许外部间接操作数据,接口之上可以附加额外的逻辑来对数据的类型进行严格的控制

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

    def tell_info(self):
        print(f'姓名: {self.__name}, 年龄:{self.__age}')

    def set_info(self, name, age):
        if not isinstance(name, str):
            raise TypeError('name must str')
        if not isinstance(age, int):
            raise TypeError('age must int')
        self.__name = name
        self.__age = age

p = People('action', 18)
p.tell_info()
# 结果: 姓名: action, 年龄:18
p.set_info('cross', 30)
p.tell_info()
# 结果: 姓名: cross, 年龄:30
p.set_info('10', 123)
p.tell_info()
# 结果: 姓名: 10, 年龄:123

print(p.__age)
# 结果: AttributeError: type object 'People' has no attribute '__age'
print(p.__dict__)
# 结果: {'_People__name': '10', '_People__age': 123}
print(p._People__age)
# 结果: 123
p.__age = 12
print(p.__dict__)
# 结果: {'_People__name': '10', '_People__age': 123, '__age': 12}

> 在类内部是 可以直接访问双下划线开头的属性,比如self.__age,因为在类定义阶段内部双下划线开头的属性统一发生了变行, 变形操作只在类定义阶段发生一次,在 类定义之后赋值操作,不会变形隐藏属性与开放接口,本质 就是 为了明确地区分内外类内部可以修改封装内的东西而不影响外部调用者的代码,而 类外部只需要拿到一个接口,只要接口名,参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码,

property
python 专门提供了一个装饰器 property 可以将类中的函数 伪装成 对象的数据类型,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果, 使用property有效的保证了属性的访问的一致性,另外 property 还提供了 设置删除属性 的的功能

class People:
    def __init__(self, name, age, height, weight):
        self.__name = name
        self.__age = age
        self.__height = height
        self.__weight = weight

    @property
    def bmi(self):
        return self.__weight / (self.__height ** 2)

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

    @name.setter
    def name(self, name):
        if not isinstance(name, str):
            raise TypeError('name must str')
        self.__name = name

    @name.deleter
    def name(self):
        raise TypeError('name is not allow delete')

cross = People('cross', 30, 1.80, 75)
print(cross.name)
# 结果: cross
cross.name = 'acton'
print(cross.name)
# 结果: acton

del cross.name
print(cross.name)
# 结果:抛出异常: TypeError: name is not allow delete

2.5 多态与鸭子类型

多态性是指一类事物的多种形态, 可以在不用考虑对象的具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种

不依赖继承,只需要制造出外观和行为相同对象同样可以实现不考虑对象类型而使用对象,这正是python的鸭子类型,比起继承,鸭子类型在某种程度上实现了程序的松耦合度

class Dog(object):
    def work(self):  # 父类提供统一的方法,哪怕是空方法
        print('指哪打哪...')

class ArmyDog(Dog):  # 继承Dog类
    def work(self):  # 子类重写父类同名方法
        print('追击敌人...')

class DrugDog(Dog):
    def work(self):
        print('追查毒品...')

class Person(object):
    def work_with_dog(self, dog):  # 传入不同的对象,执行不同的代码,即不同的work函数
        dog.work()

ad = ArmyDog()
dd = DrugDog()

p = Person()

p.work_with_dog(ad)
# 结果: 追击敌人...

p.work_with_dog(dd)
# 结果: 追查毒品...

> 多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承 Dog 类创建一个新的类,实例化得到的对象 obj,可以使用相同的方式使用obj.work()。

> 多态性的本质在于不同的类中定义相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过父类引用抽象类的概念来硬性显示字类必须有某些方法名

import abc

# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):

    # 该装饰器限制子类必须定义有一个名为talk的方法
    @abc.abstractmethod  
    def talk(self):  
        # 抽象方法中无需实现具体的功能
        pass

class Cat(Animal):  
    # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):
        pass

cat = Cat()  
# 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化

鸭子类型

import abc

class Animal:
    def eat(self):
        pass

    @abc.abstractmethod
    def speak(self):
        pass

class Pig:
    def speak(self):
        print('say heng')

class Dog:
    def speak(self):
        print('say wang')

class People:
    def speak(self):
        print('say hello')

class Radio:
    def speak(self):
        print('say radio')

people = People()
pig = Pig()
dog = Dog()
people.speak()
# 结果: say hello
pig.speak()
# 结果: say heng
dog.speak()
# 结果: sat wang

2.6 类方法和静态方法

类方法
需要用装饰器 @classmethod 来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以 cls 作为第一个参数

class Dog(object):
    __tooth = 10

    @classmethod
    def get_tooth(cls):
        return cls.__tooth

wangcai = Dog()
result = wangcai.get_tooth()
print(result)  # 10

> 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法,类方法一般和类属性配合使用

静态方法
需要通过装饰器 @staticmethod 来进行修饰,静态方法既不需要传递类对象也不需要传递实例对象(形参没有self/cls), 静态方法 也能够通过 实例对象类对象 去访问。

class Dog(object):
    @staticmethod
    def info_print():
        print('这是一个狗类,用于创建狗实例....')

wangcai = Dog()
# 静态方法既可以使用对象访问又可以使用类访问
wangcai.info_print()
Dog.info_print()

> 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
> 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。