Python面向对象

概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Box():
"""盒子类,实现了开门、关门方法"""

def open_door(self):
pass

def close_door(self):
pass

class IceBox(Box):
"""冰箱"""

def ice(self):
"""制冷"""
pass
# 这里的Box()是一个类,具有开关的功能,而IceBox通过继承了Box这个类,也具有了开关的功能,同时在其基础之上添加了制冷的功能

面向对象的思想主要是以对象为主,将一个问题抽象出具体的对象,并且将抽象出来的对象和对象的属性和方法封装成一个类。


类与对象

定义

是多个类似事物组成的群体的统称

对象,又称为实例,由类创造的一个具体可操作结构

python中,一切都是对象,所以之前已经接触过非常多的对象了,比如各种数据类型,使用type()函数,可以输出对象的类(数据类型)

1
2
3
print(type(1220))  # 输出<class 'int'>  说明1220这个对象,是int这个类的对象,由int类创造

print(type('candle')) # 输出<class 'str'> 说明'candle'这个对象,是str这个类的对象,由str类创造

创建类和对象

1
2
3
4
class Candle(object) :
pass

candle=Candle() #创建Candle类的对象 candle

创建一个类的语法相对简单,一般类中有多个函数,把类中的函数称为 对象的方法

实例名=类名() 将类赋值一个变量,这个变量就是类的对象(实例)


类的属性

1
2
class Person (object):
love='money'

直接写在类里面的变量被称为:类的属性,在代码中,每个通过Person类生成的对象,都有love的值,为'money'

类的属性可以被直接修改,比如Person.love='light',之后创建的类的love属性就变成了light


对象属性

1
2
3
4
5
6
7
8
class Person (object):
love='money'
def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender

candle=Person("candle","man")

__init__(self, ...)是一个特殊方法,在创建对象时进行初始化操作,它会自动执行,他的第一个参数永远都是self,代表实例本身。

__init__其中的name,gender规定了对象的属性,即创建的对象必须传入这两个属性,比如candle=Person("candle","man"),我们就创建了一个namecandlegenderman的对象

而之所以需要这个self参数,是因为必须先有实例,才能对实例的属性进行修改,也就是说,必须有self指代类中的对象,才能给通过self.name=name 给对象赋予它传入的名字!

self在类中表示的是对象本身。在类的内部,通过这个参数访问自己内部的属性和方法


类中方法

上文提到过,类中的函数称为方法,而类中的方法主要分为以下3种

实例方法

是最普遍的方法

1
2
3
4
5
6
7
8
9
10
11
12
 class Person (object):
love='money'
def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender

def info(self):
print('my name is ',self.name)

candle=Person("candle","man")
candle.info() #输出my name is candle

info方法就是普通的实例方法,self指代实例,对象在调用该方法的时候,会自动把对象本身传入进去,这种方式称为 隐式传输

candle.info()会输出my name is candle,括号里并不需要传入什么参数


静态方法

使用关键字@staticmethod 创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 class Person (object):

def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender

def info(self): #实例方法
print('my name is ',self.name)

@staticmethod #静态方法
def eat(name):
print(name,'want eat')


Candle=Person("candle","man")
Candle.info()
Candle.eat('candle') #输出结果 candle want eat

可以发现,静态方法本质上,就是一个只能由对象使用的函数罢了!

而静态方法和函数的唯一区别就是,静态方法的作用空间在类中,而函数的作用空间在整个程序中

所以,在编程中,基本不会使用到静态方法


类方法

python中,一切皆是对象,虽然对象是由类创建的,但是类本身也是一个对象

原则上,类方法是将类本身作为对象进行操作的方法,使用@classmethod这一关键字

而类方法的作用是,让类本身可以记录信息,换言之,类方法可以修改类属性

比如我想知道类被调用了几次,而类本身作为一个模版,是不会记录数据的,所以此时需要类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Man:
id = 0 # 类的属性

def __init__(self, name):
self.name = name
self.id = self.id_number()

@classmethod
def id_number(cls): #和self一样,我们规定传入类方法的参数为cls
cls.id += 1
return cls.id

a = Man('A')
print(a.id) #1
b = Man('B')
print(b.id) #2

类方法id_number的作用是让类属性id+1,然后返回类属性,赋予给对象属性,从而每次创建对象时,都实现了类属性id+1


