Python 基础八-面向对象
八 面向对象
对象(Object)
什么是对象?
-
对象是内存中专门用来存储数据的一块区域
-
对象中可以存放各种数据
-
对象由三部分组成(id,type,value)
面向对象(oop)
- Python是一门面向对象的编程语言
- Python中的所有操作,都是通过对象来进行的
- 对应的是面向过程的编程语言,指的是将程序的逻辑分解为一步步的抽象来完成程序
- 面向过程编写的代码往往只适用于一个功能,不方便扩展,可复用性低
- 面向对象的编程语言关注的是对象而不是过程,一切都是对象
类(class)
- 我们目前学习的对象都是Python的内置对象
- 自定义对象(class)
- 类,简单理解他就是一个图纸,程序中需要根据类创建对象
- 我们称对象是类的instance实例
- 如果多个对象是通过一个类创建的,那么我们称这些对象是一类对象
- 自定义的类都需要使用大写字母开头,大驼峰命名法
定义一个简单的类
类创建语法:
class 类名([父类]):
代码块
class MyClass():
pass
mc=MyClass()
print(MyClass)
print(isinstance(mc,MyClass))
# __main__.MyClass
# True
mc =MyClass() #mc 就是MyClass的实例
mc_2=MyClass()
实际上类也是一个对象,就是用来创建对象的对象,可以看下每个类的type
使用类创建对象的流程 mc=MyClass()
1. 创建一个变量mc
2. 在内存中创建一个新的对象
3. 分配地址,type就等于MyClass
4. 将对象的id赋值给变量mc
现在我们通过MyClass创建的对象什么都没有,相当于一个空盒子,可以像对象中添加变量称为属性
属性语法:
对象.属性=属性值
mc.name=‘tom’
类的定义
- 类和对象是对现实生活中的内容抽象
- 实际上所有的事物都有俩部分构成
- 数据(属性)
- 行为(方法)
class Person:
name='tom'
def sayHello():
print('Hello')
p1=Person()
p2=Person()
print(p1.name,p2.name)
p1.sayHello()
# TypeError: sayHello() takes no arguments (1 given)
属性和方法都定义在类对象中,为什么实例可以访问到类中的属性和方法
-
属性和方法的查找流程
- 当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性
- 如果有则直接返回当前的对象的属性值
- 如果没有则去当前对象的类对象中去寻找,如果有则返回类对象的属性值(就是type对应的类对象)
-
所以共有的属性和方法应该存在类对象中,实例独有的属性和方法需要自定义
比如:人共有的吃饭/睡觉等方法是共有的,名字是自定义的
方法调用和函数调用的区别:
如果是函数的调用传几个参数就是几个参数,如果是方法的调用默认至少有一个参数.
注意:方法调用时,第一个参数由解析器自动传递,所以定义方法时,至少要定义一个形参
这里第一个参数,就是调用方法的对象本身,如果p1调的,则第一个参数就是p1对象,p2调的则第一个参数就是p2对象,一般我们都会将这个参数命名为self
class Person:
name='tom'
def sayHello(self):
print(self)
p1=Person()
p1.sayHello()
print(p1)
p2=Person()
p2.sayHello()
print(p2)
#<__main__.Person instance at 0x7f688ec60638>
#<__main__.Person instance at 0x7f688ec60638>
#<__main__.Person instance at 0x7f688ec60680>
#<__main__.Person instance at 0x7f688ec60680>
对象的初始化
在类中可以定义一些特殊的方法(魔术方法),以_
开头_
结尾的方法
特殊方法不需要我们自己调用,会在类对象创建的时候自动调用,这样可以避免必要属性不传导致的错误.
创建对象的流程 :
- 创建一个变量
- 在内存中创建一个对象
_
init_
(self)方法执行- 将对象的id赋值给变量
一个标准类的流程
class Person:
def __init__(self,name):
self.name=name
def sayHello(self):
print('i am '+ self.name)
p1=Person('tom')
p1.sayHello()
print(p1.name)
p2=Person('jack')
p2.sayHello()
print(p2.name)
# i am tom
# tom
# i am jack
# jack
类的基本结构
class类名([父类]):
公共的属性
#对象的初始化方法
def _
init_
(self,…):
…
def method(self):
…
目前对象的属性可以随意修改,非常不安全,不论对错例如:只需要p.name=…就行,所以引入封装
封装
封装是面向对象的三大特性之一
封装指的是隐藏对象中一些不希望被外部所访问到的属性或方法
如何隐藏对象中的一个属性?
-
修改对象的属性名为外部知道的名字
-
getter and setter
-
getter setter 方法其实也可以有妙用 比如get方法 用户获取商品价格的时候计算PU UV
set方法 用户修改密码的时候给出提醒
-
使用getter方法可以表示一些计算的属性 比如计算长方形的面积
-
可以使用
__
双下划线开头的属性,这个是隐藏属性只能在类的内部访问,(相当于java中的private),无法通过对象访问 -
其实隐藏属性只不过是Python自动为属性修改了一个名字,
__
属性实际上是改为了,_
类命_属性名 -
一般情况下我们使用
_
单下划线开头属性表示私有属性,没有特殊需要不要修改私有属性(只是不希望你改)class Person: def __init__(self,name): self._name=name def get_name(self): return self._name def set_name(self,name): self._name=name p=Person('hetao') print(p.get_name())
class Person:
def __init__(self,name,age,price):
self.hidden_name=name
self.hidden_age=age
self.hidden_price=price
def get_name(self):
return self.hidden_name
def set_name(self,name):
self.hidden_name=name
def get_age(self):
return self.hidden_age
def set_age(self,age):
if age>0:
self.hidden_age=age
def get_price(self):
return self.hidden_price
def set_price(self,price):
self.hidden_price=price
def sayHello(self):
print('i am '+ self.hidden_name)
p=Person('hetao',2,'1800')
print(p.get_price())
p.set_price(2000)
print(p.get_price())
# 1800
# 2000
装饰器
property装饰器,用来将一个get方法,装换为对象的属性
添加为property装饰器以后,我们就可以像调用属性一样使用get方法
使用property装饰的方法,必须和属性名是一样的 @属性名.setter(注意:有setter必须有getter)
class Person:
def __init__(self,name):
self._name=name
@property
def name(self):
print('get is running')
return self._name
@name.setter
def name(self,name):
print('set is running')
self._name=name
p=Person('hetao')
print(p.name)
p.name='tom'
print(p.name)
# get is running
# hetao
# set is running
# get is running
# tom
继承
在定义类的同时,可以在括号中指定当前类的父类,子类可以继承父类中所有的属性和方法
class Animal:
def run(self):
print('running funny')
def sleep(self):
print('sleeping deeply')
class Dog(Animal):
def bark(self):
print('wang wang wang')
d=Dog()
d.run()
print(issubclass(Dog,Animal))
print(isinstance(d,Animal))
# running funny
# True
# True
如果在创建类时省略了父类,默认父类为Object,Object是所有类的父类,所有的类都继承Object
如果子类的方法和父类方法重复,这个时候调用谁?
重写
class Animal:
def run(self):
print('running funny')
def sleep(self):
print('sleeping deeply')
class Dog(Animal):
def run(self):
print('dog is running')
def bark(self):
print('wang wang wang')
d=Dog()
d.run()
# dog is running
调用一个对象的方法时,如果有调用当前对象的方法,没有的话调用父类中去寻找,直到Object
父类中所有的方法都会被子类继承,包括特殊方法,如果不初始化父类中的属性会报错,所有希望直接调用父类中的init方法来初始化父类中的属性super()
super()
通过super()返回对象调用父类方法时,不需要传递self(python3中)
class Animal(object):
def __init__(self,name,age):
self._name=name
self._age=age
@property
def name(self):
self._name=name
@name.setter
def name(self,name):
self._name=name
@property
def age(self):
self._age=age
@age.setter
def age(self,age):
self._age=age
class Dog(Animal):
def __init__(self,name,age):
super(Dog,self).__init__(name,age)
@property
def name(self):
self._name=name
@name.setter
def name(self,name):
self._name=name
@property
def age(self):
self._age=age
@age.setter
def age(self,age):
self._age=age
d=Dog('tom',18)
# python2
class Animal:
def __init__(self,name,age):
self._name=name
self._age=age
@property
def name(self):
self._name=name
@name.setter
def name(self,name):
self._name=name
@property
def age(self):
self._age=age
@age.setter
def age(self,age):
self._age=age
class Dog(Animal):
def __init__(self,name,age):
super( ).__init__(name)
self._age=age
@property
def name(self):
self._name=name
@name.setter
def name(self,name):
self._name=name
@property
def age(self):
self._age=age
@age.setter
def age(self,age):
self._age=age
d=Dog('tom',18)
# python3
类名.__
bases__
这个属性可以用来获取当前类的所有父类
class A(object):
def test(self):
print('a')
class B(object):
def test(self):
print('b')
class C(A,B):
def test(self):
print('c')
print(C.__bases__)
#(<class '__main__.A'>, <class '__main__.B'>)
如果多个父类中有重名的方法,会先调前面的
class A(object):
def test(self):
print('a')
class B(object):
def test(self):
print('b')
class C(B,A):
pass
c=C()
c.test()
# b
没有特殊情况下我们一般不会用多重继承
比如
object 2
A 1 B 3
c 4
D 5
class D(A,B,C):
pass
d=D()
d.test()这个时候的调用顺序如上
# 先找第一个父类A,如果A中没有找A的父类Object,Object没有找B,b没有找b的父类(因为Object已经找过不再找了,然后是c,c找不到找c的父类(因为abObject都找过不再找),然后是找d)
# 所以多重继承太过复杂
多态
多态从字面理解是多种形态
java中的多态指的是父类的应用对应子类的对象,这样更加灵活使用
Animal animal =new Cat();
在Python中调用不同的子类将会产生不同的行为,而无需知道这种子类实际上是什么,这是多态重要的应用场景
多态的具体好处可以用一句话来概括: 一个接口,多种实现
- 增加了程序的灵活性,不论对象如何变化,使用者都是通过同一种形式去调用
- 增加了程序的额外可扩展性
多态和多态性(鸭子类型)
多态指的是一类事物的多种形态,比如animal类可以有多种子类(猫/狗/猪等)
多态性指的具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名,调用不同内容的函数
比如len()函数可以传递列表,元组等
任意定义一个函数只要有__
len__
方法也可以使用len()函数
class A(object):
def __len__(self):
return 10
c=A()
print(len(c))
# 10
面向对象的三大特征
-
封装
确保了对象中的数据安全
-
继承
保证了对象的可扩展性
-
多态
保证了程序的灵活性
类中的属性和方法
类属性
class A(object):
count=0
def __init__(self):
self.name='tom'
a=A()
print(a.count)
A.count=100
print(A.count)
# 0
# 100
- 类属性,可以直接在类中定义的属性是类属性
- 类属性可以通过类或类的实例访问
- 但是类属性只能通过类对象来修改,无法通过实例对象来修改
实例属性
- 通过实例对象添加的属性属于实力属性
- 实例属性只能通过实例对象来访问和修改,类对象无法访问修改
class A(object):
count=0
def __init__(self):
self.name='tom'
a=A()
a.name='jack'
print(a.name)
# jack
实例方法
- 在类中定义,以self为第一个参数的方法都是实例方法
- 实例方法在调用的时候,Python会将调用对象作为self传入
- 实例方法可以通过实例和类调用,通过类调用时候不会传self,此时必须手动传递self
class A(object):
count=0
def __init__(self):
self.name='tom'
def test(self):
print('test')
a=A()
a.test()
A.test(a)
# test
# test
类方法
- 在类的内部,@classmethod 来修饰的方法属于类方法
- 类方法的第一个参数是cls,也会被自动传递,cls就是当前的类对象
- 类方法和实例方法的区别 实例方法的参数是self,而类方法的第一个参数是cls
- 类方法可以通过类去调用,也可以通过实例调用,没有区别
class A(object):
count=0
def __init__(self):
self.name='tom'
@classmethod
def test(cls):
print('test')
A.test()
# test
静态方法
- 在类中使用@staticmethod 来修饰的方法属于静态方法
- 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用
- 静态方法,基本上是一个和当前类无关的方法,只是保存到当前类中的函数
- 静态方法一般都是一些工具方法
class A(object):
count=0
def __init__(self):
self.name='tom'
@staticmethod
def test():
print('test')
a=A()
a.test()
A.test()
# test
# test
垃圾回收
在程序中没有被引用的对象就是垃圾,垃圾回收就是将垃圾对象从内存中进行删除(Python中有自动的垃圾回收机制)
class A(object):
def __init__(self):
self.name='tom'
a=A()
print(a.name)
a=None # 将a设置为None 此时没有任何变量使用A()对象进行引用,就是变成了垃圾 程序结束的时候也会触发垃圾回收
class A(object):
def __init__(self):
self.name='tom'
def __del__(self): # 这是个魔术方法,当对象没有引用的时候会自动调用这个方法
print('i was deleted')
a=A()
print(a.name)
a=None
# tom
# i was deleted
特殊方法(魔术方法)
- 特殊方法都是双下划线开头和结尾的
- 特殊方法一般不需要手动调用,在特殊的情况下自动执行
__
str()__
可以指定对象转换为字符串的结果
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return 'Person [name=%s,age=%d]'%(self.name,slef.age)
p1=Person('TOM',18)
p2=Person('jack',16)
#1 .print(p1) # 当我们在打印对象的时候,实际上是调用了对象中的特殊方法__str__
# <__main__.Person object at 0x7f08d587b410>
# 2 .print(p1)# Person [name='tom',age=18]
__
repr__
直接在交互模式中输出的结果
例如
>>> a='hello'
>>> print(a)
hello # 调用的str
>>> a
'hello' # 调用的repr
>>>
__
gt__
大于比较
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def __gt__(self,other):
return self.age>other.age
p1=Person('TOM',18)
p2=Person('jack',16)
print(p1>p2)
# True
- lt <
- le <=
- eq =
- ne !=
- gt >
- ge >=
__
len__
__
bool__
本来是判断对象是否为空
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def __gt__(self,other):
return self.age>other.age
p1=Person('TOM',18)
p2=Person('jack',16)
p1=None
print(bool(p1)) # false
可以通过bool来指定对像转为布尔值的条件
还有许多的魔法方法都可以改比如加减乘除,都是多态的表现
模块
创建模块
模块化,指的是将一个完整的程序分解为一个一个小的模块,通过将模块组合,搭建出一个完整的程序
在Python中一个py文件就是一个模块,创建模块就是创建py文件
注意:模块名要符合合格的标识符的规范
如何在其它模块中引入外部模块
-
import模块名 (模块名,就是Python文件的名字,不需要py后缀)
-
import test_module
-
模块可以引入同一个模块多次,但是模块的实例只会创建一个
-
-
import 模块名 as 模块别名
- import test_module as test
- print(test)
- import 可以在程序的任意位置调用,一般都写在开头
- 没一个模块都有一个
__
name__
属性可以查看当前模块的文件名 - 注意
__
name__
属性为__
main__
的模块是主模块,一个程序中只会有一个主模块,就是通过python执行的模块
使用模块
访问模块中的属性:模块名.变量名
#bb.py
a=10
b=20
# aa.py
import bb
print(bb.a)
# 10
访问模块中的函数:模块名.函数名
访问模块中的类: 模块名.类
也可以引入模块中的部分内容
语法:from 模块名 import 变量,变量…
- from m import Person,test
- from m import * #一般不会轻易使用 会和主模块冲突
也可以为引入的变量使用别名
语法:from 模块名 import 变量 as 别名
- from m import test2 as new_test2
添加了_
的变量只能在模块内部访问,相当与私有变量,通过import *引入时不会看见 但import m 可以
*规定某些代码,只有当当前模块是主模块的时候才执行,而当前代码被其它模块引入的时候不需要执行
if __name__=='__main__':
pass
包 package
-
包也是一个模块,当我们模块中代码过多时,或者一个模块需要被分解成多个模块时,就需要使用到包
-
普通的模块就是一个py文件,包就相当于一个文件夹
-
包中必须要有一个
__
init__
.py
from hello import a,b
print(a.xxx)
print(b.xxx)
包的好处就是可以把功能相似的代码放到一块
__
pycache__
模块缓存文件,py在执行前,需要被解析器转为机器码,然后再执行,所以我们在使用模块(包)时,需要将模块的代码转为机器码然后再交由计算机执行,而为了提高程序运行性能,Python会在编译一次过后,将代码保存到一个缓存文件中,下次加载这个包时,不再重新编译,加载缓存缓存中的.
python标准库
开箱即用(在官方文档中global module index中可以找到全局的索引库)
为了实现开箱即用,Python中提供了一个模块的标准库,标准库是Python安装自带的
sys模块,提供了一些变量和函数,可以获取到Python的解析器
pprint模块,提供了我们一个方法pprint()该方法可以用来对打印的数据做简单的格式化
import sys
import pprint
print(sys.argv)# 获取在命令行执行这个py脚本的参数
print(sys.modules) # 返回所有引入的模块,是一个字典,key是模块名字 value是模块对象
pprint.pprint(sys.modules) # 返回pretty(漂亮)的print语句
pprint.pprint(sys.path) # 他是一个列表,列表中保存的是模块的搜索路径(这个是pprint的路径)
print(sys.platform) #表示当前Python运行的操作系统环境
sys.exit('gg')# 用来退出程序
os模块,让我们对操作系统进行访问
import pprint
import os
pprint.pprint(os.environ)# 返回的是系统的环境变量是个字典
os.system('ls') # 可以直接执行Linux命令