-
面向对象编程
Object Oriented Programming,简称OOP,是一种程序设计方法。OOP把对象作为程序的基本单元,一个对象包含了数据(静态--属性)和操作数据的方法(动态--方法)。Python就是一种面向对象的语言,支持面向对象编程,在其内部,一切都被视作对象。
面向对象编程出现以前,结构化程序设计是程序设计的主流,结构化程序设计又称为面向过程编程。在面向过程编程中,问题被看作一系列需要完成的任务,函数(在此泛指例程、函数、过程)用于完成这些任务,解决问题的焦点集中于函数。其中函数是面向过程的,即它关注如何根据规定的条件完成指定的任务。
在多函数程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数访问。每个函数都可以具有它们自己的局部数据,将某些功能代码封装到函数中,日后便无需重复编写,仅调用函数即可。从代码的组织形式来看就是根据业务逻辑从上到下垒代码 。
- 面向过程编程通常具有如下的表现形式
1. 导入各种外部库
2. 设计各种全局变量
3. 写一个函数完成某个功能
4. 写一个函数完成某个功能
5. 写一个函数完成某个功能
6. 写一个函数完成某个功能
7. 写一个函数完成某个功能
8. ......
9. 写一个main函数作为程序入口
面向对象编程中,将函数和变量进一步封装成类,类才是程序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地改变。类和和类的实例(也称对象)是面向对象的核心概念,是和面向过程编程、函数式编程的根本区别。面向对象编程通常具有如下的表现形式:
1. 导入各种外部库
2. 设计各种全局变量
3. 决定你要的类
4. 给每个类提供完整的一组操作
5. 明确地使用继承来表现不同类之间的共同点
6. 根据需要,决定是否写一个main函数作为程序入口
- 面向对象编程类的实现
class Student(object):
def __init__(self, name1, age1): 构造函数中传入的name、age等于后面的name、age 为了区别,标成name1、age1,实际项目中就是 name、age
self.name = name1
self.age = age1
def print_ age(self):
print('%s: %s' % (self.name, self.age))
- 举一个例子
假如我现在要实现一个需求,根据对象的生物种类,发出不同的叫声,比如狗“旺旺”,猫“喵喵”,牛“哞哞”,二哈.....
用函数进行面向过程编程,一般是下面的样子:
def speak(animal):
if animal == "狗":
print("旺旺!")
elif animal == "猫":
print("喵!喵!")
elif animal == "牛":
print("哞!哞!")
else:
print("说人话!")
a, b, c, d = "猫", "狗", "牛", "二哈"
speak(a)
speak(b)
speak(c)
speak(d)
- 使用面向对象编程,一般是这样的子的:
# 先定义一个动物的类
class Animal:
# 动物实例的初始化方法,需要提供动物类别和该类动物的叫声
def __init__(self, kind, voice):
self.kind = kind
self.voice = voice
# 让动物发出叫声的方法
def speak(self):
print(self.voice)
# 实例化四种动物对象
a = Animal("狗", "旺旺!")
b = Animal("猫", "喵!喵!")
c = Animal("牛", "哞!哞!")
d = Animal("二哈", "说人话!")
# 调用动物类的发声方法
a.speak()
b.speak()
c.speak()
d.speak()
仔细揣摩两者的不同之处,感受两者在思考问题、分析问题和解决问题上的模式区别
概念与术语:
- 类(Class): 用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
- 实例:也称对象。通过类定义的初始化方法(__init__),赋予具体的值,成为一个"有血有肉的实体"。
- 实例化:创建类的实例的过程或操作。
- 实例变量:定义在实例中的变量,只作用于当前实例。
- 类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
- 数据成员:类变量、实例变量、方法、类方法、静态方法和属性等的统称。
- 方法:类中定义的函数。
- 静态方法:不需要实例化就可以由类执行的方法
- 类方法:类方法是将类本身作为对象进行操作的方法。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对父类的方法进行改写,这个过程也称override。
- 封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制
- 继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
- 多态:根据对象类型的不同以不同的方式进行处理。
- __init__:类的构造函数、初始化方法、实例化方法,具有
__init__
方法的类在实例化的时候,会自动调用该方法,并传递对应的参数
类和实例
类是抽象的模板,用来描述具有相同属性和方法的对象的集合,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
- python 使用 class 关键字来定义类
class 类名(父类列表):
pass
class Student: #此时Student类默认继承Object 类
pass
类名通常采用驼峰式命名方式,尽量让字面意思体现出类的作用。Python采用多继承机制,一个类可以同时继承多个父类(也叫基类、超类),继承的基类有先后顺序,写在类名后的圆括号里。继承的父类列表可以为空,此时的圆括号可以省略。但在Python3中,即使你采用类似class Student:pass
的方法没有显式继承任何父类的定义了一个类,它也默认继承object
类。因为,object
是Python3中所有类的基类。
- 定义一个学生类:
class Student():
classroom = '101'
address = 'beijing'
def __init__(self,name,age): #初始化方法/实例化方法/构造函数
self.name = name
self.age = age
def print_age(self): #实例方法
print('{}--{}'.format(self.name,self.age))
可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例。默认情况下,使用类似obj=Student()
的方式就可以生成一个类的实例。但是,通常每个类的实例都会有自己的实例变量,例如这里的name(zhangsan 、lisi)和age(23、24),为了在实例化的时候体现实例的不同,Python提供了一个def __init__(self):
的实例化机制。
任何一个类中,名字为__init__
的方法就是类的实例化方法,具有__init__
方法的类在实例化的时候,会自动调用该方法,并传递对应的参数
li = Student("李四", 24)
zhang = Student("张三", 23)
- 实例变量 和类变量
实例变量指的是实例本身(li、zhang)拥有的变量。每个实例的变量在内存中都不一样
实例变量:Student类中__init__
方法里的name和age就是两个实例变量。通过实例名加圆点的方式调用实例变量。
类变量:定义在类中,方法之外的变量,称作类变量。类变量是所有实例公有的变量,每一个实例都可以访问、修改类变量。在Student类中,classroom和address两个变量就是类变量。可以通过类名或者实例名加圆点的方式访问类变量,比如:
Student.classroom
Student.address
li.classroom
zhang.address
在使用实例变量和类变量的时候一定要注意,使用类似zhang.name访问变量的时候,实例会先在自己的实例变量列表里查找是否有这个实例变量,如果没有,那么它就会去类变量列表里找,如果还没有,弹出异常
为了防止发生混淆情况,对于类变量,请坚持使用类名.类变量
的访问方式,不要用实例去访问类变量
- 类方法
Python的类中包含实例方法、静态方法和类方法三种方法。这些方法无论是在代码编排中还是内存中都归属于类,区别在于传入的参数和调用方式不同。在类的内部,使用def
关键字来定义一个方法。
- 实例方法
类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给self。self
代表的是类的实例,而非类本身。self
不是关键字,而是Python约定成俗的命名,你完全可以取别的名字,但不建议这么做。
- 静态方法
静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。它属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)
- 类方法
类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)
class Student():
def __init__(self,name,age):
self.name = name
self.age = age
@staticmethod
def static_method(): #静态方法
pass
@classmethod
def class_method(cls): #类方法
pass
#静态方法调用
Student.static_method()
#类方法调用
Student.class_method()
类、类的所有方法以及类变量在内存中只有一份,所有的实例共享它们。而每一个实例都在内存中独立的保存自己和自己的实例变量。
创建实例时,实例中除了封装诸如name和age的实例变量之外,还会保存一个类对象指针,该值指向实例所属的类的地址。因此,实例可以寻找到自己的类,并进行相关调用,而类无法寻找到自己的某个实例。
封装、继承、多态
面向对象编程有三大重要特征:封装、继承和多态。
- 封装
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正是由于封装机制,程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。使用封装能隐藏对象实现细节,使代码更易维护,同时因为不能直接调用、修改对象内部的私有信息,在一定程度上保证了系统安全性。类通过将函数和变量封装在内部,实现了比函数更高一级的封装。
class Student:
classroom = '101'
address = 'beijing'
def __init__(self, name, age):
self.name = name
self.age = age
def print_age(self):
print('%s: %s' % (self.name, self.age))
# 以下是错误的用法
# 类将它内部的变量和方法封装起来,阻止外部的直接访问
print(classroom)
print(adress)
print_age()
- 继承
继承来源于现实世界,一个最简单的例子就是孩子会具有父母的一些特征,即每个孩子都会继承父亲或者母亲的某些特征,当然这只是最基本的继承关系,现实世界中还存在着更复杂的继承。继承机制实现了代码的复用,多个类公用的代码部分可以只在一个类中提供,而其他类只需要继承这个类即可。
在OOP程序设计中,当我们定义一个新类的时候,新的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:
class Foo(superA, superB,superC....):
class DerivedClassName(modname.BaseClassName): ## 当父类定义在另外的模块时
Python支持多父类的继承机制,所以需要注意圆括号中基类的顺序,若是基类中有相同的方法名,并且在子类使用时未指定,Python会从左至右搜索基类中是否包含该方法。一旦查找到则直接调用,后面不再继续查找。
- python3 的继承机制
Python3的继承机制不同于Python2。其核心原则是下面两条,请谨记!
- 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
- 根据父类定义中的顺序,以深度优先的方式逐一查找父类!
- 继承参数的书写有先后顺序,写在前面的被优先继承。
- 当一条路走到黑也没找到的时候才换另一条路。可见,在这种继承结构关系中,左边具有深度优先权,搜索顺序是这样的:
以下为继承关系以及搜索路径
- super() 函数
我们都知道,在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。那如果你想强制调用父类的成员呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__
语法:super(子类名, self).方法名()
,需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数
- 多态
狗、猫、猪都继承了动物类,并各自重写了kind方法。show_kind()函数接收一个animal参数,并调用它的kind方法。可以看出,无论我们给animal传递的是狗、猫还是猪,都能正确的调用相应的方法,打印对应的信息。这就是多态。
使用showanminal(d),相当于使用实例化的对象d.kind()来完成调用,但是封装一个函数就可以直接让其他实例使用了,
- 类的成员保护、访问限制
往往我们也不希望所有的变量和方法能被外部访问,需要针对性地保护某些成员,限制对这些成员的访问。这样的程序才是健壮、可靠的,也符合业务的逻辑。
在类似JAVA的语言中,有private关键字,可以将某些变量和方法设为私有,阻止外部访问。但是,Python没有这个机制,Python利用变量和方法名字的变化,实现这一功能。
在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__
,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。
class People:
title = "人类"
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_age(self):
print('%s: %s' % (self.__name, self.__age))
obj = People("jack", 18)
obj.__name
------------------------------
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 68, in <module>
obj.__name
AttributeError: 'People' object has no attribute '__name'
- 那外部如果要对
__name
和__age
进行访问和修改呢?在类的内部创建外部可以访问的get和set方法!
class People:
title = "人类"
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_age(self):
print('%s: %s' % (self.__name, self.__age))
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_name(self, name):
self.__name = name
def set_age(self, age):
self.__age = age
obj = People("jack", 18)
obj.get_name()
obj.set_name("tom")
这样做,不但对数据进行了保护的同时也提供了外部访问的接口,而且在get_name
,set_name
这些方法中,可以额外添加对数据进行检测、处理、加工、包裹等等各种操作,
比如下面这个方法,会在设置年龄之前对参数进行检测,如果参数不是一个整数类型,则抛出异常。
def set_age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError
- 那么,以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问
__age
是因为Python解释器对外把__age
变量改成了_People__age
,也就是_类名__age
(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age
在类的外部访问__age
变量:
obj = People("jack", 18)
print(obj._People__name) #此处_People__name, 没有 点,直接__双划线
也就是说:Python的私有成员和访问限制机制是“假”的,没有从语法层面彻底限制对私有成员的访问
- 此外,有些时候,你会看到以一个下划线开头的成员名,比如
_name
,这样的数据成员在外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的标识符时,意思就是,“虽然我可以被外部访问,但是,请把我视为私有成员,不要在外部访问我!”。
类的成员与下划线总结:
_name
、_name_
、_name__
:建议性的私有成员,不要在外部访问。__name
、__name_
:强制的私有成员,但是你依然可以蛮横地在外部危险访问。__name__
:特殊成员,与私有性质无关,例如__doc__
。name_
、name__
:没有任何特殊性,普通的标识符,但最好不要这么起名。
python 装饰器
- @property
Python内置的@property
装饰器可以把类的方法伪装成属性调用的方式。也就是本来是Foo.func()
的调用方法,变成Foo.func
的方式。
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError
@age.deleter
def age(self):
print("删除年龄数据!")
obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.age
>>>
18
obj.age: 19
删除年龄数据!
将一个方法伪装成为属性后,就不再使用圆括号的调用方式了。而是类似变量的赋值、获取和删除方法了
如何将一个普通的方法转换为一个“伪装”的属性呢?
- 首先,在普通方法的基础上添加
@property
装饰器,例如上面的age()方法。这相当于一个get方法,用于获取值,决定类似于中这样的形式"result = obj.age"的语句
执行什么代码。该方法仅有一个self参数。 - 写一个同名的方法,添加
@xxx.setter
装饰器(xxx表示和上面方法一样的名字),比如例子中的第二个方法。这相当于编写了一个set方法,提供赋值功能,决定类似"obj.age = ...."
的语句执行什么代码。 - 再写一个同名的方法,并添加
@xxx.delete
装饰器,比如例子中的第三个方法。用于删除功能,决定"del obj.age "
这样的语句具体执行什么代码。
简而言之,就是分别使用三个方法定义,对同一个属性的获取(get)、修改(set)和删除(delete)。还可以定义只读属性,也就是只定义getter方法,不定义setter方法就是一个只读属性。
- property()函数
除了使用装饰器的方式将一个方法伪装成属性外,Python内置的builtins模块中的property()函数,为我们提供了第二种设置类属性的手段。
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_age(self):
return self.__age
def set_age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError
def del_age(self):
print("删除年龄数据!")
# 核心在这句
age = property(get_age, set_age, del_age, "年龄")
obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.age
通过语句age = property(get_age, set_age, del_age, "年龄")
将一个方法伪装成为属性。其效果和装饰器的方法是一样的。
property()函数的参数:
- 第一个参数是方法名,调用
实例.属性
时自动执行的方法 - 第二个参数是方法名,调用
实例.属性 = XXX
时自动执行的方法 - 第三个参数是方法名,调用
del 实例.属性
时自动执行的方法 - 第四个参数是字符串,调用
实例.属性.__doc__
时的描述信息。
python 特殊成员和魔法方法
Python中有大量类似__doc__
这种以双下划线开头和结尾的特殊成员及“魔法方法”,它们有着非常重要的地位和作用,也是Python语言独具特色的语法之一!
- 1.
__doc__
说明性文档和信息。Python自建,无需自定义。
class Foo:
""" 描述类信息,可被自动收集 """
def func(self):
pass
# 打印类的说明文档
print(Foo.__doc__)
>>>
描述类信息,可被自动收集
-
2.
__init__()
实例化方法,通过类创建实例时,自动触发执行。
class Foo():
def __init__(self, name):
self.name = name
self.age = 18
obj = Foo(jack') # 自动执行类中的 __init__ 方法
- 3.
__module__
和__class__
__module__
表示当前操作的对象在属于哪个模块。
__class__
表示当前操作的对象属于哪个类。
这两者也是Python内建,无需自定义。
class Foo:
pass
obj = Foo()
print(obj.__module__)
print(obj.__class__)
------------
运行结果:
__main__
<class '__main__.Foo'>
- 4.
__call__()
如果为一个类编写了该方法,那么在该类的实例后面加括号,可会调用这个方法。
注:构造方法的执行是由类加括号执行的,即:对象 = 类名()
,而对于__call__()
方法,是由对象后加括号触发的,即:对象()
或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
怎么判断一个对象是否可以被执行呢?能被执行的对象就是一个Callable对象,可以用Python内建的callable()函数进行测试
>> callable(int)
True
>>> callable([1,2,3])
False
>>> def student():
... pass
...
>>> callable(student())
False
>>> callable(student)
True
>>>