动态绑定属性

在已经创建对象之后,可以给对象添加类中不存在的属性,以及使用类中不存在的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 class Person (object):

def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender

def info(self): #实例方法
print('my name is ',self.name)


candle=Person("candle","man")
candle.love="rain" #给candle这个对象设置其他属性
print(candle.love) #可以输出rain
def show():
print('output')
candle.show=show #将show这个函数绑定在candle上
candle.show() #一样可以调用show这个函数

需要注意的是,动态绑定的属性or函数,仅绑定的对象能用,类创建的其他对象是无法使用的


特性

封装

封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。

实际上,通过类这一结构,我们可以通过创建对象就实现类中的方法,而并不需要关注类中的具体细节

私有变量

私有变量是一种在Python中实现封装的方式,它可以保护类中的数据不被外部访问或修改。私有变量的命名规则是以两个下划线开头,但不以两个下划线结尾,例如__name

1
2
3
4
5
6
7
8
9
10
class Person (object):

def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender
self.__love='rain'

candle=Person('candle','man')
print(candle.love) #程序会报错,因为是私有变量

私有变量的用途不是为了让外部访问,而是为了让类的内部使用。私有变量可以存储类的一些重要的信息,比如状态、属性、配置等,这些信息只有类自己知道,不需要让外部知道。


继承

继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

继承的作用是实现代码的可复用性,提供工作效率

实现形式是:class name (father1,father2):,在创建类的时候,在后面括号中添加父类的名字,python是支持多继承

如果一个子类没有继承任何类,会默认继承object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal(object): # 定义一个动物类,继承自object类
def __init__(self, name, age): # 定义初始化方法,设置name和age属性
self.name = name
self.age = age
def call(self): # 定义一个叫的方法
print(self.name, '会叫')

class Cat(Animal): # 定义一个猫类,继承自动物类
def __init__(self, name, age, gender): # 定义初始化方法,设置gender属性,并调用父类的初始化方法
super(Cat, self).__init__(name, age) # 使用super函数来调用父类的初始化方法
self.gender = gender

def call(self): # 重写父类的叫的方法,改变叫声
print(self.name, '会“喵喵”叫')

在定义子类的属性的时候,必须在构造函数中调用父类的构造函数(__init__)

注意这里使用了super()方法来创建了一个父类的对象,以调用父类的构造函数

super本身其实就是一个类,super()其实就是这个类的实例化对象,它需要接收两个参数 super(class, obj),它返回的是obj的继承顺序中class类的父类(比如C继承了A,B,则C的继承顺序就是C—>A—>B,super(A,c)指的就是B)

实际上在简单的继承关系中,可以不写super()的内置参数,默认指的是当前对象的父类


object类

object类是所有类的父类,因此所有类都有object类的属性和方法

object类有一个__str__()方法,用于返回一个对于“对象的描述”,默认是输出对象的内存地址,但是我们可以重写__str__()方法,使之返回我们想要的信息

1
2
3
4
5
6
7
8
9
10
11
12
class Person (object):

def __init__(self,name,gender):
print("当创建对象时候,会自动执行这个函数")
self.name=name
self.gender=gender
def __str__(self):
return f'my name is {self.name},gender is {self.gender}'
#改写了__str()__方法,使之不输出内存地址,而是输出基本信息

candle=Person('candle','man')
print(candle) #my name is candle,gender is man

多态

Python中的多态是一种面向对象编程的特性,它指的是不同类型的对象可以对同一个方法或函数产生不同的响应。多态的好处是可以提高代码的灵活性和可扩展性,也可以实现不同对象的自定义行为。

Python中的多态有两种形式:

  • 鸭子类型(duck typing):这种形式不依赖于继承关系,而是依赖于对象的属性和方法。只要对象具有相同的属性或方法,就可以被视为同一类型,而不管它们实际上属于什么类。
  • 继承和重写(inheritance and overriding):这种形式是基于继承关系的,子类可以继承父类的属性和方法,并且可以重写父类的方法,以实现自己的功能。这样,当调用父类的方法时,会根据对象的实际类型,执行相应的子类的方法。这种形式也叫做多态性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 定义一个动物类,有一个叫的方法
class Animal:
def call(self):
print("动物会叫")

