面向对象的程序设计具有封装、继承、多态3个基本特征,可以大大增加程序可靠性、代码可重用行和程序可维护性,从而提高程序的开发效率。
类对象与实例对象
类对象
# 类对象
class 类名:
类体
class Person1:
pass
# 测试代码
p1 = Person1()
print(Person1, type(Person1), id(Person1))
print(p1, type(p1), id(p1))
实例对象
# 实例对象
anObject = 类名(参数列表)
anObject.对象函数 或 anObject.对象属性
Python创建实例对象的方法无需使用关键字new
,而是直接调用类对象并传递参数。因此,类对象是可调用对象。
Python内置函数中,可调用内置类对象bool / int / str / list / dict / set
# 实例对象的创建实例
c1 = complex(1, 2)
c1.conjugate()
c1.real
# output
(1-2j)
1.0
属性
Python变量不需要声明,可直接使用。
实例对象属性
通过“self.变量名”定义的属性称为实例对象属性,也称为实例对象变量。类的每个实例都包含了该类的实例对象变量的一个单独副本,实例对象变量属于特定的实例。实例对象变量在类的内部通过self访问,在外部通过对象实例访问。
# 实例对象属性一般在__init__()方法中初始化
self.实例变量名 = 初始值
# 在其他实例函数中通过self访问
self.实例变量名 = 值
# 创建对象实例后通过对象实例访问
obj1 = 类名()
obj1.实例变量名 = 值
obj1.实例变量名
class Person2:
def __init__(self, name, age): # 初始化__init__()方法
self.name = name
self.age = age
def say_hi(self):
print('您好,我叫', self.name)
# 测试代码
p1 = Person2('张三', 25)
p1.say_hi()
print(p1.age)
类对象属性
class Person3:
count = 0
name = "Person"
类属性如果通过obj.属性名
来访问,则属于实例的实例属性。虽然类属性可以使用对象实例来访问,但容易造成困惑,访问类属性建议使用标准的访问方式"类名.变量名"
私有属性和共有属性
Python类的成员没有范文控制限制。
# 私有属性:以两下划线开头,但不以两下划线结束
class A:
__name = 'class A'
def get_name():
print(A.__name)
A.get_name()
A.__name # 不可访问私有类属性
@property装饰器
面向对象编程的封装性原则要求不直接访问类中的数据成员。在Python中可以定义私有属性,然后定义相应的访问该私有属性的函数,并使用@property装饰器来装饰这些函数。程序可以把函数“当作”属性访问,从而提供更加友好的访问方式。
class Person11:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
# 测试代码
p = Person11('Estellasy')
print(p.name)
class Person12:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
self.__name = value
@name.deleter
def name(self):
del self.__name
#测试代码
p = Person12('姚六')
p.name = '王依依'
print(p.name)
# property的调用格式如下
property(fget=None, fset=None, fdel=None, doc=None)
# 其中,fget为get访问器;fset为set访问器;fdel为del访问器
特殊属性
以双下划线开始和结束的属性
自定义属性
@Override
方法
对象实例方法
在一般情况下,类方法的第一个参数一般为self,这种方法称为对象实例方法。
def 方法名(self, [形参列表]):
函数体
对象.方法名([实参列表])
class Person4: #定义类Person4
def say_hi(self, name): #定义方法say_hi
self.name = name #把参数name赋值给self.name,即成员变量name(域)
print('您好, 我叫', self.name)
p4 = Person4() #创建对象实例
p4.say_hi('Alice') #调用对象实例的方法
静态方法
与类的对象实例无关的方法,称之为静态方法。
静态方法不对特定实例进行操作,在静态方法中访问对象实例会导致错误。静态方法通过装饰器@staticmethod来定义,其声明格式如下。
@staticmetho
def 静态方法名([形参列表]):
函数体
一般通过类名来访问,也可以通过对象实例来调用。
class TemperatureConverter:
@staticmethod
def c2f(t_c): #摄氏温度到华氏温度的转换
t_c = float(t_c)
t_f = (t_c * 9/5) + 32
return t_f
@staticmethod
def f2c(t_f): #华氏温度到摄氏温度的转换
t_f = float(t_f)
t_c = (t_f - 32) * 5 /9
return t_c
#测试代码
print("1. 从摄氏温度到华氏温度.")
print("2. 从华氏温度到摄氏温度.")
choice = int(input("请选择转换方向:"))
if choice == 1:
t_c = float(input("请输入摄氏温度: "))
t_f = TemperatureConverter.c2f(t_c)
print("华氏温度为: {0:.2f}".format(t_f))
elif choice == 2:
t_f = float(input("请输入华氏温度: "))
t_c = TemperatureConverter.f2c(t_f)
print("摄氏温度为: {0:.2f}".format(t_c))
else:
print("无此选项,只能选择1或2!")
类方法
Python也允许声明属于类本身的方法,即类方法。类方法不对特定实例进行操作,在类方法中访问对象实例属性会导致错误。类方法通过装饰器@classmethod来定义,第一个形式参数必须为类对象本身,通常为cls。
@classmethod
def 类方法名(cls, [形参列表]):
函数体
# 类方法一般通过类名来访问,也可以通过对象实例来调用。
类名.类方法名([实参列表])
class Foo:
classname = "Foo"
def __init__(delf, name):
self.name = name
def f1(self):
print(self.name)
@staticmethod
def f2():
print("static")
@classmethod
def f3():
print(cls.classname)
#测试代码
f = Foo("李")
f.f1()
Foo.f2()
Foo.f3()
在Python类体中可以定义特殊的方法,例如__new__()
方法和__init__()
方法。
__new__()
方法是一个类方法,在创建对象时调用,返回当前对象的一个实例,一般无须重载该方法。
__init__()
方法即构造函数(构造方法),用于执行类的实例的初始化工作。在创建完对象后调用,初始化当前对象的实例,无返回值。
class Person5: #定义类Person5
def __init__(self, name): #__init__方法
self.name = name #把参数name赋值给self.name,即成员变量name(域)
def say_hi(self): #定义类Person的方法say_hi
print('您好, 我叫', self.name)
p5 = Person5('Helen') #创建对象
p5.say_hi() #调用对象的方法
__del__
方法即析构函数(析构方法),用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(例如:打开的文件、网络连接等)
默认情况下,当对象不再被使用时,__del__
方法运行,由于Python解释器实现自动垃圾回收,即无法保证这个方法究竟在什么时候运行
通过del语句,可以强制销毁一个对象实例,从而保证调用对象实例的__del__
方法。
class Person3:
count = 0 #定义类域count,表示计数
def __init__(self, name,age): #构造函数
self.name = name #把参数name赋值给self.name,即成员变量name(域)
self.age = age #把参数age赋值给self.age,即成员变量age(域)
Person3.count += 1 #创建一个实例时,计数加1
def __del__(self): #析构函数
Person3.count -= 1 #销毁一个实例时,计数减1
def say_hi(self): #定义类Person3的方法say_hi()
print('您好, 我叫', self.name)
def get_count(): #定义类Person3的方法get_count()
print('总计数为:', Person3.count)
print('总计数为:',Person3.count) #类名访问
p31 = Person3('张三',25) #创建对象
p31.say_hi() #调用对象的方法
Person3.get_count() #通过类名访问
p32 = Person3('李四',28) #创建对象
p32.say_hi() #调用对象的方法
Person3.get_count() #通过类名访问
del p31 #删除对象p31
Person3.get_count() #通过类名访问
del p32 #删除对象p32
Person3.get_count() #通过类名访问
两个下划线开头,但不以两个下划线结束的方法是私有的(private),其他为公共的(public).
以双下划线开始和结束的方法是Python的专有特殊方法。不能直接访问私有方法,但可以在其他方法中访问
class Book: #定义类Book
def __init__(self, name, author, price):
self.name = name #把参数name赋值给self.name,即成员变量name(域)
self.author = author#把参数author赋值给self.author,即成员变量author(域)
self.price = price #把参数price赋值给self.price,即成员变量price(域)
def __check_name(self): #定义私有方法,判断name是否为空
if self.name == '' : return False
else: return True
def get_name(self): #定义类Book的方法get_name
if self.__check_name():print(self.name,self.author) #调用私有方法
else:print('No value')
b = Book('Python程序设计教程','江红',59.0) #创建对象
b.get_name() #调用对象的方法
b.__check_name() #直接调用私有方法,非法
方法重载
可以定义多个重名的方法,只要保证方法签名是唯一的
方法签名包括三个部分:方法名、参数数量和参数类型
class Person21:
def say_hi(self, name = None):
self.name = name
if name == None:
print('您好')
else:
print('您好,我叫', self.name)
p21 = Person21()
p21.say_hi()
p21.say_hi('任思怡')
class Person22: #定义类Person22
def say_hi(self, name): #定义类方法say_hi,带两个参数
print('您好, 我叫', self.name)
def say_hi(self, name, age): #定义类方法say_hi,带三个参数
print('hi, {0}, 年龄:{1}'.format(name,age))
p22 = Person22() #创建对象
p22.say_hi('Lisa', 22) #调用对象的方法
#p22.say_hi('Bob') #TypeError: say_hi() missing 1 required positional argument: 'age'
Python类体中定义多个重名的方法虽然不会报错,但只有最后一个方法有效,所以建议不要定义重名的方法。
继承
派生类:python支持多重继承,即一个派生可以继承多个基类
声明派生类时,必须在其构造函数中调用基类的构造函数
class Person: #基类
def __init__(self, name, age): #构造函数
self.name = name #姓名
self.age = age #年龄
def say_hi(self): #定义基类方法say_hi
print('您好, 我叫{0}, {1}岁'.format(self.name,self.age))
class Student(Person): #派生类
def __init__(self, name, age, stu_id): #构造函数
Person.__init__(self, name, age) #调用基类构造函数
self.stu_id = stu_id #学号
def say_hi(self): #定义派生类方法say_hi
Person.say_hi(self) #调用基类方法say_hi
print('我是学生, 我的学号为:', self.stu_id)
p1 = Person('张王一', 33) #创建对象
p1.say_hi()
s1 = Student('李姚二', 20, '2018101001') #创建对象
s1.say_hi()
查看继承的层次关系
通过类的方法mro()或类的属性__mro__
可以输出其继承的层次关系
class A: pass
class B(A): pass
class C(B): pass
class D(A): pass
class E(B, D): pass
D.mro()
E.__mro__
类成员的继承和重写
通过继承,派生类继承基类中除构造方法之外的所有成员
如果在派生类中重新定义从基类继承的方法,则派生类中定义的方法覆盖从基类中继承的方法
class Dimension: #定义类Dimensions
def __init__(self, x, y): #构造函数
self.x = x #x坐标
self.y = y #y坐标
def area(self): #基类的方法area()
pass
class Circle(Dimension): #定义类Circle(圆)
def __init__(self, r): #构造函数
Dimension.__init__(self, r, 0)
def area(self): #覆盖基类的方法area()
return 3.14 * self.x * self.x #计算圆面积
class Rectangle(Dimension): #定义类Rectangle(矩形)
def __init__(self, w, h): #构造函数
Dimension.__init__(self, w, h)
def area(self): #覆盖基类的方法area()
return self.x * self.y #计算矩形面积
d1 = Circle(2.0) #创建对象:圆
d2 = Rectangle(2.0, 4.0) #创建对象:矩形
print(d1.area(), d2.area()) #计算并打印圆和矩形面积
Python面向对象程序设计 续
对象的特殊方法
- 包含许多以双下划线开始和结束的方法
- 创建对象实例时,自动调用
__init__
方法,a<b时,自动调用对象a的__It__
方法
class Person:
def __init__(self, name, age): #特殊方法(构造函数)
self.name = name
self.age = age
def __str__(self): #特殊方法,输出成员变量
return '{0}, {1}'.format(self.name,self.age)
#测试代码
p1 = Person('张三', 23)
print(p1)
运算符重载与对象的特殊方法
Python的运算符实际上是通过调用对象的特殊方法实现的
# 运算符重载示例
class MyList: #定义类MyList
def __init__(self, *args): #构造函数
self.__mylist = [] #初始化私有属性,空列表
for arg in args:
self.__mylist.append(arg)
def __add__(self, n): #重载运算符"+",每个元素增加n
for i in range(0, len(self.__mylist)):
self.__mylist[i] += n
def __sub__(self, n): #重载运算符"-",每个元素减少n
for i in range(0, len(self.__mylist)):
self.__mylist[i] -= n
def __mul__(self, n): #重载运算符"*",每个元素乘以n
for i in range(0, len(self.__mylist)):
self.__mylist[i] *= n
def __truediv__(self, n): #重载运算符"/",每个元素除以n
for i in range(0, len(self.__mylist)):
self.__mylist[i] /= n
def __len__(self): #对应于内置函数len(),返回列表长度
return(len(self.__mylist))
def __repr__(self): #对应于内置函数str(),显示列表
str1 = ''
for i in range(0, len(self.__mylist)):
str1 += str(self.__mylist[i]) + ' '
return str1
#测试代码
m = MyList(1, 2, 3, 4, 5) #创建对象
m + 2; print(repr(m)) #每个元素加2
m - 1; print(repr(m)) #每个元素减1
m * 4; print(repr(m)) #每个元素乘4
m / 2; print(repr(m)) #每个元素除2
print(len(m)) #列表长度
@functools.total ordering装饰器
支持大小比较的对象需要实现特殊方法:__eq__、__lt__、__le__、__ge__、__gt__
使用functools模块的total_ordering装饰器装饰类,则只需要实现__eq__
,以及__lt__、__le__、__ge__、__gt__
中的任意一个
total_ordering装饰器实现其他比较运算,以简化代码量
import functools
@functools.total_ordering
class Student:
def __init__(self, firstname, secondname):
self.firstname = firstname
self.secondname = secondname
def __eq__(self, other): #判断姓名是否一致
return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other): #self姓名<other姓名
return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
#测试代码
if __name__ == '__main__':
s1 = Student('Mary','Clinton')
s2 = Student('Mary','Clinton')
s3 = Student('Charlie','Clinton')
print(s1==s2)
print(s1>s3)
__call__
方法和可调用对象(callabe)
Python类体中可以定义一个特殊的方法:__call__
方法
定义了__call__
方法的对象称之为可调用对象
class GDistance:
def __init__(self, g):
self.g = g
def __call__(self, t):
return (self.g*t**2)/2
# 测试代码
if __name__ = '__main__':
e_gdist = GDistance(9.8)
for t in range(11):
print(format(e_gdist(t), "0.2f"),end=' ')
对象的引用、浅拷贝和深拷贝
对象的引用:对象的赋值,
id
相同-
对象的浅拷贝:对象的赋值会引用同一个对象,即不拷贝对象
切片操作
对象实例化
copy函数
import copy acc1 = ['Charlie', ['credit', 0.0]] acc2 = acc1[:] # 使用切片方式拷贝对象 acc3 = list(ac1) # 使用对象实例化方法拷贝对象 acc4 = copy.copy(acc1) # 使用copy.copy函数拷贝对象 acc2[0] = 'Mary' acc2[1][1] = -99.9
-
对象的深拷贝:如果要递归复制对象中包含的子对象,可以使用copy模块的deepcopy()函数
import copy acc1 = ['Charlie', ['credit', 0.0]] acc5 = copy.deepcopy(acc1) acc5[0] = 'Clinton' acc5[1][1] = -19.9
可迭代对象:迭代器和生成其
可循环迭代的对象称之为可迭代对象,迭代器和生成器函数是可迭代对象。
相对于序列,可迭代序列仅在迭代时产生数据,故可节省内存空间。
Python语言提供了若干内置可迭代对象:range\map\filter\enumerate\zip
标准库itertools模块中包含各种迭代器。
可迭代对象:实现了__iter__()
的对象是可迭代对象
collections.abc
模块中定义了抽象基类Iterable,使用内置的isinstance,可以判断一个对象是否为可迭代对象。
import collections.abc
isinstance((1, 2, 3), collections.abc.Iterable)
isinstance('python33',collections.abc.Iterable)
isinstance(123,collections.abc.Iterable)
系列对象都是可迭代对象,生成器函数和生成器表达式也是可迭代对象。
迭代器:实现了__next__()
的对象是迭代器
可以使用内置函数next(), 调用迭代器的__next__
方法,一次返回下一个项目值
使用迭代器可以实现对象的迭代循环,迭代器让程序更加通用、优雅、高校,更加python化。
import collections.abc
i1 = (i**2 for i in range(10))
insinstance(i1, collections.abc.Iterator)
迭代器对象必须实现两个方法:__iter__()
和__next()__
,二者合称为迭代器协议。__iter__()
用于返回对象本身,以方便for语句进行迭代,__next()__
用于返回下一元素
自定义可迭代对象和迭代器
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a
def __iter__(self):
return self
# 测试代码
fibs = Fib()
for f in fibs:
if f < 1000: print(f, end = ',')
else: break
生成器函数
生成器函数使用yield语句返回一个值,然后保存当前函数整个执行状态,等待下一次调用。
生成器函数是一个迭代器,是可迭代对象,支持迭代
def gentripls(n):
for i in range(n):
yield i*3
f = gentripls(10)
反向迭代 reversed迭代器
使用内置函数reversed(),可以实现一个系列的反向系列
如果一个可迭代对象实现了__reversed__()
方法,则可以使用reversed()函数获得其反向迭代对象
生成器表达式
生成器表达式的语法和列表解析基本一样,只不过生成器表达式使用()代替[]
表达式expr使用每次迭代内容iter_var,计算生成一个列表。如果指定了条件表达式cond_expr,则只有满足条件的iterable元素参与迭代
range可迭代对象
迭代时产生指定范围的数字序列,可以节省内存空间
map迭代器和itertool.starmap迭代器
map是可迭代对象,使用指定函数处理可迭代对象的每个元素(如果函数需要多个参数,则对应各可迭代对象),返回结果可迭代对象
filter迭代器和itertools.filterfalse迭代器
filter是可迭代对象,使用指定函数处理可迭代对象的每个元素,函数返回bool类型的值。若结果为True,则返回该元素。如果function为None,则返回元素为True的元素
>>> filter
<class 'filter'>
>>> list(filter(lambda x: x>0, (-1, 2, -3, 0, 5)))
[2, 5]
>>> list(filter(None, (1, 2, 3, 0, 5)))
[1, 2, 3, 5]
如果需要返回结果为False的元素,则需要使用itertools.filterfalse迭代器
filterfalse根据条件函数predicate处理可迭代对象的每个元素,若结果为True,则丢弃;否则返回该元素
>>> import itertools
>>> list(itertools.filterfalse(lambda x: x%2, range(10)))
[0, 2, 4, 6, 8]
zip迭代器和itertools.zip_longest迭代器
zip是可迭代对象,拼接多个可迭代对象iter1、iter2…的元素,返回新的可迭代对象,其元素为各系列iter1、iter2…对象元素组成的元组。如果各系列iter1、iter2…的长度不一致,则截断至最小系列长度。可以节省内存空间
>>> zip #<class 'zip'>
>>> zip((1,2,3),'abc', range(3)) # <zip object at 0x000001ED6C5A72C8>
>>> list(zip((1,2,3),'abc', range(3))) #[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2)]
>>> list(zip('abc', range(10))) #[('a', 0), ('b', 1), ('c', 2)]
多个可迭代对象的元素个数不一致时,如果需要取最大的长度,则需要使用itertools. zip_longest迭代器
>>> import itertools
>>> list(itertools.zip_longest('ABCD', 'xy', fillvalue='-'))
[('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]
enumerate迭代器
enumerate是可迭代对象,用于枚举可迭代对象iterable中的元素,返回元素为元组(计数, 元素)的可迭代对象