Python面向对象知识点

一、面向对象编程思想简单解析*

之前学过面向过程实现,它的核心即是过程两字,即就是先干啥后干啥,最后在干啥,基于这个思想编程就像是设计流水线,将复杂的流程问题流程化进而简单化,缺点就是拓展性较差牵一动则全身;
面向对象编程思想核心则是对象两字,对象可以把它当成一个盛放数据和功能的容器,基于这个思想写程序就像在整合工具\程序,优点数据等分开存放,拓展行性强,缺点也很明细,会较大的提升编程的复杂度。

二、类与对象

类即类别/种类,是面向对象分析和设计的基石,如果多个对象有相似的数据与功能,那么该多个对象就属于同一种类。可以把同一类对象相同的数据与功能存放到类里,无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。
在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制,面向对象编程最终的核心仍然是去使用对象。

1. 类的定义与实例化*

必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。
类体最常见的是变量的定义和函数的定义,类体可以包含任意Python代码,类体的代码在类定义阶段就会执行,因而会产生新的名称空间用来存放类中定义的名字;
类的命名应该使用“驼峰体”
调用类的过程称为将类实例化,拿到的返回值就是程序中的对象,或称为一个实例;

2. 类内置方法

2.1 __init__方法

__init__函数(方法),首先需要理解的是,两个下划线开头的函数是声明该属性为私有,不能在类的外部被使用或访问。而__init__方法支持带参数类的初始化,也可为声明该类的属性(类中的变量)。__init__函数(方法)的第一个参数必须为self(self可以改名,默认为self),后续参数为自己定义。

2.2 __str__方法

调用print()打印实例化对象时会调用__str__()打印其的返回值,返回值必须为字符串,否者报错。

class Sailan:
    def __init__(self, name, handsome):
        self.name = name
        self.handsome = handsome

    def __str__(self):
        return f'{self.name}太{self.handsome}了'


print(Sailan('sailan', '帅'))
sailan太帅了

2.3 __del__方法

当对象在内存中被释放时,自动触发执行。

2.4 __call__方法

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__

3. 内置类的特殊属性

类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)

三、什么是封装

封装就是指的是把数据和功能整合起来,封装类与对象我们可以较严格的控制它们的访问,做到隐藏它们和开放关联访问的接口。

1. 隐藏设置私有属性

Python中Class可以采用双下划线开头的方式将属性隐藏(设置成私有的),这只是一种变形的操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式。

  1. 在定义类或者初始化对象时,在属性前加__,就会将该属性隐藏起来;但该隐藏起始只是一种变形,并没有真的隐藏起来,只是检测语法时自动变成了“_类名__属性名”的形式;

  2. 该变形操作是在类定义阶段扫描语法时发生的变形,类定义之后添加的__开头的属性不会发生变形;

  3. 该隐藏是对外不对内。

class Foo:
    __N = 0  # 变形为_Foo__N

    def __init__(self):  # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__x = 10  # 变形为self._Foo__x

    def __f1(self):  # 变形为_Foo__f1
        print('__f1 run')

    def f2(self):  # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__f1()  # 变形为self._Foo__f1()


print(Foo.__N)  # 报错AttributeError:类Foo没有属性__N
print(Foo._Foo__N)  # 知道底层原理我们即可以通过“_类名__属性名”的形式访问
# 0
obj = Foo()
print(obj.__x)  # 报错AttributeError:对象obj没有属性__x

四、特殊装饰的使用

1. property装饰器

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

# 三种使用案例
# 例1
class People:
    def __init__(self, name, height, weight):
        self.name = name
        self.height = height
        self.weight = weight

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


p = People('egon', 1.81, 70)
p.height = 1.84
print(p.bmi())

# 例2
class Student:
    __school = "oldboy"  # _Student__school = "oldboy"

    def __init__(obj, x, y, z):
        obj.__name = x
        obj.__age = y
        obj.gender = z

    def get_name(self):
        print("访问控制")
        return self.__name

    def set_name(self,x):
        print("赋值控制")
        self.__name = x

    def del_name(self):
        print("删除控制")
        del self.__name

    def get_age(self):
        return self.__age

    def set_age(self, x):
        if type(x) is not int:
            print("年龄必须是整型,傻叉")
            return
        self.__age = x

    def del_age(self):
        print("不让删")


    age = property(get_age, set_age, del_age)
    name = property(get_name, set_name, del_name)