# 定义一个狗类,继承自动物类,重写叫的方法
class Dog(Animal):
def call(self):
print("狗会汪汪叫")

# 定义一个猫类,继承自动物类,重写叫的方法
class Cat(Animal):
def call(self):
print("猫会喵喵叫")

# 定义一个函数,接受一个对象作为参数,调用它的叫的方法
def func(obj):
obj.call()

# 创建一个狗的实例
dog = Dog()
# 调用函数,传入狗的对象
func(dog)
# 输出结果:狗会汪汪叫

# 创建一个猫的实例
cat = Cat()
# 调用函数,传入猫的对象
func(cat)
# 输出结果:猫会喵喵叫

尽管catdog完全不是一个类型,但是函数func()却可以执行他们的call()方法,这就是动态语言的优势


特殊

特殊属性

__dict__ 属性可以获得类对象或实例对象所绑定的所有属性和方法的字典

__class__属性可以返回当前对象是哪个类的实例

1
2
3
4
5
6
7
8
9
class Person():
def __init__(self,name,age):
self.name=name
self.age=age


candle=Person('candle',22)
print(candle.__dict__) #输出:{'name': 'candle', 'age': 22}
print(candle.__class__) #输出: <class '__main__.Person'>

特殊方法

__new__(cls, *args, **kwargs)

之前在上文讲过,__init__(self, ...)是一个特殊方法,在创建对象时进行初始化操作

那么,随之而来有一个问题,既然__init__是方法,会自动执行,那么是什么对象在创建新对象的时候调用了__init__方法呢?此时就需要用到__new__(cls, *args, **kwargs)这个特殊方法,

只要是面向对象的编程语言,类的实例化都一定包含两个步骤:

  1. 在内存中创建对象,即开辟一块内存空间来存放类的实例 ( new )
  2. 初始化对象,即给实例的属性赋予初始值,例如全部填 0 ( init )

先分析一下new函数的内部

1
2
3
class Person(object):
def __new__(cls, *args, **kwargs):
return super(Person, cls).__new__(cls)

这里的super(Person, cls)的意思就是调用person类的父类,以调用new方法,在上文解释过super方法的用法,在简单的继承关系可以不写

可以看到,__new__方法的参数是cls, *args, **kwargs,这表示它可以接受任意数量和类型的参数,但是它并不会使用这些参数,而是直接传递给父类的__new__方法。这样可以保证Person类的__new__方法和object类的__new__方法的兼容性,也可以让Person类的__init__方法自由地定义和使用参数

python 中,第一步由 new 函数负责,第二步由 init 函数负责。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person(object):

def __new__(cls, *args, **kwargs):
print '__new__ called.'
return super(Person, cls).__new__(cls)

def __init__(self, name, age):
print '__init__ called.'
self.name = name
self.age = age

def __str__(self):
return f'my name is {self.name},age is {self.age}'


candle = Person('Candle', 24)
print(candle)

"""
输出结果:
__new__ called.
__init__ called.
my name is Candle,age is 24
"""

通过运行这段代码,我们可以看到,new方法的调用是发生在init之前的。其实当 你实例化一个类的时候,具体的执行逻辑是这样的:

  1. p = Person(name, age)
  2. 首先执行使用nameage参数来执行Person类的new方法,这个new方法会 返回Person类的一个实例(通常情况下是使用 super(Persion, cls).__new__(cls) 这样的方式),
  3. 然后利用这个实例来调用类的__init__方法,上一步里面__new__产生的实例也就是__init__里面的的 self

所以,__init____new__最主要的区别在于:

  • __init__通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
  • __new__通常用于控制生成一个新实例的过程。它是类级别的方法。

拷贝

浅拷贝

可以使用copy模块的copy函数来创建一个新的对象,该对象是原始对象的浅层副本。下面是一个浅拷贝的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import copy

# 创建一个列表
original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 使用浅拷贝创建一个新的列表
new_list = copy.copy(original_list)

# 修改新列表中的元素
new_list[0][0] = 0

# 打印原始列表和新列表
print("原始列表:", original_list)
print("新列表:", new_list)

运行上述代码,输出如下:

1
2
原始列表: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
新列表: [[0, 2, 3], [4, 5, 6], [7, 8, 9]]

