OOP编程思想

  • 面向对象编程是在面向过程编程的基础上发展来的,它具有更强的灵活性和扩展性。面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。我们已经接触了封装,比如说,将数据放进列表和字典中中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口。
  • 面向对象编程,也是一种封装的思想,不过显然比以上两种封装更先进,它可以更好地模拟真实世界里的事物,并把描述特征的数据和代码块(函数)封装到一起。

类与实例对象

  • 类是人们抽象出来的一个概念,所有拥有相同属性和功能的事物称为一个类;而拥有相同属性和功能的具体事物则成为这个类的实例对象。

声明类和实例化对象

  • 面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
1
2
3
4
5
class Person(object):
pass

p1=Person()
p2=Person()

每一个人的实例对象都应该有自己的属性,比如姓名和年龄,实例变量的赋值如下:

1
2
3
4
5
6
7
8
9
# 实例变量的增删改查
p1.name="alvin"
p1.age=18

p2.name="zhangsan"
p2.age=22

print(p1.gender)
del p2.age

对象的属性初始化

  • 在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。

    __init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。

    __init__() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割,从而完成初始化的工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 定义Person类
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
print(id(self))


# 实例化Person类的实例对象: 类实例对象=类名(实例化参数)
zhangsan=Person("zhangsan",18)
lisi=Person("lisi",22)

print(id(zhangsan))

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

实例方法

  • 实例方法或者叫对象方法,指的是我们在类中定义的普通方法。只有实例化对象之后才可以使用的方法,该方法的第一个形参接收的一定是对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
class Person(object):

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

def print_info(self):
print("姓名:%s,年龄:%s"%(self.name,self.age))


yuan = Person("zhangsan",18)
yuan.print_info()

一切皆对象

  • 在python语言中,一切皆对象!

我们之前学习过的字符串,列表,字典等等数据都是一个个的类,我们用的所有数据都是一个个具体的实例对象。

区别就是,那些类是在解释器级别注册好的,而现在我们学习的是自定义类,但语法使用都是相同的。所以,我们自定义的类实例对象也可以和其他数据对象一样可以进行传参、赋值等操作。

类对象和类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person:
# 类属性
legs_num = 2
has_emotion = True

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

def play_fire(self):

print("%s玩火"%self.name)


# 实例对象和类对象可以获取类属性,但是只有类对象才能修改类属性
zhangsan=Person("zhangsan",18)
lisi=Person("lisi",22)


print(zhangan.legs_num)
print(zhangsan.name)
# Person:一个类对象
print(Person.legs_num)

静态方法和类方法

  • 静态方法

定义:使用装饰器@staticmethod。参数随意,没有selfcls参数,但是方法体中不能使用类或实例的任何属性和方法

调用:类对象或实例对象都可以调用。

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

@staticmethod
def add(x,y):
return x+y

@staticmethod
def mul(x,y):
return x*y

cal=Cal()
print(cal.add(1, 4))
  • 类方法

定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为cls,通过它来传递类的属性和方法(不能传实例的属性和方法);

调用:类对象或实例对象都可以调用。

1
2
3
4
5
6
7
8
9
10
11
class Student:

# 类属性
cls_number = 68

@classmethod
def add_cls_number(cls):
cls.cls_number+=1
print(cls.cls_number)

Student.add_cls_number()

面向对象之继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类派生类,被继承的类称为基类父类超类

1
2
class 派生类名(基类名)
...

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。

实际上继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。继承定义了类如何相互关联,共享特性。对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。

重点

子类拥有父类非私有化的属性和方法。

子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

子类可以用自己的方式实现父类的方法。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 无继承方式

class Dog:

def eat(self):
print("eating...")

def sleep(self):
print("sleep...")

def swimming(self):
print("swimming...")

class Cat:

def eat(self):
print("eating...")

def sleep(self):
print("sleep...")

def climb_tree(self):
print("climb_tree...")


# 继承方式

class Animal:

def eat(self):
print("eating...")

def sleep(self):
print("sleep...")


class Dog(Animal):

def swimming(self):
print("toshetou...")

class Cat(Animal):

def climb_tree(self):
print("climb_tree...")


alex = Dog()
alex.run()

重写父类方法和调用父类方法

  • 当父类的方法实现不能满足子类需求时,可以对方法进行重写
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
32
33
34
35
36
class Animal(object):
def eat(self):
print("吃")

def run(self):
print("跑")

def sleep(self):
print("睡")


class Dog(Animal):

def bark(self):
print("汪汪叫")


class XiaoTianQuan(Dog):

def fly(self):
print("会飞")

def bark(self):
# 1. 针对子类特有的需求,编写代码
print("天籁之音")

# 2. 使用super(). 调用原本在父类中封装的方法(避免父类换名字,可用super方法)
super().bark()

# 3. 增加其他子类的代码
print("OK啦")

# 创建一个哮天犬对象
xtq = XiaoTianQuan()

xtq.bark()

下面这段代码定义了两个类,Base 和 Son,并创建了 Son 的实例 s。

在这个例子中,Son 继承了 Base 类,并重写了其中的方法 func()。在初始化 Son 的实例 s 时,会调用父类 Base 的构造函数 init(),然后调用 Son 类中的 func() 方法,因为在 Son 类中重写了 func() 方法,所以会打印出 “in son”。

因此,运行该代码会输出 “in son”。