stu_obj1 = Student("冯疯子", 18, "female")

# print(stu_obj1.age)
# stu_obj1.age = "19"
# del stu_obj1.age
# print(stu_obj1.age)


print(stu_obj1.name)
# stu_obj1.name="EGON"
# del stu_obj1.name

# 例3:
class Student:
    __school = "oldboy"  # _Student__school = "oldboy"

    def __init__(obj, x, y, z):
        obj.__name = x
        obj.__age = y
        obj.gender = z

    @property
    def name(self):
        print("访问控制")
        return self.__name

    @name.setter
    def name(self, x):
        print("赋值控制")
        self.__name = x

    @name.deleter
    def name(self):
        print("删除控制")
        del self.__name


stu_obj1 = Student("冯疯子", 18, "female")

stu_obj1.name

2. 装饰器 staticmethod、classmethod的使用

类中的定义的函数
绑定方法: 谁来调用就会将谁当作第一个参数传入,

  1. 绑定给对象的方法: 类中定义的函数默认就是绑定给对象的方法,应该是由对象调用,会把对象当作第一个参数传入;
  2. 绑定给类的方法: 在类中的函数上加一个装饰器@classmethod,该函数就绑定给类了,应该是由类来调用,会把类当作第一个参数传入

非绑定方法: 既不与类绑定也不与对象绑定,就是一个普通的函数,谁都可以来调用,没有自动传参的效果,在函数上添加装饰器@staticmethod

使用案例

import uuid
import settings

class MySQL:
    def __init__(self,ip,port):
        self.mid = self.__create_id() # 接受生成的地址
        self.ip = ip
        self.port = port

    def tell_info(self): # 输入内容
        print("%s:<%s:%s>" %(self.mid,self.ip,self.port))

    @staticmethod 
    def __create_id():  # 用uuid模块生成独立地址
        return uuid.uuid4()  # 返回生成的地址

    @classmethod
    def from_conf(cls): # 绑定给类
        return cls(settings.IP, settings.PORT) # 返回对象

# obj = MySQL("10.10.11.11",3306)
# obj.tell_info()


obj1=MySQL.from_conf()
obj1.tell_info()

五、继承指什么*

继承是一种新建类的方式,,新建的类称之为子类或派生类,被继承的类称之为父类、基类、超类,Python中支持多继承,子类会遗传父类的属性,继承是用来解决类与类之间代码冗余问题;

1. 新式类与经典类

经典类:
Python2中有经典类与新式类之分,没有显式地继承object类的类,以及该类的子类,都是经典类,显式地继承object的类,以及该类的子类,都是新式类。
新式类:

显示继承object的类,py3中默认继承object,因此py3中都是新式类;

object类提供了一些常用内置方法的实现,如用来在打印对象时返回字符串的内置方法__str__

2. 继承的实现原理

2.1 棱形问题引入属性查找顺序

棱形问题:
一个子类继承的多条件分支最终汇聚到一个非object类,在菱形继承下新式类与经典类关于属性查找的方式不同;
非菱形继承,经典类与新式类的属性查找顺序都一样;

2.2 属性.mro()查看该属性的查找顺序

我们定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。

由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,

由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,

2.3 经典类 =》深度优先

在这里插入图片描述
如上图,查找顺序为:obj->A->B->E->G->C->F->D->object

2.4 新式类 =》广度优先

在这里插入图片描述
如上图,查找顺序为:obj->A->B->E->C->F->D->G->object

3. Mixins机制

简单说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下

class Vehicle:  # 交通工具
    pass


class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass


class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路

4. 在子类派生的新方法中重用父亲的功能*

方式一:
指名道姓的调用某个类的函数;

特点:
不依赖于继承关系;

class OldboyPeople:
    school = "oldboy"
    #             空对象,"艾利克斯",73,'male'
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender

    def f1(self):
        print('1111111')

class Student(OldboyPeople):
    #            空对象,"艾利克斯",73,'male',1001,"python全栈开放"
    def __init__(self,name,age,gender,stu_id,course):
        OldboyPeople.__init__(self,name,age,gender)  # OldboyPeople.__init__(空对象,"艾利克斯",73,'male')
        self.stu_id = stu_id
        self.course = course


    def choose(self):
        print('%s 正在选课' %self.name)

    def f1(self):
        OldboyPeople.f1(self)
        print("22222")

stu1=Student("艾利克斯",73,'male',1001,"python全栈开放")
# tea1=Teacher("egon",18,'male',2000,10)


