python 面向对象编程
1. 面向对象常用术语
- Python是一门面向对象编程语言。
- 面向对象编程是一种程序设计思想,把对象作为程序的基本单元,一个对象包含数据和操作数据的函数。
- 在Python中,所有数据类型都被视为对象,也可以自定义对象。自定义对象数据类型就是面向对象中的类(Class)的概念。
- 类:用来描述具有相同属性和方法的对象的集合。类定义了集合中每个对象共有的属性和方法。
- 对象:对象是类的实例。
- 类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中,且在方法之外。类变量通常不作为实例变量使用。类变量也称作属性。
- 实例变量:定义在方法中的变量只作用于当前实例的类。
- 数据成员:类变量或实例变量用于处理类及其实例对象的相关数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,就可以对其进行改写,这个过程称为方法的覆盖(Override),也称为方法的重写。
- 实例化(Instance):创建一个类的实例、类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
- 面向对象的三大特性:
- 多态(Polymorphism):对不同类的对象使用同样的操作。
- 封装(Encapsulation):对外部世界隐藏对象的工作细节。
- 继承(Inheritance): 即一个派生类(derivedclass)继承基类(base class)的字段和方法。
2. 类
2.1 类的定义和对象实例化
类用关键字 class 进行定义,定义一个类,通常要定义属性和方法。类在定义后,一般通过类实例化对象,然后通过对象访问类的属性和方法。
class Dog(): # 定义了一个Dog类
name = "小花" # 类的属性
def cry(self): # 类中定义的方法
return "汪汪汪"
dog = Dog() # 实例化对象
# 通过对象调用类的属性和方法。
print(f"我的小狗名字为:{dog.name},它喜欢{dog.cry()}的叫")
我的小狗名字为:小花,它喜欢汪汪汪的叫
2.2 类的构造方法
在Python中,_init_()方法是一个特殊方法,在对象实例化时会被调用。_init_()的意思是初始化,是initialization的简写。这个方法也叫构造方法。在定义类时,若不显式地定义一个_init_()方法,则程序默认调用一个无参的_init_()方法。
class Dog():
name = "小花"
def __int__(self): # 不显示定义,默认调用该构造方法
pass
dog = Dog()
dog.name
'小花'
class Dog():
def __init__(self,name):
self.name = name
print(f"构造方法带一个参数:{self.name}")
dog = Dog("小花")
构造方法带一个参数:小花
class Dog():
def __init__(self,name,age):
self.name = name
self.age = age
print(f"构造方法带两个参数:{self.name}-{self.age}")
dog = Dog("小花","2岁")
构造方法带两个参数:小花-2岁
2.3 类的访问权限
类的访问权限在类内部有属性和方法,外部代码可以通过直接调用实例变量的方法操作数据,这样就隐藏了内部的复杂逻辑。
class Dog():
def __init__(self,name,age):
self.name = name
self.age = age
def show_info(self):
print(f"姓名:{self.name}\t 年龄:{self.age}")
dog = Dog("小花","2岁")
print(f"修改前的年龄:{dog.age}")
dog.show_info()
dog.age = "5岁"
dog.show_info()
修改前的年龄:2岁
姓名:小花 年龄:2岁
姓名:小花 年龄:5岁
在类中定义的非构造方法可以调用类中构造方法实例变量的属性,调用的方式为self.实例变量属性名。可以在类的外部修改类的内部属性。如果要让内部属性不被外部访问,可以在属性名称前加两个下画线__,使实例变量变为就会变成私有变量(private),如果外部代码要获取类中的name和score,可以为类增加get_attrs和set_attrs方法,获取和修改类中的私有变量。
class Dog():
def __init__(self,name,age):
self.__name = name
self.__age = age
def set_name(self,name):
self.__name = name
def set_age(self,age):
self.__age = age
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def show_info(self):
print(f"姓名:{self.get_name()}\t 年龄:{self.get_age()}")
dog = Dog("小花","2岁")
dog.show_info()
dog.set_age("5岁")
dog.show_info()
姓名:小花 年龄:2岁
姓名:小花 年龄:5岁
3. 继承
继承可以理解成类之间类型和子类型的关系。当我们定义一个class时,可以从某个现有的class继承,定义的新class称为子类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。如上图:Animal和Plant类都继承object类,Dog和Cat类继承Animal类,Tree和Flower类继承Plant类。
在Python中,继承有以下特点:
- 在继承中,基类的构造方法(_init_()方法)不会被自动调用,需要在子类的构造方法中专门调用。
- 在调用基类的方法时需要加上基类的类名前缀,并带上self参数变量。区别于在类中调用普通函数时不需要带self参数。
- 在Python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
注意: 继承语法class子类名(基类名)时,//基类名写在括号里,基本类是在定义类时,在元组中指明的。
class Animal(object):
def cry(self):
print("动物在叫。")
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.cry() # 子类直接继承父类的公有方法,如果cry()方法为__cry()私有方法,则不能继承。
cat = Cat()
cat.cry()
动物在叫。
动物在叫。
class Animal(object):
def __cry(self):
print("动物在叫。")
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.__cry() #如果cry()方法为__cry()私有方法,则不能继承。
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-20-71a8dd49d0b3> in <module>()
9
10 dog = Dog()
---> 11 dog.__cry() #如果cry()方法为__cry()私有方法,则不能继承。
AttributeError: 'Dog' object has no attribute '__cry'
4.多态
继承可以帮助我们重复使用代码。但对于上面继承中的示例,无论是Dog还是Cat,调用父类的cry()方法时显示的都是“动物在叫。”,如果想让结果显示为“汪汪汪”和“喵喵喵”则需要对从父类继承的方法进行重写。
class Animal(object):
def cry(self):
print("动物在叫。")
class Dog(Animal):
def cry(self): # 子类对父类的方法进行重写
print("汪汪汪。")
def eat(self): #子类新增的方法
print("小狗只吃狗粮。")
class Cat(Animal):
def cry(self): # 子类对父类的方法进行重写
print("喵喵喵。")
def eat(self):
print("小猫只吃猫粮。")
animal = Animal()
animal.cry()
dog = Dog()
dog.cry()
dog.eat()
cat = Cat()
cat.cry()
cat.eat()
动物在叫。
汪汪汪。
小狗只吃狗粮。
喵喵喵。
小猫只吃猫粮。
# 为了理解多态,请观察下面的结果。
print(f"dog 的类型为Dog吗:{isinstance(dog,Dog)}")
print(f"dog 的类型为Animal吗:{isinstance(dog,Animal)}")
dog 的类型为Dog吗:True
dog 的类型为Animal吗:True
因为Dog是从Animal继承下来的,当我们创建Dog的实例dog时,我们认为dog的数据类型是Dog,但dog同时也是Animal,Dog本来就是Animal的一种。实际上,任何依赖Animal作为参数的函数或方法都可以不加修改地正常运行,原因就在于多态。如下面的例子,在定义cry_twice()方法中,传入的参数为animal类型,想要具体到子类,不需要在改为dog或者cat,只需要传入相应类型的子类对象即可。
def cry_twice(animal):
animal.cry()
animal.cry()
cry_twice(Animal())
cry_twice(Dog())
cry_twice(Cat())
动物在叫。
动物在叫。
汪汪汪。
汪汪汪。
喵喵喵。
喵喵喵。
5. 封装
封装并不等同于多态。多态可以让用户对不知道类(或对象类型)的对象进行方法调用,而封装可以不用关心对象是如何构建的,直接使用即可。
class Dog():
def __init__(self,name,age):
self.__name = name
self.__age = age
def set_name(self,name):
self.__name = name
def set_age(self,age):
self.__age = age
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def show_info(self):
print(f"姓名:{self.get_name()}\t 年龄:{self.get_age()}")
dog = Dog("小花","2岁")
dog.show_info()
dog.set_age("5岁")
dog.show_info()
姓名:小花 年龄:2岁
姓名:小花 年龄:5岁
每个实例都拥有各自的name和age数据。我们可以通过函数访问这些数据就没有必要从外面的函数访问,可以直接在Dog类内部定义访问数据的函数,这样就把“数据”封装起来了。这些封装数据的函数和Dog类本身是相关联的,我们称之为类的方法。这样一来,我们从外部看Dog类,只需要知道创建实例需要给出的name和age,如何输出是在Dog类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,不用知道内部实现的细节。
6. 多重继承
多重继承: 一个子类就可以继承多个父类,同时获得多个父类的所有非私有功能。
class People():
def run(self):
print("每天都要走路。")
class Student():
def study(self):
print("每天都要学习。")
class Senior(People,Student):
pass
senoir = Senior()
senoir.run()
senoir.study()
每天都要走路。
每天都要学习。
7. 获取对象信息
7.1 type()函数
type(123)
int
type(dog)
__main__.Dog
type(senoir)
__main__.Senior
7.2 isinstance()函数
isinstance(dog,Dog)
True
isinstance(senoir,Senior)
True
isinstance(senoir,Student)
True
isinstance(senoir,People)
True
isinstance(senoir,object)
True
isinstance(cat,Dog)
False
7.3 dir()函数
dir(dog) ##获取对象所有的属性和方法
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'age',
'name',
'show_info']
8. 类的专有方法
8.1. _str_
class Student(object):
def __init__(self,name):
self.name = name
print(Student('小明'))
<__main__.Student object at 0x000001844A42C390>
class Student(object):
def __init__(self,name):
self.name = name
def __str__(self):
return f'学生名称:{self.name}'
print(Student('小明'))
学生名称:小明
8.2._repr_
class Student(object):
def __init__(self,name):
self.name = name
def __str__(self):
return f'学生名称:{self.name}'
stu = Student('小明')
stu
<__main__.Student at 0x18448ae3ef0>
class Student(object):
def __init__(self,name):
self.name = name
def __str__(self):
return f'学生名称:{self.name}'
__repr__ = __str__
stu = Student('小明')
stu
学生名称:小明
8.3. _iter_
如果想将一个类用于for …in循环,类似list或tuple一样,就必须实现一个_iter_()方法。该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的_next_()方法,获得循环的下一个值,直到遇到StopIteration错误时退出循环。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化:a和b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self. b # 计算下一个月的值
if self.a > 10000: #退出循环
raise StopIteration();
return self.a # 返回下一个值
for n in Fib():
print(n)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
8.4 _getitem_
Fib()[5]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-79-14290f619470> in <module>()
----> 1 Fib()[5]
TypeError: 'Fib' object does not support indexing
上面的实例,如果要取出第n个元素,直接Fib()[n],不可行。要像list一样按照下标取出元素,则需要实现__getitem__方法
class Fib(object):
def __getitem__(self,n):
a, b = 1, 1
for x in range(n):
a, b = b, a+b
return a
fib = Fib()
fib[5]
8
8.5 _getattr_
Python还提供了一种机制,就是写一个_getattr_()方法,动态返回一个不存在的属性。
注: 只有在没有找到属性的情况下才调用_getattr_,已有的属性(如name)不会在__getattr__中查找。此外,如果所有调用都会返回None(如stu.abc),就是定义的__getattr__默认返回None。
class Student(object):
def __init__(self):
self.name = "小明"
def __getattr__(self,attr):
if attr == "score":
return 90
stu = Student()
print(stu.name)
print(stu.score)
小明
90
8.6 _call_
一般通通过对象名.方法名()调用实例,但是,任何类,如果定义了一个_call_()方法,就可以直接对实例进行调用,就合函数调用差不多。
class Student(object):
def __init__(self,name):
self.name =name
def __call__(self):
print(f"学生姓名:{self.name}")
stu = Student( "小明")
stu() # 直接使用实例进行调用
学生姓名:小明
怎么判断一个变量是对象还是函数呢? 通过callable()函数可以判断一个对象是否为“可调用”对象。
callable(stu)
True
callable(Student("小明"))
True
callable(max)
True
callable(list)
True
callable([1,2,3,4])
False
callable("小明")
False