1.面向对象介绍
面向对象与面向过程
-
面向过程:根据业务逻辑从上到下写代码。
-
面向对象:将变量与函数绑定到一起,分类进行封装,每个程序只要负责分配给自己的分类,这样能够更快速的开发程序,减少了重复代码。
面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑,面向过程基本是由函数组成的。
1.1 面向过程编程
面向过程编程的关注点在于怎么做
-
把完成某一个需求的 所有步骤 从头到尾 逐步实现
-
根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数
-
最后完成的代码,就是顺序地调用 不同的函数
特点:
-
注重步骤与过程,不注重职责分工
-
如果需求复杂,代码会变得很复杂
-
开发复杂项目,没有固定的套路,开发难度很大!
1.2 面向对象基本概念
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)和面相过程编程,是两种不同的编程方式。
面向对象编程的关注点在于谁来做
相比较函数,面向对象是更大的封装,根据职责在 一个对象中封装多个方法
-
在完成某一个需求前,首先确定职责 —— 要做的事情(方法)
-
根据 职责 确定不同的 对象,在对象内部封装不同的方法(多个)
-
最后完成的代码,就是顺序地调用不同对象的相应方法。
特点:
-
注重 对象和职责,不同的对象承担不同的职责。
-
更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路。
-
需要在面向过程基础上,再学习一些面向对象的语法。
2.类和对象
类和对象是面向对象编程的两个核心概念。
2.1 类
类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用
-
特征其实就是一个变量,在类里我们称之为属性。
-
行为其实就是一个函数,在类里我们称之为方法。
-
类其实就是由 属性 和 方法 组成的一个抽象概念。
类就相当于制造飞机时的图纸,是一个模板。这个模板只规定了飞机的某些特征(例如大小,颜色,型号等等)和行为(例如起飞,降落,飞行等等),它并不是一个具体的飞机,而是对飞机的一个抽象概念。它出现的目的,是为了让我们的创建飞机对象。
2.2 对象
对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 对象 就相当于用图纸制造的飞机。在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。
类和对象的关系
-
类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
-
使用同一个类,能够创建出很多对象。
-
类中定义了什么属性和方法,对象中就有什么属性和方法。
-
不同对象对应的属性值也会不同。
例如:定义了一个狗类,这个狗类有以下属性:
-
品种
-
颜色
-
性别
-
名字
现在根据这个类创建出了两条狗,这两条狗分别是 哈士奇、灰色、母、二哈
和 中华田园犬、黄色、公、旺财
。我们发现,这两条狗都具有 品种、颜色、性别和名字这些属性,但是每条狗对应的属性值却不一样。
2.3 类的设计
在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
在程序开发中,要设计一个类,通常需要满足一下三个要素:
-
类名 这类事物的名字,安照大驼峰命名法(每个单词的首字母大写)起名。
-
属性 这类事物具有什么样的特征。
-
方法 这类事物具有什么样的行为。
2.4 定义类名
名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类。
2.5 属性和方法的确定
-
对对象的特征描述,可以定义成属性
-
对象具有的行为(动词)可以定义成方法
3.面向对象基本用法
3.1 定义简单的类(只包含方法)
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!
定义类
在Python中要定义一个只包含方法的类,语法格式如下:
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
方法的定义格式和之前学习过的函数一样
方法里的第一个参数必须是self,大家暂时先记住,稍后介绍 self.
类名要遵守大驼峰命名法。
创建实例对象
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量名 = 类名()
第一个面向对象代码
需求
-
小猫 爱 吃 鱼,小猫 要 喝 水
分析
-
定义一个猫类 Cat
-
定义两个方法 eat 和 drink
-
按照需求 —— 不需要定义属性
class Cat:
"""这是个猫类"""
def eat(self):
print("小猫在吃东西")
def drink(self):
print("小猫在喝水")
tom = Cat() # 创建了一个Cat对象
tom.eat()
tom.drink()
hello_kitty = Cat() # 又创建了一个新的Cat对象
hello_kitty.eat()
hello_kitty.drink()
思考:tom
和 hello_kitty
是同一个对象吗?
3.2 self的使用
给对象添加属性
python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值
就可以很方便的给对象添加一个属性。
tom = Cat()
tom.name = 'Tom' # 可以直接给 tom 对象添加一个 name 属性
这种方法很方便,但是,不建议使用这种方式给对象添加属性。
self的概念
哪个对象调用了方法,方法里的self
指的就是谁。 通过 self.属性名
可以访问到这个对象的属性;通过 self.方法名()
可以调用这个对象的方法。
class Cat:
def eat(self):
print("%s爱吃鱼" %self.name)
tom = Cat()
tom.name = 'Tom' # 给 tom 对象添加了一个name属性
tom.eat() # Tom爱吃鱼
lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat() # 大懒猫爱吃鱼
直接给对象添加属性的缺点
上述代码中,我们是先创建对象,然后再给对象添加 name
属性,但是这样做会有问题。
tom = Cat()
tom.eat()
tom.anme = "Tom"
程序运行时会报错:
AttributeError: 'Cat' object has no attribute 'name'
错误提示:'Cat'对象没有 'name' 属性
在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。
4.魔法方法
Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:
-
两侧各有两个下划线;
-
"咒语"名字已经由 Python 官方定义好,我们不能乱写。
4.1 __init__
方法
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__
方法进行改造。
class Cat:
"""这是一个猫类"""
def __init__(self,name): # 重写了 __init__ 魔法方法
self.name = name
def eat(self):
return "%s爱吃鱼"%self.name
def drink(self):
return '%s爱喝水'%self.name
"""
tom = Cat()
TypeError: __init__() missing 1 required positional argument: 'name'
这种写法在运行时会直接报错!因为 __init__ 方法里要求在创建对象时,必须要传递 name 属性,如果不传入会直接报错!
"""
tom = Cat("Tom") # 创建对象时,必须要指定name属性的值
tom.eat() # tom爱吃鱼
注意:
-
__init__()
方法在创建对象时,会默认被调用,不需要手动的调用这个方法。 -
__init__()
方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self -
在类的内部,可以使用self来使用属性和调用方法;在类的外部,需要使用对象名来使用属性和调用方法。
-
如果有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
-
方法是所有对象共享的,只占用一份内存空间,方法被调用时会通过self来判断是哪个对象调用了实例方法。
4.2 __del__
方法
创建对象后,python解释器默认调用__init__()
方法;
而当删除对象时,python解释器也会默认调用一个方法,这个方法为__del__()
方法。
class Student:
def __init__(self,name,score):
print('__init__方法被调用了')
self.name = name
self.score = score
def __del__(self):
print('__del__方法被调用了')
s = Student('lisi',95)
del s
input('请输入内容')
4.3 __str__
方法
__str__
方法返回对象的描述信息,使用print()
函数打印对象时,其实调用的就是这个对象的__str__
方法。
class Cat:
def __init__(self,name,color):
self.name = name
self.color = color
tom = Cat('Tom','white')
# 使用 print 方法打印对象时,会调用对象的 __str__ 方法,默认会打印类名和对象的地址名
print(tom) # <__main__.Cat object at 0x0000021BE3B9C940>
如果想要修改对象的输出的结果,可以重写 __str__
方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '哈哈'
p = Person('张三',18)
print(p) # 哈哈 打印对象时,会自动调用对象的 __str__ 方法
一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def __str__(self):
return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s) # 姓名是:lisi,成绩是95分
4.4 __repr__
方法
__repr__
方法和__str__
方法功能类似,都是用来修改一个对象的默认打印内容。在打印一个对象时,如果没有重写__str__
方法,它会自动来查找__repr__
方法。如果这两个方法都没有,会直接打印这个对象的内存地址。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return 'helllo'
class Person:
def __repr__(self):
return 'hi'
def __str__(self):
return 'good'
s = Student('lisi', 95)
print(s) # hello
p = Person()
print(p) # good
4.5 __call__
方法
对象后面加括号,触发执行。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
总结:
-
当创建一个对象时,会自动调用
__init__
方法,当删除一个对象时,会自动调用__del__
方法。 -
使用
__str__
和__repr__
方法,都会修改一个对象转换成为字符串的结果。一般来说,__str__
方法的结果更加在意可读性,而__repr__
方法的结果更加在意正确性(例如:datetime模块里的datetime类)
4.6 运算相关的魔法方法
思考:
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
p1 = Person('zhangsan',18)
p2 = Person('zhangsan',18)
print(p1 == p2)
上述代码中,使用==
运算符比较两个对象,结果是True还是False?==
到底比较的是什么?
4.7 比较运算符相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
# def __ne__(self, other):
def __lt__(self, other):
return self.age < other.age
# def __gt__(self, other):
def __le__(self, other):
return self.age <= other.age
# def __ge__(self, other):
s1 = Student('zhangsan', 18)
s2 = Student('zhangsan', 18)
s3 = Student('lisi', 20)
print(s1 == s2)
print(s1 != s2)
print(s1 > s2)
print(s1 >= s2)
print(s1 <= s2)
print(s1 <= s2)
4.8 算数运算符相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other):
return self.age + other
def __sub__(self, other):
return self.age - other
def __mul__(self, other):
return self.age * other
def __truediv__(self, other):
return self.age / other
def __mod__(self, other):
return self.age % other
def __pow__(self, power, modulo=None):
return self.age ** power
s = Student('zhangsan', 18)
print(s + 1) # 19
print(s - 2) # 16
print(s * 2) # 36
print(s / 5) # 3.6
print(s % 5) # 3
print(s ** 2) # 324
4.9 类型转换相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __int__(self):
return self.age
def __float__(self):
return self.age * 1.0
def __str__(self):
return self.name
def __bool__(self):
return self.age > 18
s = Student('zhangsan', 18)
print(int(s))
print(float(s))
print(str(s))
print(bool(s))
if s:
print('hello')
5.内置属性
使用内置函数dir
可以查看一个对象支持的所有属性和方法,Python中存在着很多的内置属性。
5.1 __solts__
Python中支持动态属性,可以直接通过点语法直接给一个对象添加属性,代码更加的灵活。但是在某些情况下,我们可能需要对属性进行控制,此时,就剋使用slots实现。
class Person(object):
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('张三', 18)
p.name = '李四'
# 对象p只能设置name和age属性,不能再动态添加属性
# p.height = 180 # 报错
5.2 __doc__
表示类的描述信息。
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
#输出:类的描述信息
5.3 __module__和__class__
module 表示当前操作的对象在那个模块;class 表示当前操作的对象的类是什么。
test.py
class Person(object):
def __init__(self):
self.name = 'laowang'
main.py
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
5.4 __dict__
以字典的形式,显示对象所有的属性和方法。
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
6.类属性和对象属性
在面向对象开发中,使用类创建出来的实例是一个对象,那么,类是否是一个对象呢?
6.1 实例属性
通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。
class Person(object):
def __init__(self,name,age):
# 这里的name和age都属于是实例属性,每个实例在创建时,都有自己的属性
self.name = name
self.age = age
# 每创建一个对象,这个对象就有自己的name和age属性
p1 = Person('张三',18)
p2 = Person("李四",20)
6.2 类属性
类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。
class Dog:
type = "狗" # 类属性
dog1 = Dog()
dog2 = Dog()
# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type) # 结果:狗
print(dog1.type) # 结果:狗
print(dog2.type) # 结果:狗
注意:
-
尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
class Dog(object): type = "狗" # 类属性 def __init__(self): self.type = "dog" # 对象属性 # 创建对象 dog1 = Dog() print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,使用 实例对象 访问的是 实例属性
-
类属性只能通过类对象修改,不能通过实例对象修改
lass Dog(object): type = "狗" # 类属性 # 创建对象 dog1 = Dog() dog1.type = "dog" # 使用 实例对象 创建了对象属性type print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,访问的是实例属性 print(Dog.type) # 结果为 "狗" 访问类属性 # 只有使用类名才能修改类属性 Dog.type = "土狗" print(Dog.type) # 土狗 dog2 = Dog() print(dog2.type) # 土狗
-
类属性也可以设置为私有,前边添加两个下划线。 如:
class Dog(object): count = 0 # 公有的类属性 __type = "狗" # 私有的类属性 print(Dog.count) # 正确 print(Dog.__type) # 错误,私有属性,外部无法访问。
7.私有属性和方法
在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。
7.1 定义方法
在定义属性或方法时,在属性名或者方法名前增加两个下划线__
,定义的就是私有属性或方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # 使用 __ 修饰的属性,是私有属性
def __shopping(self, cost):
self.__money -= cost # __money 只能在对象内部使用
print('还剩下%d元'%self.__money)
def test(self):
self.__shopping(200) # __shopping 方法也只能在对象内部使用
p = Person('张三',18)
# print(p.__money) 这里会报错,不能直接访问对象内部的私有属性
p.test()
# p.__shopping() 这里会报错,__shopping 只能在对象内部使用,外部无法访问
7.2 访问私有属性和方法
私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。
直接访问:
使用方式:在私有属性名或方法名前添加 _类名
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000
def __shopping(self, cost):
self.__money -= cost
p = Person('李四',20)
print(p._Person__money) # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
p._Person__shopping(100) # 使用对象名._类名__函数名 可以直接调用对象的私有方法
print(p._Person__money)
Copy
注意:在开发中,我们强烈不建议使用 对象名._类名__私有属性名
的方式来访问对象的私有属性!
定义方法访问私有变量:
在实际开发中,如果对象的变量使用了__
来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。如果硬要修改这个属性,可以使用定义get
和set
方法这种方式来实现。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # __money 是私有变量,外部无法访问
def get_money(self): # 定义了get_money 方法,在这个方法里获取到 __money
return self.__money # 内部可以访问 __money 变量
def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
self.__money = money
p = Person('王五',21)
# 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
print(p.get_money())
p.set_money(8000)
print(p.get_money())