stu1.f1()

方式二:
调用super(自己的类名,self)会返回一个特殊的对象,super(自己的类名,self).属性,会参照属性查找发起的那个类的mro列表去它父类中查找属性

特点:严格依赖于继承关系

class OldboyPeople:
    school = "oldboy"
    #             空对象,"艾利克斯",73,'male'
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender

    def f1(self):
        print('1111111')

class Student(OldboyPeople):
    def __init__(self,name,age,gender,stu_id,course):
        # OldboyPeople.__init__(self,name,age,gender)  # OldboyPeople.__init__(空对象,"艾利克斯",73,'male')
        super(Student,self).__init__(name,age,gender)
        self.stu_id = stu_id
        self.course = course


    def choose(self):
        print('%s 正在选课' %self.name)

    def f1(self):
        # OldboyPeople.f1(self)
        # super().f1()
        print("22222")

# print(Student.mro())
stu1=Student("艾利克斯",73,'male',1001,"python全栈开放")
# print(stu1.__dict__)
stu1.f1()

5. 组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合;

class Teacher:
    def __init__(self, name, age, gender, level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level

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


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


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

    def tell(self):
        print('<%s:%s:%s>' % (self.name, self.price, self.period))


tea1 = Teacher("egon", 18, "male", 10)
stu1 = Student("xxx", 19, "male")

python = Course("python开放", 30000, "3mons")
linux = Course("linux课程", 30000, "3mons")

tea1.courses = [python,linux]
stu1.course = python

# tea,stu  # 超级对象

# stu1.course.tell()
for course_obj in tea1.courses:
    course_obj.tell()

6. 用抽象基类实现多态

多态指同一种事物有多种形态,例如动物这种事物有多种形态,如人\狗\猪;
特性:
我们可以在不考虑某一个对象具体类型的前提下,直接使用该对象

父类有的功能,子类一定有

import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
    def speak(self): # 抽象方法中无需实现具体的功能
        pass

    @abc.abstractmethod
    def run(self):
        pass

# Animal()  # Animal的作用是用来制定标准的

class People(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def speak(self):
        print("啊啊啊啊")

    def run(self):
        print("咻咻咻...")

class Dog(Animal):
    def giao(self):
        print("汪汪汪")

class Pig(Animal):
    def heheng(self):
        print("哼哼哼")

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

7. 鸭子类型

可以不依赖于继承,只需要制造出外观和行为相同的对象,同样可以实现不考虑对象类型而使用对象,这就是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下;

#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
    def read(self):
        pass
    def write(self):
        pass

class Disk: #Disk类也有两个与文件类型同名的方法:read和write
    def read(self):
        pass
    def write(self):
        pass

六、反射*

python是动态语言,而反射(reflection)机制被视为动态语言的关键。反射机制指的是在程序的运行状态中对于任意一个类,都可以知道这个类的所有属性和方法;对于任意一个对象,都能够调用他的任意方法和属性。这种动态获取程序信息以及动态调用对象的功能称为反射机制。

python中实现反射非常简单,在程序运行过程中,如果我们获取一个不知道存有何种属性的对象,若想操作其内部属性,可以先通过内置函数dir来获取任意一个类或者对象的属性列表,列表中全为字符串格式

>>> class People:
...     def __init__(self,name,age,gender):
...         self.name=name
...         self.age=age
...         self.gender=gender
... 
>>> obj=People('egon',18,'male')
>>> dir(obj) # 列表中查看到的属性全为字符串
[......,'age', 'gender', 'name']

接下来就是想办法通过内置函数hasattr、getattr、setattr、delattr来操作字符串属性(Python中一切皆对象,类和对象都可以被这四个函数操作,用法一样)

class Teacher:
    def __init__(self,full_name):
        self.full_name =full_name

t=Teacher('Egon Lin')

# hasattr(object,'name')
hasattr(t,'full_name') # 按字符串'full_name'判断有无属性t.full_name

# getattr(object, 'name', default=None)
getattr(t,'full_name',None) # 等同于t.full_name,不存在该属性则返回默认值None

# setattr(x, 'y', v)
setattr(t,'age',18) # 等同于t.age=18

# delattr(x, 'y')
delattr(t,'age') # 等同于del t.age

七、元类及单例*

元类操作解析及单例的三种实现方式

猜你喜欢

转载自blog.csdn.net/msmso/article/details/108187156