Python之旅基础——类的相关总结part1
一、面向对象设计和面向对象编程
1、对象:是指特征和动作的结合。在具体程序中,特征就是数据属性,动作就是函数属性(方法)。
2、面向对象设计,就是符合属性与方法结合的设计理念。
3、面向对象编程,就是用固定的编程方式来编写程序,在Python中就是使用class来定义。
4、类与对象的关系:
类是把一类失误的相同的特征和动作整合到一起。类是一个抽象的概念。
对象是基于类而产生的一个具体的事物,它也是特征和动作的结合,但是他是具体存在的。
5、声明定义一个类的方法
class Room: #按照规范,声明类时,类名首字母大写
"一个房间的类" #类的说明文档。正规变成中,都会写文档,以说明创建该类的作用
name = "bathroom"
width = 3
length = 3
def area(self):
print("%s的面积是%s" % (Room.name, Room.width * Room.length))
注:函数属性中的self,也是一个位置参数,它特指实例对象本身。当实例对象调用该函数属性时,Python会自动将实例传给函数。反过来考虑,当类调用该函数属性时,由于没有实例对象可以传给这个self参数,那么就需要手动传入。
二、属性
属性包括数据属性和函数属性。数据属性就是所描述事物的静态特征,函数属性,又称方法,是所描述事物的动作特征。
1、类和对象都是通过点来访问属性的。
class Room: #按照规范,声明类时,类名首字母大写
"一个房间的类" #类的说明文档。正规变成中,都会写文档,以说明创建该类的作用
name = "bathroom"
width = 3
length = 3
def area(self):
print("%s的面积是%s" % (Room.name, Room.width * Room.length))
print(Room.width) #类通过点来访问类的数据属性
print(Room.area) #类通过点来访问类的函数属性,由于没有运行函数,所以获得函数的内存地址
r1 = Room() #实例化出一个对象
print(r1.name) #对象通过点来访问类的数据属性
r1.area() #对象通过点来调用类的函数属性
Room.area(r1) #类通过点来调用类的函数属性,由于self位置参数的影响,需要传入一个实例作为第一个参数
3
<function Room.area at 0x0000000001EB1D08>
bathroom
bathroom的面积是9
bathroom的面积是9
其实类和对象通过点来调用数据属性和函数属性,都是通过底层的属性字典来完成调用的。
class Room:
"一个房间的类"
name = "bathroom"
def __init__(self, width, length): #实例化的过程就是类调用了__init__函数
self.width = width #self.width表示实例对象的数据属性
self.length = length
def area(self):
print("%s的面积是%s" % (Room.name, self.width * self.length))
#说明一下使用__init__和上面直接创建类的数据属性有什么区别。
#使用__init__所创建的类,在实例化对象的时候,需要加入对象的参数。
r2 = Room(2,3) #实例化的过程就是类调用__init__函数,所以需要传入相对应的参数,第一个参数是self实例本身,第二第三个参数就是传入的长和宽。
#这样做的目的就是实例化出来的每一个房间,他们的名字都是bathroom,但是每个实例的长和宽都是每个实例特有的。
print(Room.__dict__) #类的属性字典包括了类的数据属性和函数属性,以及其他特定的属性参数
print(r2.__dict__) #实例的属性字典包括了实例的数据属性。
由结果可以看出,类的属性字典里有类的数据属性和类的函数属性;而实例对象的属性字典里只有实例化时所产生的数据属性。
但是实例对象也可以调用类的数据属性和函数属性,其原因在于作用域。当实例对象用点的方式调用某一个属性时,首先在自己属性字典中查找,如果没有,就会在类的属性字典里查找。
类属性又称为静态属性,或者静态数据。这些数据是与它们所属的类绑定的,不依赖与任何实例对象。
2、类属性和实例对象的增删改查
class Room:
name = "bathroom"
def __init__(self, width, length):
self.width = width
self.length = length
def area(self):
print("%s的面积是%s" % (Room.name, self.width * self.length))
print(Room.name) #利用点的方式查看类属性
Room.place = "north" #添加类属性
print(Room.place)
Room.name = "bedroom" #修改类属性
print(Room.name)
del Room.place #删除类属性
def per(self):
print(self.width * 2 + self.length * 2)
Room.per = per #添加类的函数属性
print(Room.per)
r1 = Room(2,3)
print(r1.width) #利用点的方式查看实例属性
r1.width = 3 #修改实例属性
r1.name = "badroom" #给实例添加一个name的数据属性,由于和类属性重名,不会覆盖类属性
print(r1.name)
print(r1.area) #查看函数属性,由于实例对象的属性字典里没有,就会在类的属性字典里获取
bathroom
north
bedroom
<function per at 0x0000019E16541E18>
2
badroom
<bound method Room.area of <__main__.Room object at 0x0000019E18214438>>
小结:从狭义的角度上看,类的属性字典里包含类的数据属性和函数属性,实例对象的属性字典里包含实例的数据属性(就是由__init__产生的数据属性)。但由于实例对象可以调用类的函数属性,所以从广义的角度上看,实例对象也有函数属性。
类的数据属性和函数属性做任何修改后,都是立即作用于实例对象的。
在类内部出现一个变量名,只要不是以点的方式调用,变量名指向的就是全局变量。
三、可以利用类,当做一个作用域
class MyData:
pass
x=10
y=20 #x,y都是全局变量
MyData.x = 1
MyData.y =5 #MyData.x和MyData.y都是类MyData的类属性
print(x,y,MyData.x,MyData.y)
10 20 1 5
四、静态属性、类方法、静态方法
①静态属性property
class Room:
def __init__(self,width,length):
self.width = width
self.length = length
@property
def calc_area(self):
return self.width * self.length
r1 = Room(2,4)
print(r1.calc_area)
在类的函数属性前添加@property,将函数属性变成了数据属性,可以直接用点来获取并运行函数属性。
如果没有添加@property,用户在调用函数属性时,需要在函数名后加上()。
这样做的目的是为了更好的封装函数属性。
②类方法classmethod
我们知道,在函数定义过程中的self其实就是指实例本身。所以,在实例对象调用类的函数属性时,self作为第一个位置参数,会自动接收实例对象。
但是,当类调用函数属性时,self没有接收到实例对象,就会报错。
在一些情况下,只想用类调用函数属性,不与实例有任何关系,就需要使用@classmethod来装饰函数。
class Test:
tag = 1
def __init__(self,name):
self.name = name
@classmethod
def tell_info(cls): #创建一个类方法,用于描述类本身
print(cls)
print("%s类的数据属性是%s" % (cls, cls.tag))
Test.tell_info()
<class '__main__.Test'>
<class '__main__.Test'>类的数据属性是1
③静态方法staticmethod
在类中定义的静态方法,该函数与类和实例对象都没有绑定关系,即参数中没有self和cls。所以无论是类还是实例对象,都可以随时调用该函数属性。
class Test:
def __init__(self,name):
self.name = name
@staticmethod
def test_1(a,b,c):
return a+b+c
def test_2(self):
return "from test_2"
t = Test("alex")
print(t.test_2())
print(t.test_1(1,2,3))
print(Test.test_1(1,2,3))
from test_2
6
6
五、组合
将一个类的实例作为另外一个类的参数,这样的情况就是组合。当类之间有明显的不同,且较小的类是较大的类所需要的组件,那么使用组合比较好。
class School:
def __init__(self,name, addr): #学校的静态信息包含校名和地址
self.name = name
self.addr = addr
class Teacher:
def __init__(self,name,salary,school): #老师的静态信息包含姓名、工资和所属学校
self.name = name
self.salary = salary
self.school = school
s1 = School("北大", "北京") #实例化一个学校
t1 = Teacher("alex", 12300, s1) #老师的信息与学校的信息相关,将学校的实例对象作为参数传给Teacher类中
print(t1.school.name) #通过点的方式可以获取老师所属学校的属性
六、类的三大特征(继承、多态、封装)
1、继承
当类之间有很多共同的功能,提取这些共同的功能作为基类,用继承的方式产生各种子类。
class Father:
tag = 10
def __init__(self,x,y):
self.x = x
self.y = y
def calc(self):
return self.x + self.y
class Son(Father): #Son类继承于Father类。
pass #虽然下Son类中什么都没有写,但是它继承了Father类的数据属性和函数属性
print(Father.__dict__)
print(Son.__dict__) #通过类的属性字典可以看出,Son类中并没有真正获取到Father类的属性
# 但是在使用过程中,子类会在父类的属性字典中查找获取
#这也说明了,使用继承的方式得到的子类,与父类高度耦合
s1 = Son(1,2)
print(s1.tag) #子类的实例对象可以调用父类的数据属性
print(s1.calc()) #子类的实例对象可以调用父类的函数属性
class Daugher(Father): #Daughter类继承于Father类
name = "alex"
def calc(self): #在Daughter类中修改了clac函数属性
return self.x - self.y
d1 = Daugher(1,2)
print(Daugher.__dict__)
print(d1.calc()) #如果子类中定义了自己的数据属性和函数属性,在调用过程中就会在自己的属性字典中查找
#如果子类中的数据属性和函数属性,和父类中的重名,也会使用自己的属性,对父类的属性不变
{'__module__': '__main__', 'tag': 10, '__init__': <function Father.__init__ at 0x0000022075C42A60>, 'calc': <function Father.calc at 0x0000022075C42D90>, '__dict__': <attribute '__dict__' of 'Father' objects>, '__weakref__': <attribute '__weakref__' of 'Father' objects>, '__doc__': None}
{'__module__': '__main__', '__doc__': None}
10
3
{'__module__': '__main__', 'name': 'alex', 'calc': <function Daugher.calc at 0x0000022075C429D8>, '__doc__': None}
-1
2、在子类继承父类的过程中,想调用父类的方法(不想完全修改父类的方法,只需要进行修改)。那么就要用到super()。
class Vehicle:
Country = "China"
def __init__(self,name,speed,power):
self.name = name
self.speed = speed
self.power = power
def run(self):
print("%s开动啦" % self.name)
class Bus(Vehicle):
def __init__(self,name,speed,power,num):
super().__init__(name,speed,power) #对__init__方法进行修改,但调用了父类的__init__方法
self.num = num
def run(self):
super().run() #对函数属性run进行修改,但也继承了父类的方法
print("%s%s路开动啦" % (self.name, self.num))
b1 = Bus("公交车", 40, "gasoline", 77)
b1.run()
公交车开动啦
公交车77路开动啦
使用super()的好处在于:
① 不用指定父类名,对于代码的扩展性强
② 不用写self
3、继承的用途
①子类通过继承父类的属性和方法,实现共同的功能。缺点在与父子类的有更多的耦合关系,其实不好。
②接口继承(归一化设计)。父类用于规定子类中必须实现的功能。
import abc
class All_file(metaclass = abc.ABCMeta): #定义一个接口类(固定格式)
@abc.abstractmethod #在这个父类中规定了,继承于这个父类的子类,都必须要有read和write函数。
def read(self): #其实可以在父类中read和write没有实现任何功能
pass #所以没有必要实例化这个接口类
@abc.abstractmethod
def write(self):
pass
class Disk(All_file):
def read(self):
print("from Disk read")
def write(self):
print("from Disk write") #在子类中规定具体的函数实现
d = Disk()
4、继承顺序
由于Python2中的类分经典类和新式类两种,所以它的继承顺序分为深度优先和广度优先。
Python3中的类都是新式类,故继承顺序都是广度优先。
class A:
def test(self):
print("A")
class B(A):
def test(self):
print(B)
class C(A):
def test(self):
print("C")
class D(B):
def test(self):
print("D")
class E(C):
print("E")
class F(D,E):
print("F")
print(F.__mro__)
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
在上述例子中,A为基类,B,C分别继承于A,D继承于B,E继承于C,F继承于D和E。如果F中没有test方法,那么就会在父类中查找。
广度优先如下图:

继承顺序是F→D→B→E→C→A→object。
在python3中,可以通过F.__mro__对继承顺序进行查看。
深度优先,如下图:继承顺序是F→D→B→A→E→C。

5、多态
不同的对象执行相同的方法,得到结果会有不同。
6、封装
第一层封装:类就像一个麻袋,对于其他人来说,只能看到类名,却不知道类中所定义的数据属性和函数属性有哪些。
第二层封装:类中约定私有的,只能在类的内部使用,外部无法访问。
注意:这只是约定,用户依然可以通过其具体的属性名来进行访问。
约定一:任何以单下划线开头的名字都是内部的,私有的。
class People:
_star = "earth" #单下划线开头,约定_star是私有属性
def __init__(self,name):
self.name = name
p1 = People("alex")
#print(p1.star) #无法直接调用star数据属性
print(p1._star) #依然可以通过_star来获取
约定二:双下划线开头的名字,在python内部重新命名。
class People:
__star = "earth" #双下划线开头,约定_star是私有属性
def __init__(self,name):
self.name = name
p1 = People("alex")
print(p1._People__star) #依然可以通过:对象._类名__属性名 来获取
第三层封装:(真正层面上的封装)
明确区分内外,内部的实现逻辑,外部无法知晓,关键的是,为封装在内部的逻辑提供一个访问接口给外部使用。
class People:
__star = "earth" #双下划线开头,约定_star是私有属性
def __init__(self,name):
self.name = name
#接口函数/访问函数
def get_star(self):
return self.__star
p1 = People("alex")
print(p1.get_star()) #通过接口函数来获取相应的属性

浙公网安备 33010602011771号