python基础 面向对象编程

面向对象编程是将数据和操作数据相关的方法封装到对象中。

面向对象&面向过程

面向过程:关注程序的逻辑流程,是一个执行者思维,适合编写小规模程序,更适合一个人的工作,关键是找动词
面向对象:适合分工协作,每个人造不同的零件最后组装,适合从宏观上把握,从整体上分析整个系统,但是具体到实现部分的微观操作,仍然需要面向过程的思路去处理,关键是找名词

对象的进化

随着编程面临的问题越来越复杂,编程语言本身也在进化,从主要处理简单数据开始,随着数据变多进化"数组";数据类型变复杂,进化成了"结构体";处理数据的方式和逻辑变复杂,进化出了“对象”。

对象:就是将不同类型的数据,方法(即函数)放在一起,就是对象。

类的定义

如果把对象比作一个饼干,那么类就是制造这个饼干的模具,所以对象就有同样的属性。

在这里插入图片描述
在这里插入图片描述
一个典型的类的结构

# 定义类的时候,名字首字母大写
class Student:
    # 定义属性,名字固定,均为__init__
    # self 必须位于第一个参数,而且必须有
    def __init__(self, name, score):
        self.name = name
        self.score = score

    # self 必须位于第一个参数
    def say_score(self):
        print("{0}的分数是:{1}".format(self.name, self.score))


# 类名+括号代表相当于调用的是属性,就是第一个__init__
s1 = Student('Di', 18)
s1.say_score()

运行结果

构造函数__init__()

之前提过一个对象包括id,type 和value ,那么value 细分为属性(attribute)和方法(method)。
__init__方法要点

  1. 名称固定,必须是__init__
  2. 第一个参数固定,必须是:self。self指的是刚刚创建好的实例对象
  3. 构造的目的就是为了初始化对象的实例属性,如下代码就是初始化实例。
def __init__(self, name, score):
        self.name = name
        self.score = score

可以理解为这一步是为了定义参数:把形参加到括号里面,在下面定义参数。

通过类名(参数列表)来调用构造函数
调用后,将创建好的对象返回给相应的变量。
比如 s1 = Studen(‘Di’,18)包括了下列两步:

  1. new() 方法:用于创建对象,但我们一般无需重新定义该方法.
  2. int()方法:初始化创建好的对象。初始化是指给对象里面的属性赋值。

实例属性

在这里插入图片描述
刚开始定义的是 属性,后来的的say_score定义的是方法。

# 定义类的时候,名字首字母大写
class Student:
    # 定义属性,名字固定,均为__init__
    # self 必须位于第一个参数,而且必须有
    def __init__(self, name, score):
        self.name = name
        self.score = score

    # self 必须位于第一个参数
    def say_score(self):
        print("{0}的分数是:{1}".format(self.name, self.score))


# 类名+括号代表相当于调用的是属性,就是第一个__init__
s1 = Student('Di', 18)
print(s1.name)
# 调用方法属性
s1.say_score()
# 增加属性:年龄和工资
s1.age = 32
s1.salary = 3000

print(s1.salary)
# 再增加一个对象
s2 = Student('Di1', 30)
print(s2.name)
# 删除对象,里面的属性也会随之消失
del s1
s1.name

运行结果

Di
Traceback (most recent call last):
Di的分数是:18
3000
  File "D:/PycharmProjects/MyTest/Day_7/learn_class.py", line 37, in <module>
Di1
    s1.name
NameError: name 's1' is not defined

实例方法

实例方法是从属于实例对象的方法,实例方法的定义格式如下:
def 方法名(self[形参列表]):
函数体
方法的调用格式如下:
对象.方法名()

要点

  1. 定义实例方法时,第一个参数必须为self。和前面一样,self指当前的实例对象。
  2. 调用实例方法时,不需要也不能给self传参,但是可以增加其他的参数。

还是上面的例子:调用函数的两种写法

s1.say_score()
Student.say_score(s1)

运行结果

Di的分数是:18
Di的分数是:18

函数和方法的区别

  1. 都是用来完成一个功能的语句块,本质一样
  2. 方法调用时,通过对象来调用,方法从属于特定实例对象,普通函数没有这个特点。
  3. 直观上看,方法定义时需要传递self,函数不需要

其他

  1. dir()可以获得所有对象属性
print(dir(s1))

结果

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_score', 'score']
  1. obj.__dict__对象的属性字典
print(s1.__dict__)

运行结果

{'name': 'Di', 'score': 18}
  1. pass 空:表示还没想好怎么写,那就暂时不写
