前言
- 在python中一切皆为对象,变量是对象,函数是对象,类也是对象。例如:数字6、字符串"hello"、列表[1,2,3]等等都是对象。对象是分配好的一块内存空间,具有三要素:标识(identity, id)、类型(type)、值(value)。
- 标识: 唯一标识一个对象,通常对应对象在计算机内存中的地址。可使用内置函数
id(obj)
返回对象的内存地址。 - 类型:表示对象存储的数据类型,使用内置函数
type(obj)
返回对象所属类型。 - 值:表示对象存储的数据信息,也就是对象的值。使用内置函数
print(obj)
可以直接打印值。
- 变量:python中变量总是存放对象的引用,变量和对象之间的关系为引用(或者说变量是对象的引用)。不需要提前声明变量的数据类型,只需要在用的时候,给变量赋值即可。例如:
a = [1,2,3], a = 6
,[1,2,3]是列表对象,6是数字对象,刚开始变量a是该列表对象的一个引用,后来变量a变成了该数字对象的一个引用。赋值操作=
就是把一个变量和一个对象绑定在一起,就像给对象添加标签。变量本身没有数据类型,但变量指向的对象具有数据类型。当没有变量指向对象时,这个对象便进入了垃圾收集过程。(Python 中的变量与 C/C++ 中的变量有着很大的不同)。顺便说一下,python的基本数据类型可分为两大类:可变数据类型和不可变数据类型。其中,列表、元组和字典属于"可变对象",数字、字符串和元组属于"不可变对象"。 ==
用来比较对象的值(value)是否相同。is
用来比较对象的地址(id)是否相同,即是否为同一个引用。
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # 结果: True
print(id(a)) # 结果: 2247727763080
print(id(b)) # 结果: 2247727861320
print(a is b) # 结果: False
a = 12
b = 12
print(a == b) # 结果: True
print(a is b) # 结果: True,表示此时a和b是同一个引用(地址),即指向同一个对象
具体可参考以下文章:
python 引用和对象理解
Python彻底搞懂:变量、对象、赋值、引用、拷贝
彻底理解Python中对象、对象与变量引用的关系
- 之前学习的字符串(str)、整数(int)、列表(list)等都是类的对象,我们可根据类来创建这些对象。
my_int = int(1)
my_str = str("hello")
my_list = list([1, 2, 3])
print(type(my_int)) # 结果: <class 'int'>
print(type(my_str)) # 结果: <class 'str'>
print(type(my_list)) # 结果: <class 'list'>
str1 = "hello" # python在内部省略了初始化方法
1.1 类的成员
- 类的定义
"""
类的定义语法:
class 类名称:
类的属性
类的行为
创建类对象的语法:
对象 = 类名称()
"""
- class是关键字,表示要定义类了。
- 类的属性:即定义在类中的变量(成员变量)
- 类的行为:即定义在类中的函数(成员方法)
- 成员方法的定义
"""
在类中定义成员方法和定义函数基本一致,但仍有细微区别:
def 方法名(self, 形参1, 形参2, ..., 形参N):
方法体
"""
- 参数列表中的self表示类对象自身的意思,必须填写。但是self这个形参可以修改为a,b,c,…等等,只是约定俗称的定义为self。当我们通过
对象.方法名()
的方式去调用方法时,python会将该对象传递给self这个参数,换而言之,self就是当前调用这个方法的对象。 - 在方法内部,想要访问类的成员变量,必须使用self。
class Student:
name = None
def say_hi(self):
print("Hi,大家好")
def say_hello(self, msg):
print(f"Hello {msg}")
stu = Student()
stu.say_hi() # 会将对象stu传递给self
stu.say_hello("张三")
- 构造方法的定义
构造方法(初始化方法)是__init__()
方法,在创建类对象(构造类)的时候,会自动执行,并将参数自动传递给__init__()
方法使用。
class Student:
"""
在创建对象的时候,即Student("张三", 21, "男"),会产生一块内存空间。然后自动调用(执行)__init__()方法,
并将创建的那块内存空间传递给参数self(即self是该对象的引用),将"张三"、21和"男"分别传递给形参name、age和gender。
最后通过self.name、self.age和self.gender在对象中创建成员变量name、age和gender,并将"张三"、21和"男"分别赋值给它。
只要你实例化对象(创建对象)就一定会自动调用__init__()方法。
"""
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def print_msg(self):
print(f"name:{self.name}, age:{self.age}, gender:{self.gender}")
stu = Student("张三", 21, "男")
stu.print_msg()
"""
输出:
name:张三, age:21, gender:男
"""
- 魔术方法的定义
python的内置方法具有特殊的功能,这些内置方法我们也称之为魔术方法,__init__()
方法是python内置方法之一。下面介绍几个常见的内置方法:
class Student:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def __str__(self):
return f"Student类对象, name={self.name}, age={self.age}, gender={self.gender}"
stu = Student("张三", 21, "男")
print(stu)
"""
如果不加 __str__()方法
print(stu)会输出:<__main__.Student object at 0x000001525F5B1C08>
加上 __str__()方法
print(stu)会输出:Student类对象, name=张三, age=21, gender=男
(__str__"类似于Java中的toString()方法。)
"""
1.2 面向对象的三大特性
面向对象的三大特性:封装、继承、多态。
- 封装:将现实世界事物在类中描述为属性和方法,即为封装。私有成员无法被外部的类对象调用,但是可以被类内部的其它成员使用。
- 继承:就是一个类继承另外一个类的成员变量和成员方法。
- 单继承:
class 类名(父类名): 类内容体
- 多继承:
class 类名(父类1, 父类2, ..., 父类N)
注意:多继承的优先级为从左到右,即父类1的优先级最高。
关键字pass是占位语句,用来保证函数(方法)或者类定义的完整性,表示无内容,空的意思。
- 继承的复写(重写)
- 调用父类同名成员
- 多态:指的是多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。
- 抽象类(接口):包含抽象方法的类被称为抽象类。抽象方法指的是没有具体实现的方法(方法体内部使用关键字pass)
super().__init__()
的使用
- 一个子类继承一个父类(超类)时,子类将自动获得父类的所有属性(成员变量)和行为(成员方法),同时子类还可以定义自己特有的属性和行为。子类可以继承父类中的所有成员,但是没有权限使用父类的私有属性和私有方法。
super()
是一个特殊函数,用于调用父类的成员,可以将python中父类和子类关联起来。 - 方法解析顺序(Method Resolution Order, mro),mro就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。我们可以通过
__mro__
属性来查看类的mro列表,即:类名.__mro__
。 __init__()
构造方法的继承顺序:
(1)当子类中没有__init__()
方法,且不使用super().__init__()
来初始化父类的属性,将会自动调用父类的__init__()
方法。
class Car:
""""这个一个名为汽车的父类"""
def __init__(self):
print("这个一个名为汽车的父类")
self.title = "遥遥领先"
class ElectricCar(Car):
"""这是一个电动车的子类"""
print("这是一个电动车的子类")
my_tesla = ElectricCar()
print(my_tesla.title)
"""
输出:
这是一个电动车的子类
这个一个名为汽车的父类
遥遥领先
"""
(2)子类有__init__()
方法,但不使用super().__init__()
方法来初始化父类的属性。
class Car:
""""这个一个名为汽车的父类"""
def __init__(self):
print("这个一个名为汽车的父类")
class ElectricCar(Car):
"""这是一个电动车的子类"""
def __init__(self):
print("这是一个电动车的子类")
my_tesla = ElectricCar()
"""
输出:
这是一个电动车的子类
# 此时不会调用父类的__init__()方法
"""
(3)在子类的__init__()
方法中使用super().__init__()
方法来初始化父类的属性。
class Car:
""""这个一个名为汽车的父类"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.title = "遥遥领先"
print("这个一个名为汽车的父类")
def get_car_description(self):
print(f"品牌:{self.make},型号:{self.model},生产年份:{self.year}")
class ElectricCar(Car):
"""这是一个电动车的子类"""
def __init__(self, make, model, year, battery_size):
# 初始化父类的属性(成员变量),调用父类的__init__()方法,让子类ElectricCar对象拥有父类的所有属性
# 在python2.7中,继承的语法变为super(子类名, self).__init__(make, model, year)。
# 在Python2中需要写完整,而Python3中可以简写为super()
super().__init__(make, model, year)
# super(ElectricCar, self).__init__(make, model, year) # python2中的语法
self.battery_size = battery_size
print("这是一个电动车的子类")
def get_car_description(self):
print(f"品牌:{self.make},型号:{self.model},生产年份:{self.year},电池容量:{self.battery_size}")
my_tesla = ElectricCar("tesla", "model s", 2016, 70)
my_tesla.get_car_description()
print(f"头衔:{my_tesla.title}")
print(ElectricCar.__mro__) # 查看继承顺序
"""
输出:
这个一个名为汽车的父类
这是一个电动车的子类
品牌:tesla,型号:model s,生产年份:2016,电池容量:70
头衔:遥遥领先
(<class '__main__.ElectricCar'>, <class '__main__.Car'>, <class 'object'>)
#解析"super(ElectricCar, self).__init__(make, model, year)"
首先找到子类(ElectricCar)的父类(Car);
然后将子类的对象self转换为父类的对象,也就是将子类对象self作为实参传递给父类中__init__方法的形参self;
最后父类的对象self就可以调用自己的属性和方法。子类进而继承父类的所有属性
"""
参考文章如下:
浅谈Python的super().__ init__()
Python中super().init()用法
解惑(一) ----- super(XXX, self).init()到底是代表什么含义
1.3 注解
- python在3.5版本的时候引入了类型注解,类型注解是指在代码中涉及到数据交互的地方,提供数据类型的注解(显式的说明)。类型注解的主要功能:帮助第三方IDE工具(PyCharm)对代码进行类型推断,协助做代码提示;帮助开发者自身对变量进行类型注释。类型注解支持:变量的类型注解;函数(方法)形参列表和返回值的类型注解。总而言之,加上注解之后,我们可以直观的看出变量、函数(方法)参数列表和返回值到底是个什么东西(什么类型)。
- 变量类型注解
- 为变量设置类型注解,语法:
变量: 类型
# 1.基础数据类型注解
num1: int = 10
num2: float = 3.1415926
num3: bool = True
num4: str = "hello"
# 2.基础容器类型注解
my_list: list = [1, 2, 3, 4, 5, 6]
my_tuple: tuple = (1, 2, 3)
my_set: set = {1, 2, 3}
my_dict: dict = {1: "a", 2: "b", 3: "c"}
my_str: str = "hello"
# 3. 容器类型详细注解
from typing import List, Tuple, Set, Dict
my_list1: List[int] = [1, 2, 3, 4, 5, 6]
my_tuple1: Tuple[int, str, bool] = (1, "hi", True)
my_set1: Set[int] = {1, 2, 3}
my_dict1: Dict[int, str] = {1: "a", 2: "b", 3: "c"}
# (1)元组类型设置类型详细注解,需要将每一个元素都标记出来
# (2)字典类型设置类型详细注解,需要2个类型,第一个是key第二个是value
# 4.类对象类型注解
class Student:
pass
stu: Student = Student()
- 除了使用
变量: 类型
这种注解语法外,还可以在注释中进行类型注解,语法:# type: 类型
。
num1 = 10 # type: int
num2 = 3.1415926 # type: float
num3 = True # type: bool
num4 = "hello" # type: str
- 像
num1 = 2 num2 = 3.14 list1 = [1,2,3]
num1、num2和list1就算不写注解,也明确知晓变量num1是整型,变量num2时浮点型,变量list1是列表类型,那么此时无需注解。一般,无法直接看出变量类型时会添加变量的类型注解,如下所示。
import random
import json
num1: int = random.randint(1, 10)
data: dict = {"0": "a", "1": "b"}
data_str: str = json.dumps(data)
data_dict: dict = json.loads(data_str)
注意:类型注解仅仅是提示性的,不是决定性的,就算你类型注解错误也不会报错。像下面的代码就不会报错。
num1: int = "hello"
num2: str = 22
- 函数(方法)注解
- 给函数(方法)形参类型进行注解,定义语法如下:
""""
def 函数方法名字(形参名1: 类型, 形参名2: 类型, ...):
pass
"""
def add(x: int, y: int):
return x+y
def func(data: list):
pass
- 给函数(方法)返回值进行注解,定义语法如下:
""""
def 函数方法名字(形参名1: 类型, 形参名2: 类型, ...) -> 返回值类型:
pass
"""
def add(x: int, y: int) -> int:
return x+y
def func(data: list) -> list:
pass
- Union联合类型注解定义语法如下:
""""
Union[数据类型, ..., 数据类型]
"""
# 变量Union联合类型注解
from typing import Union, List, Dict
my_list: List[Union[int, str]] = [1, 2, "hello"]
my_dict: Dict[str, Union[str, int]] = {"name": "Jack", "age": 16}
# 函数(方法)形参和返回值Union联合类型注解
def func(data: Union[int, str]) -> Union[int, str]:
pass