1
2
3
4
5
6
7
8
9
10
11
class Base():
def __init__(self):
self.func()
def func(self):
print('in base')

class Son(Base):
def func(self):
print('in son')

s = Son()

多重继承

  • 如果在继承元组中列了一个以上的类,那么它就被称作”多重继承” 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
1
2
class SubClassName (ParentClass1[, ParentClass2, ...]):
...

如果在继承元组中列了一个以上的类,那么它就被称作”多重继承” 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

1
2
class SubClassName (ParentClass1[, ParentClass2, ...]):
...

多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal:

def eat(self):
print("eating...")

def sleep(self):
print("sleep...")

class Eagle(Animal):

def fly(self):
print("fly...")

class Bat(Animal):

def fly(self):
print("fly...")

有同学肯定想那就放到父类Animal中,可是那样的话其他不会飞的动物还怎么继承Animal呢?所以,这时候多重继承就发挥功能了:

1
2
3
4
5
6
7
8
9
class Fly:
def fly(self):
print("fly...")

class Eagle(Animal,Fly):
pass

class Bat(Animal,Fly):
pass

type 和isinstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal:

def eat(self):
print("eating...")

def sleep(self):
print("sleep...")


class Dog(Animal):
def swim(self):
print("swimming...")

alex = Dog()
mjj = Dog()

print(isinstance(alex,Dog))
print(isinstance(alex,Animal))
print(type(alex))

dir()方法和__dict__属性

dir(obj)可以获得对象的所有属性(包含方法)列表, 而obj.__dict__对象的自定义属性字典

注意事项:

  1. dir(obj)获取的属性列表中,方法也认为属性的一种。返回的是list
  2. obj.__dict__只能获取自己自定义的属性,系统内置属性无法获取。返回是dict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student:

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

def test(self):
pass


yuan = Student("yuan", 100)
print("获取所有的属性列表")
print(dir(yuan))

print("获取自定义属性字段")
print(yuan.__dict__)

其中,类似__xx__的属性和方法都是有特殊用途的。如果调用len()函数视图获取一个对象的长度,其实在len()函数内部会自动去调用该对象的__len__()方法

异常机制

什么叫做**”异常”**?

  • 在程序运行过程中,总会遇到各种各样的问题和错误。
  • 有些错误是我们编写代码时自己造成的:比如语法错误、调用错误,甚至逻辑错误。
  • 还有一些错误,则是不可预料的错误,但是完全有可能发生的:比如文件不存在、磁盘空间不足、网络堵塞、系统错误等等。

这些导致程序在运行过程中出现异常中断和退出的错误,我们统称为异常。大多数的异常都不会被程序处理,而是以错误信息的形式展现出来。

异常的分类:

  • 异常有很多种类型,Python内置了几十种常见的异常,无需特别导入,直接就可使用。
  • 需要注意的是,所有的异常都是异常类,首字母是大写的!

异常的危害:

  • 如果程序中一旦出现了异常的语句代码,则该异常就会立即中断程序的运行,因此:为了保证程序的正常运行,提高程序健壮性和可用性。我们应当尽量考虑全面,将可能出现的异常进行处理,而不是留在那里,任由其发生。

基本语法

异常的基本结构:try except

  • 通用异常
1
2
3
4
try:
pass # 正常执行语句
except Exception as ex:
pass # 异常处理语句
  • 通用异常
1
2
3
4
try:
pass # 正常执行语句
except <异常名>:
pass # 异常处理语句
  • 捕获多个异常
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
# 捕获多个异常有两种方式,第一种是一个except同时处理多个异常,不区分优先级:
try:
pass # 正常执行语句

except (<异常名1>, <异常名2>, ...):

pass # 异常处理语句

# 第二种是区分优先级的:
try:
pass # 正常执行语句
except <异常名1>:
pass # 异常处理语句1
except <异常名2>:
pass # 异常处理语句2
except <异常名3>:
pass # 异常处理语句3

# 异常嵌套
try:
try:
with open("abc") as f:
pass
except NameError as e:
print(e)
except OSError as e:
print("OSError:",e.strerror)

说明:

  • 首先,执行try子句(在关键字try和关键字except之间的语句)
  • 如果没有异常发生,忽略except子句,try子句执行后结束。
  • 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常那么对应的except子句将被执行。
  • 在Python的异常中,有一个通用异常:Exception,它可以捕获任意异常

finally

1
2
3
4
5
6
try:
pass # 正常执行语句
except Exception as e:
pass # 异常处理语句
finally:
pass # 无论是否发生异常一定要执行的语句,比如关闭文件,数据库或者socket

raise语句

很多时候,我们需要主动抛出一个异常。Python内置了一个关键字raise,可以主动触发异常。

raise可以抛出自定义异常,我们已将在前面看到了python内置的一些常见的异常类型。大多数情况下,内置异常已经够用了。但是有时候你还是需要自定义一些异常:自定义异常应该继承Exception类,直接继承或者间接继承都可以,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 用户自定义异常类型(j
class TooLongExceptin(Exception):

def __init__(self, len):
self.len = len

def __str__(self):
return "输入姓名长度是" + str(self.len) + ",超过长度了"
try:
name = input("enter your name:")
if len(name) > 5:
raise TooLongExceptin(len(name))
else:
print(name)

except TooLongExceptin as error: # 这里异常类型是用户自定义的
print("打印异常信息:", error)