class Man:
	pass
  1. isinstance(对象,类型) 判断“对象”是不是“指定类型”
# 判断s1是不是Student的实例对象,如果是的话就是True,如果有调用的话那就可以是。
print(isinstance(s1,Student))

总结

  1. 类是一个对象,这个对象也有自己的固有属性,但是在值那里面还分为了属性和方法
  2. 定义类的话,要先学会定义属性,然后定义方法,学会如何调用(赋属性,用方法)
  3. 其他是用来判断类的属性等。

类对象

class Student1:
    pass


print(id(Student1))
print(type(Student1))
stu = Student1
stu()
print(id(stu))

运行结果

2377311793064
# 查找这个类的type 指的是这个type的类型是一个模具,是一个饼干类型的
<class 'type'>
# 把Student 赋值给Stu,那么两者的id值是一样的
2377311793064

类属性和类方法

类属性

区别实例方法,实例对象,实例属性,类属性

# 定义类的时候,名字首字母大写
class Student:
    company = 'SXT'  # 类属性
    count = 0  # 类属性

    def __init__(self, name, score):
        self.name = name  # 实例属性
        self.score = score
        Student.count += 1

    # self 必须位于第一个参数
    def say_score(self):  # 实例方法
        print('我的公司是:', Student.company)
        print("{0}的分数是:{1}".format(self.name, self.score))


s1 = Student('Di', 18)  # s1是实例对象,自动调用__init__()方法
s2 = Student('Di1', 90)
s3 = Student('Di2', 70)

# 调用方法属性,实例对象是s1
s1.say_score()
# 判断这个类中共有几个Student对象
print('一共创建了{0}个实例对象'.format(Student.count))
# 因为每创建一次,就调用一次__init__,所以Student.count就加一,所以可以计算出一共调用了多少次


运行结果

我的公司是: SXT
Di的分数是:18
一共创建了3个对象

图例详解【重要】
在这里插入图片描述
尚学堂就是类属性
如果要再调用一次实例对象,那么就生成一个s2。

类方法

类方法是从属于"类对象"的方法,类方法通过装饰器@classmethod来定义。

class Student:
    company = 'SXT'

    @classmethod
    def printCompany(cls):
        print(cls.company)


Student.printCompany()

运行过程分析
在这里插入图片描述
思考: 为什么类里面要有类属性,类方法,实例属性,实例方法?
回答: 就像一个班,一个班有整体信息,也有个人信息,我想观察一个班的整体状况,就需要定义类属性,然后利用类方法计算出相应的参考值,但是一个班里还有许多同学,我需要知道每个同学的状况,就需要知道实例属性和实例方法。
总结
实例属性:定义(init),调用: 实例对象
类对象:定义,调用

静态方法

Python 中允许定义与“类对象”无关的方法,称为“静态方法”
“静态方法”和在模块中定义普通函数没有区别,只不过"静态方法"放到了“类的名字空间里面”,需要通过“类调用"。
静态方法访问实例属性和实例方法会导致错误,但是可以访问类对象和类属性
跟类方法分别不是那么清晰。

class Student:
    company = 'SXT'

    @staticmethod
    def add(a, b):
        print('{0}+{1}={2}'.format(a, b, a + b))

        return a + b


Student.add(20, 30)

运行结果

20+30=50

_del_方法(析构函数)和垃圾回收机制

_del_方法成为"析构方法",用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源,网络连接等。

Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为0)由垃圾回收期调用_del_方法。
在这里插入图片描述
例如这个图,这个对象被abc引用了三次,那么当abc 都不引用的时候,这个对象就被回收了。

# 析构方法测试
class Person:

    def __del__(self):
        print("销毁对象{0}".format(self))
p1 = Person()
p2 = Person()

运行结果

销毁对象<__main__.Person object at 0x0000021AB0E4B4E0>
销毁对象<__main__.Person object at 0x0000021AB0F194A8>

_call_方法和可调用对象

定义了_call_方法的对象,称为‘可调用对象’,即该对象可以像函数一样被调用。
obj()实际上调用的是_call_方法

# 测试可调用方法
class SalaryAccount:
    '''工资计算器'''

    def __call__(self, salary):
        print('算工资啦')
        yearSalary = salary * 12
        daySalary = salary // 22.5
        hourSalary = daySalary // 8
        return dict(yearSalary=yearSalary, daySalary=daySalary, hourSalary=hourSalary)


s = SalaryAccount()
print(s(30000))

运行结果

