python 历险记(二)— python 的面向对象
前言
想学爬虫还是 python 专业啊,之前一直在用 java, 现在决定尝尝鲜,使用 python及爬虫框架来完成网络数据采集。
编程语言之间都是相通的,比如都需要模块化,引入其他文件来实现功能,使用列表等容器来处理数据,都要使用 json
或 xml
来解析和传输数据。
你会发现 通过类比的方式,带着问题去学习,会走的很快
而且我认为 代码示例的作用是异常强大的, 我会尽量使用代码示例的方式来展示,以满足同学快速学习的需要,也备后续查询。
在上篇文章 中,讨论了 python 3 中 string, 数据结构(Dict, List, 元组)等重要的主题。
今天我会继续探险,去征服 python 3 中的面向对象, let's go 让我们出发吧!
类和对象
刚接触 python 中的类和对象,我也和大多数小伙伴一样迷茫,不知道它和我所熟知的 java 都有什么异同点,为此我还提出了一大堆问题
- 如何创建和实例化类?
- 是否和 java 一样有访问修饰符,分为几个级别?
- 构造函数该怎么写?
- 怎么进行
class
的继承?
下面就一一来探索这些疑惑。
如何定义和实例化类?
在 java 中要创建一个类就必须要使用 class
关键字,要将类实例化,创建一个对象,可以使用 new
关键字。在 python 中是怎么样的呢?
先看代码
class Person():
"""这个叫做定义体,用来解释类的用途"""
print(Person) # <class '__main__.Person'>
# 由于是在程序顶层定义的,它的全名就是 '__main__.Person'
person = Person()
print(person) # <__main__.Person object at 0x000000000219A1D0>
要定义一个类(class) 只要将 class
关键字放在前面即可,类内部也可以像 java 似的定义变量和函数,这个后面再看。
实例化一个类,也就是创建一个对象,并不需要使用 new
关键字,只需将 class
当做函数来调用就可以啦,是不是比 java 简洁不少。
了解了定义和实例化类,还有两个问题:
- 要判断一个对象是不是某个类的实例该怎么做呢?用
isinstance
print(isinstance (person, Person)) # True
- 判断对象是什么类型,该怎么做? 用
type
print(type(person)) # <class '__main__.Person'>
如何定义和使用属性?
上面的代码,光有一个空对象是干不了任何事情的,我们也要像 java 一样为其定义属性和方法。
java 是不能动态定义一个变量的,必须要把它放在 class
中预先定义好才可以用;而在 python 中这却不是问题,不信看代码~
class Person():
"""这个叫做定义体,用来解释类的用途"""
person = Person()
person.age = 5
print(person.age)
虽然在对 Person class
定义时没有任何属性的声明,但在实例化后依然可以添加 age 属性,而且也并没有看到如 java 中 public
, private
等访问修饰符的存在, python 中有没有这些概念呢?还真有,变量默认就是 public
公有的,如果 在变量名前添加两个下划线,这样就会认为是 private
私有变量了,直接访问是不可以的。看下面代码
class Person():
"""这个叫做定义体,用来解释类的用途"""
gender = 'male'
__age = 5
person = Person()
print(person.gender) # male
print(person.__age) # AttributeError: 'Person' object has no attribute '__age'
上面代码中,在打印 __age
时会报错,告知没有找到这个属性,其实就是 由于使用双下划线做前缀使其变成私有变量了。
那 函数名是不是也有私有函数,是不是也在前面加双下划线呢 ?猜的没错,这个我们后面再了解。
既然 python 对象的属性操作如此灵活,可以动态添加,那用户在使用时就可能会碰到一些异常。
比较典型的就是,访问一个不存在的属性,会抛出 AttributeError
。对这种情况有两种方式可以处理:
- 预先使用内置函数
hasattr
判定对象是否拥有该属性(记住,只对公有变量有效哦~) - 使用
try
语句处理
class Person():
"""这个叫做定义体,用来解释类的用途"""
gender = 'male'
__age = 5
person = Person()
print(hasattr(person, 'gender')) # True
print(hasattr(person, 'name')) # False
print(hasattr(person, '__age')) # False
try:
name = person.name
except AttributeError:
name = 'unknown'
print(name)
什么是方法?
什么是方法?方法和函数有什么区别?在上一篇我就介绍了好多 string
的方法,为什么叫做方法,而不叫做 string
的函数呢?一起来了解下~
- 函数是指可以执行某种运算,可以通过名字来调用的一段语句的组合
- 方法是特殊的函数,是跟一个对象或类相关联的
- 方法是书写在类的定义之中,明确表示和类之间关系的
- 在调用方法时,前面需要加上类名(函数调用语法)或者实例化的对象名(方法调用语法)
静态方法和普通方法
调用方法分为两种形式,分别是
- 函数调用语法(静态方法)
- 普通方法(动态方法)
先看第一种函数调用语法,这其实和 java 中的静态方法是一样的,只是前面不需要 static
关键字。
class Person:
def print_person(person):
print('name: %s, gender%s, age:%d' % (person.name, person.gender, person.age))
person = Person()
person.name = 'Tom'
person.gender = 'male'
person.age = 10
Person.print_person(person)
函数调用语法的方式其实和单纯的函数调用,区别是不大的,因为方法前面的 class
对它没起什么作用,活动主体 依然是方法。
再看另外一种 方法调用语法,而这次的主体则是调用该方法的 对象
class Person:
__name = 'Tom'
__gender = 'male'
__age = 10
def print_person(self):
print('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))
person = Person()
person.print_person()
细心的同学会发现这里在定义方法时形参为 self
, 而在调用方法时却没有任何入参。
那这个 self
是什么呢?
如果类比 java 的话,这个 self
可以看作是 this
, 其实就是对当前对象的引用。 java 中定义方法时不必将其做入参。而这个 self
在 python 中则是必须声明的,在调用的时候则不必传入。
注意,这个 self
可不是关键字哦,只要占据方法形参的头把交椅,你可以用任何名字。
构造函数该怎么写?
在 java 中构造函数是与类同名的,而且会伴随着实例化的动作而执行。在 python 中呢?
python 中的构造函数叫做 init
方法,全名是 __init__
具体看下面代码
class Person():
__gender = 'male'
__age = '0'
def __init__(self, gender='male', age=0):
self.__gender = gender
self.__age = age
person1 = Person('female', 10)
person2 = Person()
person2 = Person('male')
作为实例方法, self
入参当然少不了,其他参数就按照顺序排开,若参数不够,就用默认值来代替。
str 方法怎么写?
在java 中, 我们一般会覆盖 toString() 方法来返回对象中包含的值得关注的信息。 python 中也有这样一个方法,叫做 __str__
。
class Person:
__name = 'Tom'
__gender = 'male'
__age = 10
def __str__(self):
return ('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))
person = Person()
print(person)
作为最佳实践的一部分,建议你在每个创建的类中都覆盖这个方法。
多态是什么?
还记得面向对象的几个特征吗?封装性,继承性,多态性。嗯,来聊下 python 对多态的实现。
什么叫做多态?
在 java 中,如果在一个 class 中有多个函数,函数名相同而参数不同(个数或类型不同),就叫做多态。
而在 python 中, 多态的概念则更进一步,对于同一个函数,如果能够处理多种类型的数据,也叫做多态。
tuple_list = [(1, 2,), (2, 3,), (4, 5)]
list = [1, 2, 3, 4]
dict1 = {
'a' : 1,
'b' : 2
}
def printSomething(something):
for i in something:
print(i)
print(tuple_list)
print(dict1)
print(list)
printSomething
一个函数可以同时打印元组,列表以及字典,充分发挥代码复用的功效,是不是很方便。
继承性和 java 是一样的吗?
聊完了多态,再来看看面向对象的另一个特征:继承性。
什么是继承?继承就是定义好了一个类 A(父类);再定义一个新类 B(子类),类 B 拥有类 A 的方法和属性,并且又定义了新的属性和方法。类 A 称为父类,类 B 称为子类。
java 中定义两个类的继承关系,使用 extends
关键字实现,在 python 中呢?
class Father:
""" 这是一个父类 """
__age = 45
class Son(Father):
""" 这是一个子类 """
python 中不需要加关键字来说明继承关系,只需要将父类的名称放在括号中就可以了,看起来要比 java
简洁一些。
父类和子类的初始化函数调用
前面讲过, python class 中可以定义自己的初始化函数,在实例化的时会被调用。那如果父类和子类都有初始化函数或者父类有而子类没有,那初始化函数该如何执行呢?这里分为三种情况来说明,先来看第一种。
第一种情况,
父类有 init 而子类没有, 这时父类的初始化函数会被默认调用
class Father():
""" 这是一个父类 """
def __init__(self, age):
print("Father's init function invoke")
self.__age = age
class Son(Father):
""" 这是一个子类 """
son = Son(5)
这里要注意,父类中需要的 age
参数一定要传进去哦,要不然会报错的。
第二种情况
父类,子类都有 init ,而子类没有显式调用父类的 init 方法时,父类初始化函数是不会被调用的
class Father():
""" 这是一个父类 """
def __init__(self, age):
print("Father's init function invoke")
self.__age = age
def get_age(self):
return self.__age
class Son(Father):
""" 这是一个子类 """
def __init__(self, age):
print("Son's init function invoke")
self.__age = age
son = Son(5) # Son's init function invoke
print(son.get_age()) # AttributeError: 'Son' object has no attribute '_Father__age'
细心的同学会发现,代码中的最后一句报错了,表示 Son 对象没有 Father
类的 __age
变量。这是因为
- 父类的初始化函数没有执行,父类的
__age
变量则没有初始化 get_age
函数是被子类从父类继承来的,返回的是父类的__age
变量
那我要是想解决这个错误,该怎么做呢?有两种方法
- 在子类
Son
的初始化函数中显式调用父类Father
的初始化函数 - 在子类
Son
中重新定义个get_age
方法,这样就会覆盖父类的同名方法,返回的是子类的_age
变量
第二种方法就不贴代码了,感兴趣的话可以试试。重点来看第一种方法,这就引出了第 3 种情况。
第三种情况
子类在自己定义的 init 方法中,显式调用父类的 init 方法,父类和子类的属性都会被初始化
class Father():
""" 这是一个父类 """
def __init__(self, age):
print("Father's init function invoke")
self.__age = age
def get_age(self):
return self.__age
class Son(Father):
""" 这是一个子类 """
def __init__(self, age):
print("Son's init function invoke")
self.__age = age
super(Son, self).__init__(age + 25)
def get_age(self):
return self.__age
def get_father_age(self):
return super(Son, self).get_age()
son = Son(5)
# Son's init function invoke
# Father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5
看到代码中是怎么调用父类的初始化函数吗? 对,用的是 super
。
java 中也有 super
关键字,表示对父类的指代, python 的 super
是怎么用的,原理是什么?我们来看下。
super 有哪些用法?
下面说明的只针对 python 单继承的情况,多继承这里暂不涉及,有兴趣的同学可以自行充电。
在单继承中,super
也可以看做对其父类的指代,它的使用场合就是用来调用父类的方法:
- 调用父类的
__init__
方法 - 实现了和父类相同的功能,还需要调用父类的方法
它的写法是 super(Son,self).xxx
, 当然也可以写成 super()
这种简写的形式。
来看代码
class Father():
""" 这是一个父类 """
def __init__(self, age):
print("Father's init function invoke")
self.__age = age
def get_age(self):
return self.__age
class Son(Father):
""" 这是一个子类 """
def __init__(self, age):
print("Son's init function invoke")
self.__age = age
super(Son, self).__init__(age + 25)
def get_age(self):
return self.__age
def get_father_age(self):
return super(Son, self).get_age()
son = Son(5)
# Son's init function invoke
# Father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5
通过代码来窥探下它的执行原理,以 super(Son, self).get_age()
为例
self
是Son
的一个实例,super
把self
转化为父类Father
的一个实例对象- 因为
self
经过了转化, 那它得到的__age
, 也是父类初始化时得到的__age
结语
看到这里,不知您对 python 的面向对象有了多少理解,反正我是理解了不少,哈哈。如果有疑问和建议,欢迎留言交流,我将仔细阅读,认真回复。
下篇文章中会涉及到 文件, json xml 处理 处理等主题,敬请期待~
python 历险记(二)— python 的面向对象的更多相关文章
- Python学习二|Python的一些疑问
最近写了一点Python代码,作为一个java程序员,面对Python这么便捷的语言不禁有点激动.不过呢,有时候也会遇到一些无法理解的东西. 例如: er = [[1,2,3], [4,5,6], [ ...
- 深入理解python之二——python列表和元组
从一开始学习python的时候,很多人就听到的是元组和列表差不多,区别就是元组不可以改变,列表可以改变. 从数据结构来说,这两者都应当属于数组,元组属于静态的数组,而列表属于动态数组.稍后再内存的分配 ...
- 【转】python 历险记(四)— python 中常用的 json 操作
[转]python 历险记(四)— python 中常用的 json 操作 目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编 ...
- python 历险记(四)— python 中常用的 json 操作
目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编码和解码? 常用的 json 操作有哪些? json 操作需要什么库? 如何 ...
- python 历险记(三)— python 的常用文件操作
目录 前言 文件 什么是文件? 如何在 python 中打开文件? python 文件对象有哪些属性? 如何读文件? read() readline() 如何写文件? 如何操作文件和目录? 强大的 o ...
- python 历险记(六)— python 对正则表达式的使用(上篇)
目录 引言 什么是正则表达式? 正则表达式有什么用? 正则表达式的语法及使用实例 正则表达式语法有哪些? 这些正则到底该怎么用? 小结 参考文档 系列文章列表 引言 刚接触正则表达式,我也曾被它们天书 ...
- python 历险记(五)— python 中的模块
目录 前言 基础 模块化程序设计 模块化有哪些好处? 什么是 python 中的模块? 引入模块有几种方式? 模块的查找顺序 模块中包含执行语句的情况 用 dir() 函数来窥探模块 python 的 ...
- Python全栈开发【面向对象进阶】
Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...
- Python全栈开发【面向对象】
Python全栈开发[面向对象] 本节内容: 三大编程范式 面向对象设计与面向对象编程 类和对象 静态属性.类方法.静态方法 类组合 继承 多态 封装 三大编程范式 三大编程范式: 1.面向过程编程 ...
- 简学Python第六章__class面向对象编程与异常处理
Python第六章__class面向对象编程与异常处理 欢迎加入Linux_Python学习群 群号:478616847 目录: 面向对象的程序设计 类和对象 封装 继承与派生 多态与多态性 特性p ...
随机推荐
- 自学Linux Shell8.2-linux逻辑卷LVM管理
点击返回 自学Linux命令行与Shell脚本之路 8.2-linux逻辑卷LVM管理 Linux逻辑卷管理器软件包用来通过将另外一个硬盘上的分区加入已有文件系统,动态地添加存储空间. 1. 逻辑卷L ...
- RabbitMQ安装详解
# RabbitMQ 消息中间件 一.安装:#安装epel源[EPEL (Extra Packages for Enterprise Linux,企业版Linux的额外软件包)rpm -Uvh htt ...
- bzoj1492/luogu4027 货币兑换 (斜率优化+cdq分治)
设f[i]是第i天能获得的最大钱数,那么 f[i]=max{在第j天用f[j]的钱买,然后在第i天卖得到的钱,f[i-1]} 然后解一解方程什么的,设$x[j]=\frac{F[j]}{A[j]*Ra ...
- Spring MVC 起步
跟踪Spring MVC的请求 在请求离开浏览器时①,会带有用户所请求内容的信息,至少会包含请求的URL. 请求旅程的第一站是Spring的DispatcherServlet.与大多数基于Java的W ...
- A1091. Acute Stroke
One important factor to identify acute stroke (急性脑卒中) is the volume of the stroke core. Given the re ...
- mod(%)之规律(除数与被除数的正负分析)
首先注意“-9 % 4”,根据运算符优先级,负号运算符优先级大于余数(取模),所以执行的是“(-9) % 4”. 其次 % = mod ,只是在不同地方表示方法不同而已. 被除数无论是正数和负数结果都 ...
- Codeforces Round #510 (Div. 2)(C)
传送门:Problem C https://www.cnblogs.com/violet-acmer/p/9682082.html 题意: 给你n个数,定义有两种操作 ① 1 i j : (i != ...
- k8s 常用命令汇集
通过yaml文件创建: kubectl create -f xxx.yaml (不建议使用,无法更新,必须先delete) kubectl apply -f xxx.yaml (创建+更新,可以重复使 ...
- P4147 玉蟾宫
P4147 玉蟾宫 给定一个 \(N * M\) 的矩阵 求最大的全为 \(F\) 的子矩阵 Solution 悬线法 限制条件为转移来的和现在的都为 \(F\) Code #include<i ...
- Linux命令之cp
cp命令 用处:复制文件到当前目录下 用法:cp +要复制的文件的路径 + 复制后的文件名字 示例: (我这里有一个m1文件,内容是qwer,我想把它复制一份成为m2)