一.类和类的实例
类(Class):用来描述具有相同的属性和方法的对象的集合。定义了该集合中每个对象所共有的属性和方法。
类的实例:每个对象都属于特定的类,并被称为该类的实例(类的具体实体)。
看点实际的:
class Person1: #定义类Person1 pass # 类体为空语句 print(Person1, type(Person1), id(Person1)) 结果: <class '__main__.Person1'> <class 'type'> 2077119090192
Person1:表示类对象的名称,属于"__main__"模块
type(Person1):表示Person1一个类
id(Person1):表示Person1类的唯一标识符,用来区别其他对象
class Person1: #定义类Person1 pass # 类体为空语句 p1 = Person1() #实例化该类并创建一个对象p1 print(p1, type(p1), id(p1)) 结果: <__main__.Person1 object at 0x0000026DD4176DD0> <class '__main__.Person1'> 2670732996048
p1:p1是Person1类的一个对象,属于“__main__”模块,其内存地址为0x0000026DD4176DD0
type(p1):表示p1所属的类是Person1,属于“__main__”模块
id(p1):表示p1对象的唯一标识符
二.类属性和实例属性
类变量(属性):类变量在整个实例化的对象中是公用的,定义在类中且在函数体之外,通常不作为实例变量使用,属于类本身,可以通过类名访问/修改
class Person2: count = 0 #定义属性count,表示计数 name = "Person" #定义属性name,表示名称 def __init__(self): pass def fun(self): pass #测试代码 Person2.count += 1 #通过类名访问,计数加1 print(Person2.count) #类名访问,读取并显示类属性 print(Person2.name) #类名访问,读取并显示类属性 结果: 1 Person
实例变量:在类的声明中,属性是用变量来表示的,定义在方法内,比如定义到构造方法中,普通的方法内,通过self.变量名定义的属性。
class Person3: #定义类Person3 def __init__(self, name,age): #__init__方法 self.name = name #初始化self.name, self.age = age #初始化self.age, def say_hi(self): #定义类Person3的函数say_hi() print('您好, 我叫', self.name) #测试代码 p1 = Person3('张三',25) #对象实例化 p1.say_hi () #调用对象的方法 print(p1.age) #通过p1.age(obj1.变量名)读取成员变量age 结果: 您好,我叫张三 25
改变类属性值或实例变量值
class Person4: count = 0 #定义属性count,表示计数 name = "Person" #定义属性name,表示名称 #测试代码 p1 = Person4() #创建实例对象1 p2 = Person4() #创建实例对象2 print((p1.name, p2.name)) #通过实例对象访问实例变量 Person4.name = "雇员" #通过类变量访问,设置类属性值 print((p1.name, p2.name)) #读取实例变量 p1.name = "员工" #通过实例变量访问,设置实例变量的值 print((p1.name, p2.name)) #读取实例变量的值
结果:
三.私有属性和公有属性
Python类的成员没有访问控制限制
约定两个下划线开头,但不以两个下划线结束的属性是私有的(private),其他为公共的(public)
class A: def __init__(self): self.__name = 'class A' #私有类属性 def get_name(self): print(self.__name) #在类方法中访问私有类属性 #测试代码 Aa = A() Aa.get_name() Aa.__name #导致错误,不能直接访问私有类属性
结果:
@property
面向对象程序设计的封装性原则要求不直接访问类中的数据成员,@property装饰器装饰访问私有属性的方法,使访问方式更友好。
class Person1: def __init__(self, name): self.__name = name @property def name(self): return self.__name #测试代码 p = Person1('王五') print(p.name) 结果: 王五
@property装饰器默认提供一个只读属性,可使用对应的getter、setter和deleter装饰器实现其他访问器函数,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。
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访问器
class Person1: def __init__(self, name): self.__name = name def getname(self): return self.__name def setname(self, value): self.__name = value def delname(self): del self.__name name = property(getname, setname, delname, "I'm the 'name' property.") #测试代码 p = Person1('张三') print(p.name) p.name = '李四' print(p.name) 结果: 张三 李四
Python对象包含许多以双下划线开始和结束的变量,称为特殊属性 :
自定义属性:
对象可以通过特殊属性__dict__存储自定义属性:
class C1: pass o=C1() o.name='custom name' print(o.__dict__)
结果:
拦截属性的访问:
可通过重载__getattr__和__setattr__拦截对成员的访问,从而自定义属性的行为
__getattr__只有在访问不存在的成员时才会调用
注意:不要使用return self.__dict__[name]返回结果,因为self.__dict__[name]同样会被__getattribute__拦截,造成无限递归死循环
__getattr__(self, name) #获取属性,比__getattribute__()优先调用
__getattribute__(self, name)# 获取属性
__setattr__(self, name, value)#设置属性
__delattr__(self,name) #删除属性
class
class CustomAttribute(object): def __init__(self): #__init__方法(构造函数) pass #空语句 def __getattribute__(self, name): #获取属性,拦截所有的获取操作 return str.upper(object.__getattribute__(self, name))#转换为大写 def __setattr__(self, name, value): #设置属性 object.__setattr__(self, name, str.strip(value))#去除收尾空格 #测试代码 o = CustomAttribute() #创建实例对象 o.firstname=' mary ' #设置成员变量的值 print(o.firstname) # 读取并显示成员变量的值
结果:
四.静态方法和类方法
静态方法
•声明与类的对象实例无关的方法
•静态方法不对特定实例进行操作,访问对象实例会导致错误
•静态方法通过装饰器@staticmethod来定义
•静态方法一般通过类名来访问,也可以通过对象实例调用
静态方法的声明和调用
#声明
@staticmethod
def 静态方法名([形参列表])
函数体
#调用
类名.静态方法名([实参列表])
class TemperatureConverter: @staticmethod def c2f(t_c): #摄氏温度到华氏温度的转换 t_c = float(t_c) t_f = (t_c * 1.8) + 32 return t_f @staticmethod def f2c(t_f): #华氏温度到摄氏温度的转换 t_f = float(t_f) t_c = (t_f - 32) /1.8 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__(self, name): self.name = name def f1(self): #实例方法 print(self.name) @staticmethod def f2(): #静态方法 print("static") @classmethod def f3(cls): #类方法 print(cls.classname) #测试代码 f = Foo("李") f.f1() Foo.f2() Foo.f3()
结果:
五.__init__方法,__new__方法和__del__方法:
•__init__方法即构造函数(构造方法),用于执行类实例的初始化。创建完对象后调用,初始化当前对象的实例,无返回值
•__new__方法是一个类方法,创建对象时调用,返回当前对象的一个实例,一般无需重载该方法
#__init__方法 class Point: def __init__(self, x = 0, y = 0): #构造函数 self.x = x self.y = y p1 = Point() #创建对象 print("p1({0},{1})".format(p1.x, p1.y)) p1 = Point(5, 5) #创建对象 print("p1({0},{1})".format(p1.x, p1.y)) 结果: p1(0,0) p1(5,5)
•__del__方法:
__del__方法即析构函数(析构方法),用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(例如:打开的文件、网络连接等)
默认情况下,当对象不再被使用时,__del__方法运行,由于Python解释器实现自动垃圾回收,即无法保证这个方法究竟在什么时候运行通过del语句,可以强制销毁一个对象实例,从而保证调用对象实例的__del__方法
class Person: count = 0 #定义类域count,表示计数 def __init__(self, name,age): #构造函数 self.name = name self.age = age Person.count += 1 #创建一个实例时,计数加1 def __del__(self): #析构函数 Person.count -= 1 #销毁一个实例时,计数减1 def get_count(): #定义类Person的方法get_count() print('总计数为:', Person.count) print('总计数为:',Person.count) #类名访问 p1 = Person('张三',25) #创建对象 Person.get_count() #通过类名访问 p2 = Person('李四',28) #创建对象 Person.get_count() #通过类名访问 del p1 #删除对象p1 Person.get_count() #通过类名访问 del p2 #删除对象p2 Person.get_count() #通过类名访问
结果:
六.私有方法和公有方法
•两个下划线开头,但不以两个下划线结束的方法是私有的(private),其他为公共的(public)
•以双下划线开始和结束的方法是Python的专有特殊方法。
•不能直接访问私有方法,但可以在其他方法中访问
class Book: def __init__(self, name, author, price): self.name = name self.author = author 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语言程序设计','嵩天',50.0) #创建对象 b.get_name() #调用对象的方法 b.__check_name()
结果:
七.方法的重载
•其他程序语言方法可以重载,即定义多个重名的方法,而方法签名唯一(方法名、参数数量和参数类型)
•Python本身是动态语言,方法的参数没有声明类型(在调用传值时确定参数的类型),参数的数量可变。故Python对象方法不需要重载,定义一个方法即可实现多种调用,从而实现相当于其他程序设计语言的重载功能
class Person21: #定义类Person21 def say_hi(self, name=None): #定义类方法say_hi self.name = name if name==None: print('您好! ') else: print('您好, 我叫', self.name) p21 = Person21() #创建对象 p21.say_hi() #调用对象的方法,无参数 p21.say_hi('威尔逊') #调用对象的方法,带参数
结果:
再看以下代码:
在Python类体中可以定义多个重名的方法,虽然不会报错,但只有最后一个方法有效,所以建议不要定义重名的方法
class Person22: def say_hi(self, name): #带两个参数 print('您好, 我叫', self.name) def say_hi(self, name, age): #带三个参数 print('hi, {0}, 年龄:{1}'.format(name,age)) p22 = Person22() #创建对象 p22.say_hi('Lisa', 22) #调用对象的方法 p22.say_hi('Bob') # error
结果:
八.方法的继承
•派生类:Python支持多重继承,即一个派生类可以继承多个基类
•如果类定义中没有指定基类,默认基类为object。object是所有对象的根基类
•声明派生类时,必须在其构造函数中调用基类的构造函数
派生类的声明和调用:
#声明
class 派生类名(基类1,[基类2...]):
类体
#调用
基类名.__init__(self, 参数列表)
或者:
super().__init__(参数列表)
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()
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
九.方法的重写
通过继承,派生类继承基类中除构造方法之外的所有成员,如果在派生类中重新定义从基类继承的方法,则派生类中定义的方法覆盖从基类中继承的方法
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()) #计算并打印圆和矩形面积
结果:
十.对象的特殊方法
特殊方法 | 含义 |
__lt__、__add__等 | 对应运算符<,+等 |
__init__、__del__ | 创建或销毁对象时调用 |
__len__ | 对应内置函数len() |
__setitem__、__getitem__ | 按索引赋值、取值 |
__repr__(self) | 对应于内置函数repr() |
__str__(self) | 对应于内置函数str() |
__bytes__(self) | 对应于内置函数bytes() |
__format__(self,format_spec) | 对应于内置函数format() |
__bool__(self) | 对应于内置函数bool() |
__hash__(self) | 对应于内置函数hash() |
__dir__(self) | 对应于内置函数dir() |
对象的特殊方法实例:
class Person: def __init__(self, name, age): #特殊方法(构造函数) self.name = name self.age = age def __str__(self): #特殊方法 return '{0}, {1}'.format(self.name,self.age) def __repr__(self): #特殊方法 return '{},{}'.format('李四','24') #测试代码 p1 = Person('张三', 23) print(p1) print(repr(p1)) 结果: 张三,23 李四,24
运算符的重载与对象的特殊方法
Python的运算符实际上是通过调用对象的特殊方法实现的
示例:
class MyList(): def __init__(self, *args): self.__mylist = [] #初始化私有属性,空列表 for i in args: self.__mylist.append(i) def __add__(self, other): #重载运算符"+",每个元素增加n for i in range(len(self.__mylist)): self.__mylist[i] += other def __sub__(self, other): #重载运算符"-",每个元素减少n for i in range(len(self.__mylist)): self.__mylist[i] -= other def __mul__(self, other): for i in range(len(self.__mylist)): self.__mylist[i] *= other def __truediv__(self, other): for i in range(len(self.__mylist)): self.__mylist[i] /= other def __len__(self): return len(self.__mylist) def __repr__(self): str1 = '' for i in range(len(self.__mylist)): str1 += str(self.__mylist[i])+' ' return str1 alist = MyList(1,2,3,4,5); alist + 2; print(repr(alist)); alist - 1; print(repr(alist)); alist * 2; print(repr(alist)); alist/2;print(repr(alist)); print(len(alist))
@functools.total_ordering:
支持大小比较的对象需要实现特殊方法:__eq__、__lt__、__le__、__ge__、__gt__,但使用functools模块的total_ordering装饰器装饰类,则只需要实现__eq__,以及__lt__、__le__、__ge__、__gt__中的任意一个
import functools @functools.total_ordering class Student: def __init__(self, firstname, lastname): #姓和名 self.firstname = firstname self.lastname = lastname 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) print(s1<s3)
结果:
__call__方法:
__call__方法的对象称之为可调用对象(callable),即该对象可以像函数一样被调用
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): #自由落体0~10秒的下落距离 print('下落距离为{:0.2f}m'.format(e_gdist(t)))
结果:
十一.对象的引用,浅拷贝和深拷贝
对象的引用:在创建一个对象并赋值给一个变量时,该变量是指向该对象的引用,其id()返回值保持一致
>>> acc10=['Charlie', ['credit', 0.0]] #创建列表对象(信用卡账户),变量acc10代表主卡 >>> acc11=acc10 #变量acc11代表副卡,指向acc10(主卡)的对象 >>> id(acc10),id(acc11) #二者id相同,输出: (2739033039112, 2739033039112)
对象的浅拷贝:
对象的拷贝可以使用以下方法
切片操作,例如,acc11[:]。
对象实例化,例如,list(acc11)。
copy模块的copy函数,例如,copy.copy(acc1)。
import copy acc1=['Charlie',['credit',0.0]] acc2=acc1[:] #使用切片方式拷贝对象 acc3=list(acc1) #使用对象实例化方法拷贝对象 acc4=copy.copy(acc1) #使用copy.copy函数拷贝对象 print(id(acc1),id(acc2),id(acc3),id(acc4)) #拷贝对象id各不相同 acc2[0]='Mary' #acc2的第一个元素赋值,即户主为'Mary' acc2[1][1]=-99.9 #acc2的第二个元素的第二个元素赋值,即消费金额99.9 print(acc1, acc2) #注意,acc2消费金额改变99.9,acc1也随之改变
结果:
对象的深拷贝:
Python复制一般为浅拷贝,即复制对象时对象中包含的子对象并不复制,而是引用同一个子对象。如果要递归复制对象中包含的子对象,需使用深拷贝
深拷贝需要使用copy模块的deepcopy函数,拷贝对象中包含的子对象
>>> acc5[0]='Clinton' #acc5的第1个元素赋值,即户主为'Clinton'
>>> acc5[1][1]=-19.9 #acc5的第2个元素的第2个元素赋值,即消费金额19.9
>>> acc1,acc5
(['Charlie', ['credit', 0.0]], ['Clinton', ['credit', -19.9]])
>>> id(acc1),id(acc5),id(acc1[1]),id(acc5[1])
(2739033040648, 2739033040264, 2739033040520, 2739033039688)