算工资啦
{'yearSalary': 360000, 'daySalary': 1333.0, 'hourSalary': 166.0}

方法没有重载

在其它语言中,保证参数的类型,数量和方法名一样,那么该方法一致,但是Python中不声明变量类型,就不可以。所以不要使用重名的函数!!

方法的动态性

Python 是动态语言,可以为类添加新方法,也可以修改类的方法。

class Person:
    def work(self):
        print('努力工作')


def watch_video(s):
    print("{0}在玩游戏".format(s))


def work2(s):
    print('好好工作')


# 为类添加新方法
Person.watch = watch_video
p = Person()
p.work()
p.watch()
# 修改类的方法
Person.work = work2
p.work()

运行结果

努力工作
<__main__.Person object at 0x00000230F50FF1D0>在玩游戏
好好工作

私有属性和私有方法

  1. 通常设定:两个下划线开头的属性是私有的(private),其他为公开的(public)
  2. 类内部可以访问私有属性(方法)
  3. 类外部不能直接访问私有属性(方法)
  4. 类外部可以通过“_类名__私有属性(方法)”进行访问
    【注】方法本质上也是属性
class Employee:
    def __init__(self, name, age):
        self.name = name
        self.__age = age  # 私有属性

    def __work(self):  # 私有方法
        print("好好工作")
        # 自己调用自己的属性不用_Employee
        print('我的年龄是{0}'.format(self.__age))

