面向对象编程(一)
1、面向过程程序设计与面向对象程序设计:
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
面向对象的程序设计把计算机程序视为一组对象(具有相同的属性和行为)的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
从如下例子感受两种编程思想的差异:
假设我们要处理一个学生的成绩:
"""
#1、使用面向过程的程序设计:
#我们首先使用一个dict来保存学生的信息
stu1 = {"name":"Lily","score":89}
#然后定义一个函数来处理学生的成绩
def print_score(stu):
print("%s:%d"%(stu["name"],stu["score"]))
print_score(stu1)
"""
2、使用面向对象的程序设计:我们首先思考的不是程序执行过程,而是学生应该被视为一个对象,
这个对象拥有name与score两个属性,如果要打印学生成绩,我们要先创建出这个对象,然后给
它发消息让它打印成绩
"""
class Student(object):
def __init__(self,name,score):
self.name = name
self.score = score
def print_score(self):
print("%s:%d"%(self.name,self.score))
Lily = Student("Lily",89)
Lily.print_score()
2、在python中定义类
在python中所有的数据类型都可以视为对象。在Python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来。在python中定义类的关键字为class,基本格式:
class Person(object):
pass
"""
关键字class其后紧跟类名Person,然后元组(object)表示继承自什么
类(可以有多个),如果不写,默认就继承自基类object
"""
3、实例化对象:
p = Person() #实例化对象p
print(type(p))
#运行结果,可以看出p属于Person类
<class '__main__.Person'>
4、类中的方法
类中方法包括对象方法、类方法和静态方法,类方法的定义需要使用装饰器@classmethod,静态方法的定义需要使用装饰器@staticmethod,如下:
def foo():
print("一般函数")
class A(object):
def foo(self): #对象方法
print("对象方法")
@classmethod
def class_foo(cls): #类方法
print("类方法")
@staticmethod
def static_foo():#静态方法
print("静态方法")
a = A()
这里先理解一下函数中的self与cls,self与cls分别是对实例与类的绑定。对于一般的函数,我们可以这么调用foo(),他的工作与任何东西(实例,类)都无关。对于对象方法,我们在类里每次定义方法的时候都需要绑定这个实例,就是foo(self),因为对象方法的调用离不开对象,我们需要把对象传给函数,调用的时候是这样a.foo()(其实是这样foo(a)),类方法是一样,只不过它传入的是类本身。对于普通方法其实和普通方法一样,不需要对谁进行绑定,只不过调用的时候需要使用a.static_foo()或者A.static_foo()
对象方法 | 类方法 | 静态方法 | |
---|---|---|---|
a=A() | a.foo() | a.class_foo() | a.static_foo() |
A | 不可用 | A.class_foo() | A.static_foo() |
class A(object):
def foo(self): #定义对象方法
print(id(self),"对象方法")
@classmethod #定义类方法
def class_foo(cls):
print(id(cls),"类方法")
@staticmethod #定义静态方法
def static_foo():
print("静态方法")
a = A() #实例化对象
print(id(a))
a.foo() #调用对象方法
print(id(A))
A.class_foo() #类调用类方法
a.class_foo() #对象调用类方法
a.static_foo() #对象调用静态方法
A.static_foo() #类调用静态方法
#执行结果 可以看出self就是对象本身,cls就是类本身
31601264
31601264 对象方法
31545232
31545232 类方法
31545232 类方法
静态方法
静态方法
5、类中的属性
(1)类中的属性包括对象属性,类字段
对象属性只对每个具体的对象有效,这个有效是这不同的对象的同一属性值可能不同,类字段归所有对象公有,值都一样。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。是通过定义一个特殊的init方法来实现初始化的,这里的这些属性就是对象属性。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传。
类字段直接写在类中就行
class A(object):
num = 64 #这个num就是类字段了
pass
(2)访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
bart = Student('Bart Simpson', 59)
print(bart.score)
bart.score = 88 #随意改变成绩
print(bart.score)
#执行结果
59
88
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以两个下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
bart = Student('Bart Simpson', 59)
bart.print_score()
print("*********")
print(bart.__score)
#执行结果,无法访问bart.__score
Bart Simpson: 59
*********
Traceback (most recent call last):
File "E:/Python工程/test/1、运算符.py", line 12, in <module>
print(bart.__score)
AttributeError: 'Student' object has no attribute '__score'
python中没有真正的私有化(没有从访问权限上去限制内容的访问),私有的原理就是在私有的属性名或者方法名前加前缀'_类名'来阻止外部直接通过带两个下划线的名字去使用属性和方法,所以上的列子中还是可以通过bart._Stedent__score来访问成绩
注意:①在python中形如foo前后都有双下划线的名字是系统内部的名字,用来区别其他用户自定义的名字
②_foo这样只有前面有一个下划线的名字是一种约定,程序员用来指定私有变量的一种方式,不能使用 from model import * 的形式导入,其他访问方式一样,但是看见这种命名,你应该自觉的不去访问
③__foo这样前边有两个下划线的名字具有真正的意义,python解释器会解析成_classname__foo。
6、数据封装,继承,多态
面向对象编程的三大特点就是数据封装,继承,多态。
数据封装
面向对象编程的一个重要特点就是数据封装,在上面的Student类中,每个实例就拥有各自的name和score这些数据。既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法。这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印score,name,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
继承与多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。继承的一个好处就是可使子类自动获取父类的所有属性和方法。继承的另一个好处就是多态。
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
print(isinstance(a, list))
print(isinstance(b, Animal))
print(isinstance(c, Dog))
print(isinstance(c, Animal))
#执行结果
True
True
True
True
看来a、b、c确实对应着list、Animal、Dog这3种类型。看来c不仅仅是Dog,c还是Animal!不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行,Dog可以看成Animal,但Animal不可以看成Dog。
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def run_twice(animal):
animal.run()
animal.run()
#当我们传入Animal的实例时,run_twice()就打印出:
run_twice(Animal())
#当我们传入Dog的实例时,run_twice()就打印出:
run_twice(Dog())
#当我们传入Cat的实例时,run_twice()就打印出:
run_twice(Cat())
#执行结果
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
7、使用__slots__
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
class Student(object):
pass
s = Student()
s.name = 'Michael' # 动态给实例绑定一个属性
print(s.name)
#执行结果
Michael
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'Michael' #
s.age = 25 # 绑定属性'age'
s.score = 99 # 绑定属性'score'
#执行结果
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义一个。
8、getter和setter
1.什么时候需要添加对象属性的getter和setter
如果希望在通过对象.属性获取属性的值之前,再干点儿别的事情,就可以给这个属性添加getter。
如果希望在通过对象.属性给属性赋值之前,再干点儿别的事情,就可以给这个属性添加setter
2.怎么添加setter和getter
getter:
a.在属性名前加_
b.添加属性对应的getter
@property
def 属性名去掉_(self):
函数体 --> 会对属性的值进行处理后,返回相应的结果(必须要有返回值)
c.使用属性的值的时候,不通过带下划线的属性名去使用,而是通过没有下划线的属性去使用
注意:对象.不带下划线的属性 --> 调用getter对应的函数
setter:
如果想要添加setter必须要先添加getter
a.添加setter
@getter名.setter
def 属性名去掉_(self, 参数):
做别的事情
self.属性名 = 处理后的值
"""
# 赋值时要求age的值只能在0-150之间,超过范围报错;获取age的值的时候,返回年龄值,并且返回这个年龄对应的阶段
# class Person:
# def __init__(self):
# self.name
# self.age = 18
# print(p1.age) --> (18, 成年)
# value, jieduan = p1.age value -> 18, jieduan -> 成年
class Number:
def __init__(self):
self._value = 0
# 0-6保存
self._week = 3
self.type = int
self.id = None
# _value添加getter和setter
@property
def value(self):
return self._value
@value.setter
def value(self, x):
if not -100 <= x <= 100:
raise ValueError
self._value = x
# _week的getter
@property
def week(self):
if self._week == 0:
return '星期天'
elif self._week == 1:
return '星期一'
elif self._week == 2:
return '星期二'
elif self._week == 3:
return '星期三'
elif self._week == 4:
return '星期四'
elif self._week == 5:
return '星期五'
elif self._week == 6:
return '星期六'
"""
isinstance(值, 类) --> 判断指定的值是否是指定类型(返回值是bool)
"""
@week.setter
def week(self, value):
# 如果传的值不是整型数据
if not isinstance(value, int):
raise ValueError
if not 0 <= value <= 6:
raise ValueError
self._week = value
number = Number()
number.value = 99
print(number.week) # number.week 实质是在通过number去调用getter对应的week方法
number.week = 1 # number.week = 值 实质是通过number去调用setter对应的week方法
number.value = 100
print(number.value)
9、方法重写与运算符重载
父类方法重写
继承后子类会拥有父类的属性和方法,也可以添加属于自己的属性和方法
1.添加新的方法
直接在子类中声明新的方法,新的方法只能通过子类来使用
2.重写
a.子类继承父类的方法,在子类中去重新实现这个方法的功能 -- 完全重写
b.在子类方法中通过super().父类方法去保留父类对应的方法的功能
3.类中的函数的调用过程
类.方法(), 对象.方法()
先看当前类是否有这个方法,如果有就直接调用当前类中相应的方法;
如果没有就去当前的父类中去看有没有这个方法,如果有就调用父类的这个方法;
如果父类中也没有这个方法,就去父类的父类中找,依次类推直到找到为止。
如果找到基类object,还没有找到这个方法,程序才异常
"""
class Person:
def __init__(self, name=''):
self.name = name
def eat(self, food):
# self = super()
print('%s在吃%s' % (self.name, food))
@staticmethod
def run():
print('人在跑步')
@classmethod
def get_up(cls):
print('===========')
print('洗漱')
print('换衣服')
class Staff(Person):
pass
class Student(Person):
def study(self):
print('%s在学习' % self.name)
def eat(self, food):
# super():当前类的父类的对象
print('对象方法:',super())
super().eat(food)
print('喝一杯牛奶!')
@staticmethod
def run():
print('学生在跑步')
@classmethod
def get_up(cls):
# super() -> 获取当前类的父类
# super().get_up() ->调用父类的get_up方法
print('类方法', super())
super().get_up() # 可以保留父类get_up的功能
print('背书包')
p1 = Person()
Person.run()
Person.get_up()
p1.name = '小红'
p1.eat('面条')
stu1 = Student()
stu1.study()
Student.run()
Student.get_up()
stu1.name = '小花'
stu1.eat('面包')
运算符重载
运算符重载: 通过实现类响应的魔法方法,来让类的对象支持相应的运算符(+, -, > ,< 等)
值1 运算符 值2 ---> 值1.魔法方法(值2)
"""
10 > 20 # int类,实现 > 对应的魔法方法 __gt__
10 < 20
['12', 2] > ['abc' , 1, 34] # list类,实现 > 对应的魔法方法 __gt__
10 / 20 # __truediv__
20 % 10
import copy
import random
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
# __gt__就是 > 对应的魔法方法
def __gt__(self, other):
# self -> 指的是大于符号前面的值, other -> 指的是>符号后面的值
return self.score > other.score
# __lt__是 < 对应的魔法方法
# 注意:gt和lt只需要实现一个就可以了
def __lt__(self, other):
return self.score < other.score
def __add__(self, other):
return self.score + other.score
def __mul__(self, other: int):
result = []
for _ in range(other):
result.append(copy.copy(self))
return result
stu1 = Student('小哈', 23, 89)
stu2 = Student('小🌺', 19, 90)
print(stu1 > stu2)
print(stu1 < stu2)
print(stu1 + stu2)
students = stu1*10
print(students)
students[0].name = '小明'
class Person:
def __init__(self, name='张三', age=0):
self.name = name
self.age = age
def __mul__(self, other: int):
result = []
for _ in range(other):
result.append(copy.copy(self))
return result
def __gt__(self, other):
return self.age > other.age
# 定制打印格式
def __repr__(self):
return str(self.__dict__)[1:-1]
# 同时创建10个人的对象
persons = Person()*10
# persons = 10 * Person()
# print(persons)
for p in persons:
p.age = random.randint(15, 35)
print(persons)
# 列表元素是类的对象,使用sort对列进行排序
persons.sort()
print(persons)
print(max(persons))
class Dog:
def __mul__(self, other):
pass
dog1 = Dog()
dog1 * 4
网友评论