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())         #通过接口函数来获取相应的属性

  

posted @ 2018-07-12 10:29  范先生学习python之旅  阅读(93)  评论(0)    收藏  举报