e = Employee('Di', 22)
print(e.name)
# 因为age前面添加了两个下划线,所以age变成私有属性,外部不能访问
print(e._Employee__age)
print(dir(e))
e._Employee__work()
```python
###############运行结果####################
Di
22
['_Employee__age', '_Employee__work', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
好好工作
我的年龄是22

结果说明
可以看到:

  1. 添加两个下划线就变成了私有属性,对外部私有。
  2. e的属性第一个就是那样的,可以通过看dir来判断怎么访问属性。

property 装饰器

@property 可以将一个方法的调用方式变成“属性调用”。

# 测试@property的性能:加了property之后,salary原本是一个函数,可是现在可以被当成一个属性进行访问了,但是不能赋值
class Employee:

    @property
    def salary(self):
        print('好好学习')
        return 10000


s1 = Employee()
print(s1.salary)
s1.salary = 2000

运行结果

Traceback (most recent call last):
好好学习
  File "D:/PycharmProjects/MyTest/Day_7/learn_property.py", line 20, in <module>
    s1.salary = 2000
AttributeError: can't set attribute
10000

一个小题目:将属性改为私有属性,这样用户就不能从外部访问j进行修改,通过get和set来对属性进行获取和修改。

方法1:利用 get 和 set 进行访问

class Employee:
    def __init__(self, name, salary1):
        self.__name = name
        self.__salary1 = salary1

    def get_salary(self):
        return self.__salary1

    def set_salary(self, salary):
        if 100 < salary < 50000:
             self.__salary1 = salary
        else:
            print("录入错误,请重新输入")


s1 = Employee('Di', 3000)
# 因为 name 和 salary 私有化了,所以可以通过get来访问
print(s1.get_salary())
s1.set_salary(-20000)
s1.set_salary(30200)
print(s1.get_salary())
######################运行结果#####################
3000
录入错误,请重新输入
30200

方法2:利用property

class Employee:
    def __init__(self, name, salary):
        self.__name = name
        self.__salary = salary

    @property
    def salary(self):
        return self.__salary

    @salary.setter
    def salary(self, salary):
        if 100 < salary < 50000:
            self.__salary = salary
        else:
            print('录入错误,请重新输入')


s1 = Employee('Di', -3000)
print(s1.salary)
s1.salary = 200
print(s1.salary)
####################运行结果####################
C:\ProgramData\Anaconda3\python.exe D:/PycharmProjects/MyTest/Day_7/learn_property.py
-3000
200

面向对象三大特征介绍

  1. 封装(隐藏)
    隐藏对象的属性和实现细节,只对外提供必要的方法,相当于将细节封装起来,只对外暴露相关调用方法。

  2. 继承
    继承可以让子类具有父类的特性,提高代码的重用性。
    是一种增量进化,在父类设计不变的情况下,可以增加新的功能,改进已有的算法。

  3. 多态
    多态是指同一个方法调用由于对象不同产生不同的行为,生活中这样的例子比比皆是,同样是休息,张三的方式是睡觉,李四的休息是玩游戏。

继承

Tips: 如何继承父类的名称和方法?方法可以直接用,名称作显式声明。

  1. “代码复用”的重要手段
  2. 父类:父类和基类
  3. 子类:子类或者派生类

在这里插入图片描述
Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:

  1. 语法格式如下:
    class 子类类名(父类1[,父类2,…]):
    类体

如果类定义里没有指定父类,则默认父类是object类

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age
        # self.__age = age

    def say_age(self):
        print('学习')


class Student(Person):

    def __init__(self, name, age, score):
        Person.__init__(self, name, age)  # 必须显式的调用父类初始化方法,不然解释器不会去调用
        self.score = score


print(Student.mro())

# 调查父类:Student→Person→Object
a = Student("Di", 10, 90)
# 继承了父类的方法
a.say_age()
# 继承了父类的属性
print(a.age)
# print(a._Person__age)
#################运行结果###################
[<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>]
学习
10

类成员的继承和重写

  1. 成员继承:子类继承了父类除构造方法之外的所有成员
  2. 方法重写:子类可以重新定义父类中的方法,这样就覆盖父类的方法,也称为“重写”
# 测试方法的重写
class Person:

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

    def say_age(self):
        print('我的年龄是{0}'.format(self.age))

    def say_introduce(self):
        print('我的名字叫{0},我今年{1}'.format(self.name, self.age))


class Student(Person):

    def __init__(self, name, age, score):
        Person.__init__(self, name, age)  # 必须显式的调用父类初始化方法,不然解释器不会去调用
        self.score = score
    '''重写了父类的方法'''
    def say_introduce(self):
        print('报告老师,我的名字是{0}'.format(self.name))


a = Student("Di", 18, 90)
a.say_introduce()
####################运行结果#################
报告老师,我的名字是Di

查看类的继承层次和结构

通过mor()或者_mro_可以输出这个类 的继承层次结构。
在这里插入图片描述
通过dir()查看对象属性

重写_str_()方法

# 测试重写object的_str_()
class Person:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '报告老师,我的名字是{0}'.format(self.name)


p = Person('Di')
print(p)
########################运行结果###########################
报告老师,我的名字是Di

多重继承

Python 支持多重继承,一个子类可以有多个"直接父类"。这样,就具备了“多个父类”的特点。尽量避免使用。
在这里插入图片描述

mro()

Python支持多继承,如果父类有相同名字的方法,在没有指定父类名字的时候,解释器将从左到右按照顺序搜索。

class A:
    def aa(self):
        print('aa')

    def say(self):
        print('say aa')


class B:
    def bb(self):
        print('bb')

    def say(self):
        print('bb')


# 如果B和A都有相同的方法,那么python按照从左至右的方法进行选择,下面这个是B,那么就先调用B,如果A放在左边,那么先调用A
class C(B, A):
    def cc(self):
        print('cc')


c = C()
c.say()
c.cc()
c.bb()
#########################运行结果#########################
bb
cc
bb

super()获得父类定义

class A:
    def say(self):
        print('A')


class B(A):
    def say(self):
    # 找到父类里面的定义
        super().say()
        #同样写法:A.say(self)
        print('B')

B().say()
#########################运行结果#####################################
A
B

多态

多态指的是同一个方法调用由于对象不同产生不同的行为。

class Chinese(Man):
    def eat(self):
        print('中国人用筷子吃饭')


class English(Man):
    def eat(self):
        print('英国人用叉子吃饭')


class Indian(Man):
    def eat(self):
        print('印度人用手吃饭')


def manEat(m):
    if isinstance(m, Man):
        m.eat() # 多态,一个方法调用,根据对象不同调用不同的方法。
    else:
        print('不能吃饭')


# 子类也是父类的对象
print(isinstance(Chinese(), Man))
manEat(Chinese())
######################运行结果########################
True
中国人用筷子吃饭

特殊方法和运算符重载

在这里插入图片描述
在这里插入图片描述

# 运算符的重载
class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def __add__(self, other):
        if isinstance(other, Person):
            return "{0}--{1}".format(self.name, other.name)
        else:
            return "不是同类对象,不能相加"


p1 = Person('Di', 19)
p2 = Person('Panda', 25)
print(p1 + p2)
###################运行结果############################
Di--Panda

特殊属性

在这里插入图片描述

class A:
    def aa(self):
        print('aa')

    def say(self):
        print('say aa')


class B:
    def bb(self):
        print('bb')

    def say(self):
        print('bb')


class C(B, A):
    def cc(self):
        print('cc')


# A的一个层级父类
print(A.__base__)
# C的多个父类(多继承)
print(C.__bases__)
# C的多层级结构
print(C.__mro__)
# 子类列表
print(object.__init_subclass__)
##############################运行结果#########################
<class 'object'>
(<class '__main__.B'>, <class '__main__.A'>)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
<built-in method __init_subclass__ of type object at 0x00000000526DC580>

对象的浅拷贝和深拷贝

  • 变量的赋值操作:只是形成两个变量,实际还是指向同一个对象
  • 浅拷贝:Python一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象和拷贝对象会引用同一个子对象。
  • 深拷贝:使用copy的deepcopy模块,递归拷贝对象中包含的子对象。源对象和拷贝对象所有的子对象也不同。
# 测试对象的浅拷贝和深拷贝
import copy


class MobilePhone:
    def __init__(self, cpu, screen):
        self.cpu = cpu
        self.screen = screen


class CPU:
    def calculate(self):
        print("算一个数字出来")
        print("CPU对象", self)


class Screen:
    def show(self):
        print("显示一个好看的画面")
        print("screen:", self)


c1 = Screen()
c2 = CPU()
m1 = MobilePhone(c2, c1)
m2 = copy.copy(m1)

print(m1, m1.cpu, m1.screen)
# m2只会复制第一个对象,下面的不会复制
print(m2, m2.cpu, m2.screen)

m3 = copy.deepcopy(m1)
print(m1, m1.cpu, m1.screen)
# m3深拷贝,会全部复制,所以看到的都不一样
print(m3, m3.cpu, m3.screen)

#############################运行结果##################################
<__main__.MobilePhone object at 0x0000021AB1DFC160> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC240> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC160> <__main__.CPU object at 0x0000021AB1DFC0F0> <__main__.Screen object at 0x0000021AB066F208>
<__main__.MobilePhone object at 0x0000021AB1DFC2B0> <__main__.CPU object at 0x0000021AB1DFC320> <__main__.Screen object at 0x0000021AB1DFC390>

组合

什么时候用"父类"?“ is -a ”的关系。我们可以使用继承,从而实现子类拥有父类的方法和属性。“is-a ”关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物。

什么时候用组合?也能实现一个类拥有另一个类的方法和属性。“has-a”关系指的是,手机拥有CPU。

class A1:
    def say_a1(self):
        print('a1,a1,a1')


class B1(A1):
    pass


b1 = B1()
b1.say_a1()


class A2:
    def say_a2(self):
        print('a2,a2,a2')


class B2:
    def __init__(self, a):
        self.a = a


a2 = A2()
b2 = B2(a2)
b2.a.say_a2()
##################################运行结果##################################
a1,a1,a1
a2,a2,a2
class MobilePhone:
    def __init__(self, cpu, screen):
        self.cpu = cpu
        self.screen = screen


class CPU:
    def calculate(self):
        print("算一个数字出来")
        print("CPU对象", self)


class Screen:
    def show(self):
        print("显示一个好看的画面")
        print("screen:", self)


a = MobilePhone(CPU(), Screen())
a.cpu.calculate()

设计模式_工厂模式的实现

设计模式是面对对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计模式有很多种,比较流行的是“GOF23”种设计模式。

工厂模式

class CarFactory:
    def create(self, brand):
        if brand == '奔驰'
            return Benz()
        elif brand == '宝马'
            return BMW()
        elif brand == '比亚迪'
            return BYD()


class Benz:
    pass


class BMW:
    pass


class BYD:
    pass

a = CarFactory()
b1 = a.create("奔驰")
b2 = a.create("宝马")

单例模式

单例模式的核心作用是确保一个类只有一个实例,并且提供一个访问该实例全局访问点
单例模式只生成一个实例对象,减少了对系统资源的开销。

# 测试单例模式
class MySingleton:
    __obj = None
    __init_flag = True

    def __new__(cls, *args, **kwargs):
        if cls.__obj == None:
            cls.__obj = object.__new__(cls)
        return cls.__obj

    def __init__(self, name):
        if MySingleton.__init_flag == True:
            print('init.......')
            self.name = name
            MySingleton.__init_flag = False


a = MySingleton('aa')
b = MySingleton('bb')
print(a)
print(b)
############################运行结果###################################
init.......
<__main__.MySingleton object at 0x000001542A4EF240>
<__main__.MySingleton object at 0x000001542A4EF240>
发布了25 篇原创文章 · 获赞 2 · 访问量 835

猜你喜欢

转载自blog.csdn.net/Di_Panda/article/details/105091469
今日推荐