copy.copy(x): 返回对象x的浅拷贝。这个函数创建一个新的对象,该对象是原始对象的浅层副本。新对象的一些属性会直接引用原始对象的属性,而不是创建新的属性。

因此,如果原始对象的属性是可变的,则新对象的属性也将是可变的,并且对新对象的更改将反映在原始对象中。如果原始对象的属性是不可变的,则新对象的属性也将是不可变的,因此对新对象的更改不会影响原始对象


深拷贝

深拷贝是一种对象复制的方式,它会创建一个新的对象,该对象是原始对象的递归副本。深拷贝复制了原始对象的所有属性,包括所有嵌套对象和子对象,而不是仅仅复制引用。这意味着,如果我们修改新对象的属性,原始对象的属性不会受到影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import copy

# 创建一个列表
original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 使用深拷贝创建一个新的列表
new_list = copy.deepcopy(original_list)

# 修改新列表中的元素
new_list[0][0] = 0

# 打印原始列表和新列表
print("原始列表:", original_list)
print("新列表:", new_list)

运行上述代码,输出如下:

1
2
原始列表: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
新列表: [[0, 2, 3], [4, 5, 6], [7, 8, 9]]

可以看到,只改变了新列表的属性,原来列表的属性没有被修改


模块

Python中,模块就是一个Python文件,以.py结尾,包含了Python对象定义和Python语句。

导入

可以使用import module_name 的形式在模块A中导入模块B,从而在模块A中使用模块B里面的函数,类以及方法等

具体的方法是:

在定义好模块之后,我们可以使用import语句来引入模块。例如,要引用名为math的模块,可以在文件的顶部使用import math来引入。在调用math模块中的函数时,需要使用模块名.函数名的语法。

如果一个模块在当前的搜索路径中,当解释器遇到import语句时,该模块将被导入。搜索路径是解释器在搜索模块时首先查找的目录列表。如果要导入模块math.py,需要将命令放在脚本的顶部

1
2
3
import math 
print(math.pi) #调用math的属性,需要用math.pi
print(math.pow(2,3)) #调用math的pow方法

主程序

Python中,如果一个模块不是被导入到其他程序中执行,那么它可能在解释器的顶级模块中执行,这种情况下,该模块被视为主程序。

在每个模块的定义中都包括一个记录模块名称的变量__name__,程序可以检查该变量,以确定它们在哪个模块中执行。如果一个模块是主程序,那么它的__name__变量的值为__main__。因此,我们可以使用以下代码来检查一个模块是否是主程序:

1
2
if __name__ == "__main__":
# 这里是主程序的代码

这个代码块只有在该模块作为主程序运行时才会执行。如果该模块被导入到其他程序中执行,那么这个代码块将不会执行


常用模块

Python中有一些常用的内置模块,方便导入使用

以下是一些常用的Python标准库模块:

  • os模块:提供了与操作系统交互的函数,例如创建、移动和删除文件和目录,以及访问环境变量等
  • sys模块:提供了与Python解释器和系统相关的功能,例如解释器的版本和路径,以及与stdin、stdout和stderr相关的信息
  • time模块:提供了处理时间的函数,例如获取当前时间、格式化日期和时间、计时等
  • random模块:提供了生成随机数的函数,例如生成随机整数、浮点数、序列等
  • math模块:提供了数学函数,例如三角函数、对数函数、指数函数、常数等

还有一些很好用的第三方模块,可以使用 pip install.... 的方式安装到本地上


包是一组Python模块的集合,它们被组织在一个目录层次结构中。一个包是一个包含一个或多个模块的目录,其中还包含一个名为__init__.py的文件,用于区分包和仅包含一堆Python脚本的目录。

包可以嵌套到任意深度,只要相应的目录包含自己的__init__.py文件即可。模块和包的区别似乎仅在文件系统级别上存在。当您导入一个模块或包时,Python创建的相应对象始终是模块类型的对象。导入一个包时,只有__init__.py文件中的变量/函数/类是直接可见的,而不是子包或模块。

在其他Python文件中使用这个包时,您可以使用import语句来导入包中的模块。例如,要使用mypackage包中的module1.py模块中的函数,可以使用以下语法:

1
2
3
4
5
from mypackage import module1

# 使用module1中的函数
module1.my_function()