Python之面向对象
11.1、OOP编程思想
-
面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程。
-
面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。其实,在前面章节的学习中,我们已经接触了封装,比如说,将数据放进列表和字典中中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。
-
代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的。
-
本节所讲的面向对象编程,也是一种封装的思想,不过显然比以上两种封装更先进,它可以更好地模拟真实世界里的事物,并把描述特征的数据和代码块(函数)封装到一起。
11.2、类与实例对象
11.2.1、类和对象的概念
类是人们抽象出来的一个概念,所有拥有相同属性和功能的事物称为一个类;而拥有相同属性和功能的具体事物则成为这个类的实例对象。
11.2.2、声明类和实例化对象
-
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Person类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
-
案例演示:
class Person(object):
pass
p1=Person()
p2=Person()
- 每一个人的实例对象都应该有自己的属性,比如姓名和年龄,实例变量的赋值如下:
# 实例变量的增删改查
p1.name="meng"
p1.age=18
p2.name="dream"
p2.age=22
print(p1.gender)
del p2.age
11.2.3、对象的属性初始化
-
在创建类时,我们可以手动添加一个
__init__()
方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。 -
__init__()
方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。 -
在
__init__()
构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割,从而完成初始化的工作。
# 定义Person类
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
print(id(self))
# 实例化Person类的实例对象: 类实例对象=类名(实例化参数)
alvin=Person("meng",18)
yuan=Person("dream",22)
print(id(meng))
注意到
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
11.2.4、实例方法
- 实例方法或者叫对象方法,指的是我们在类中定义的普通方法。只有实例化对象之后才可以使用的方法,该方法的第一个形参接收的一定是对象本身。
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("dream",18)
yuan.print_info()
11.2.5、一切皆对象
-
在python语言中,一切皆对象!
-
我们之前学习过的字符串,列表,字典等等数据都是一个个的类,我们用的所有数据都是一个个具体的实例对象。
-
区别就是,那些类是在解释器级别注册好的,而现在我们学习的是自定义类,但语法使用都是相同的。所以,我们自定义的类实例对象也可以和其他数据对象一样可以进行传参、赋值等操作。
class Weapon:
def __init__(self, name, av, color):
self.name = name
self.av = av
self.color = color
jiguangqiang = Weapon("激光枪", 100, "red")
class Hero:
def __init__(self, name, sex, hp, ce, weapon, level=2, exp=2000, money=10000): # 类必不可少的方法,用于实例化
self.name = name # 英雄的名字
self.sex = sex # 英雄的性别
self.hp = hp # 英雄生命值
self.level = level # 英雄的等级
self.exp = exp # 英雄的经验值
self.money = money # 英雄的金币
self.weapon = weapon # 英雄的武器
yuan = Hero("dream", "male", 100, 80, jiguangqiang)
print(yuan.weapon.color)
11.2.6、类对象和类属性
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)
# 实例对象和类对象可以获取类属性,但是只有类对象才能修改类属性
yuan = Person("dream", 18)
alvin = Person("meng", 18)
print(yuan.legs_num)
print(yuan.name)
# Person:一个类对象
print(Person.legs_num)
11.2.7、静态方法和类方法
静态方法
-
定义:使用装饰器
@staticmethod
。参数随意,没有self
和cls
参数,但是方法体中不能使用类或实例的任何属性和方法; -
调用:类对象或实例对象都可以调用。
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))
or
print(Cal.add(3,4))
类方法
-
定义:使用装饰器
@classmethod
。第一个参数必须是当前类对象,该参数名一般约定为cls
,通过它来传递类的属性和方法(不能传实例的属性和方法); -
调用:类对象或实例对象都可以调用。
class Student:
# 类属性
cls_number = 68
@classmethod
def add_cls_number(cls):
cls.cls_number+=1
print(cls.cls_number)
Student.add_cls_number()
思考:
类对象.实例方法会怎么样?
类方法的意义是什么,在实例方法中使用类对象变量不可以吗?
11.3、面向对象之继承
- 面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
class 派生类名(基类名)
...
11.3.1、继承的基本使用
-
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
-
实际上继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。例如猫有抓老鼠、爬树等其他动物没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为“向上转型”。
-
诚然,继承定义了类如何相互关联,共享特性。对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
-
同时在使用继承时需要记住三句话:
1、子类拥有父类非私有化的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。(下面会介绍)。
# 无继承方式
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()
11.3.2、 重写父类方法和调用父类方法
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def sleep(self):
print("基类sleep...")
class Emp(Person):
# def __init__(self,name,age,dep):
# self.name = name
# self.age = age
# self.dep = dep
def __init__(self, name, age, dep):
# Person.__init__(self,name,age)
super().__init__(name,age)
self.dep = dep
def sleep(self):
if "不在公司":
# print("子类sleep...")
# 调用父类方法
# 方式1 :父类对象调用 父类对象.方法(self,其他参数)
# Person.sleep(self)
# 方式2: super关键字 super(子类对象,self).方法(参数)or super().方法(参数)
super().sleep()
yuan = Emp("yuan",18,"教学部")
yuan.sleep()
print(yuan.dep)
# 测试题:
class Base:
def __init__(self):
self.func()
def func(self):
print('in base')
class Son(Base):
def func(self):
print('in son')
s = Son()
11.3.3、 多重继承
- 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class SubClassName (ParentClass1[, ParentClass2, ...]):
...
- 多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。
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呢?所以,这时候多重继承就发挥功能了:
class Fly:
def fly(self):
print("fly...")
class Eagle(Animal,Fly):
pass
class Bat(Animal,Fly):
pass
11.3.4、 type 和isinstance
方法
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))
11.3.5、dir()
方法和__dict__
属性
-
dir(obj)
可以获得对象的所有属性(包含方法)列表, 而obj.__dict__
对象的自定义属性字典 -
注意事项:
-
dir(obj)
获取的属性列表中,方法也认为属性的一种。返回的是list -
obj.__dict__
只能获取自己自定义的属性,系统内置属性无法获取。返回是dict
-
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__()
方法