Python 面向对象

1. 面向对象编程介绍

2. 类的定义及 self 的理解

3. “魔法”方法

4. 类属性和实例属性

5. 类方法和静态方法

6. 私有属性、私有化、属性 property

7. 继承

8. 多态

 

 

1. 面向对象编程介绍

面向对象和面向过程都是解决问题的一种思路。

  • 面向过程:面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑
  • 面向对象:数据与功能(即函数)绑定到一起,进行封装,以增强代码的模块化和重用性,这样能够减少重复代码的编写过程,提高开发效率。

面向对象编程的两个重要概念:类和对象

  • 类是具有相同属性和行为的事物的统称(或统称为抽象)。
  • 类是抽象的,在使用的时候通常会找到这个类的一个具体的存在来使用。
  • 一个类可以找到多个对象。

对象

  • 某一个具体事物的存在,在现实世界中可以是看得见摸得着的。
  • 可以直接使用。

类和对象之间的关系:类就是创建对象的模板。

b7b00137639945ae56dcfb17d46d3b3f.png  

类也是对象

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立,但 Python 中的类还远不止如此,类同样也是一种对象,只要你使用关键字 class,Python 解释器在执行的时候就会在内存中创建一个对象。

这个对象(类对象)拥有创建对象(实例对象)的能力,但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

  • 你可以将它赋值给一个变量。
  • 你可以拷贝它。
  • 你可以为它增加属性。
  • 你可以将它作为函数参数进行传递。

 

2. 类的定义及 self 的理解

类(Class)由3个部分组成:

  1. 类的名称:类名
  2. 类的属性:一组数据
  3. 类的方法:允许对数据进行操作的方法(行为)

db0ff573ef61659c29ac006b045c5d15.png  

定义类并创建对象

  • 定义类时有 2 种方式:新式类和经典类。其区别为:
    • 凡是继承了 object 的类都是新式类;没有继承的则是经典类。

    • Python3 中都是新式类(默认继承 object);经典类目前在 Python3 中基本没有应用。

    • Python2 中继承 object 的是新式类;没有父类的是经典类。

  • 类名的命名规则按照“大驼峰”。

示例:定义一个 Car 类

 1 >>> # 定义类
 2 >>> class Car:  
 3 ...     # 定义方法
 4 ...     def move(self):
 5 ...         print("车在奔跑")
 6 ...
 7 ...     def stop(self):
 8 ...         print("刹车")
 9 ...
10 >>> # 创建一个对象,并用变量 bmw 来保存它的引用
11 >>> bmw = Car()
12 >>> bmw.color = "red"  # 给对象添加属性
13 >>> bmw.move()
14 车在奔跑
15 >>> bmw.stop()
16 刹车
17 >>> print(bmw.color)
18 red
19 >>> print(bmw)  # 查看对象的内存地址
20 <__main__.Car object at 0x03108358>
  • bmw = Car(),这样就产生了一个 Car 的实例对象,它拥有属性(数据)和方法(函数)。
  • 第一次使用 bmw.color = "red" 时表示给 bmw 这个对象添加属性,如果后面再出现 bmw.color = ... 时表示对该属性进行修改。
  • 当需要创建一个对象时,就是用一个模具,来创造一个实物。

self 理解

  • 所谓的 self,可以理解为自己,即当前对象的内存地址。
  • 某个对象调用其方法时,python 解释器就会把这个对象作为第一个参数传递给 self,所以开发者只需要传递后面的参数即可。

 

3.  “魔法”方法

在 Python 中,方法名如果是 __xxx__() 的,那么就是有特殊的功能,叫做“魔法”方法。

__init__():构造方法

class 类名:
    # 初始化方法,用来完成一些默认设定
    def __init__(self):
        pass
  • __init__(self) 方法在创建一个对象时默认被调用,不需要手动调用。
  • __init__(self) 方法中默认有1个参数名称为 self,如果在创建对象是传递了两个实参,那么在 __init__(self) 中除了 self 作为第一个形参外还需要两个形参,例如 __init__(self, x, y)。
  • __init__(self) 中的 self 参数,不需要开发者传递,python 解释器会自动把当前的对象引用传递进去

示例:

 1 class Car:
 2 
 3      # 添加固定值的属性
 4 #    def __init__(self):
 5 #        self.name = "bmw"
 6 #        self.color = "red"
 7     
 8     # 在创建对象时传参作为属性值
 9     def __init__(self, name, color):
10         self.name = name
11         self.color = color
12         
13     def move(self):
14         print("{}的{}在奔跑".format(self.color, self.name))
15         
16         
17 bmw = Car("宝马", "红色")
18 bmw.move()  # 红色的宝马在奔跑

 

__del__():析构方法

  • 当创建一个对象时,python 解释器默认调用 __init__() 方法。
  • 当删除一个对象时,python 解释器也会默认调用一个方法,即 __del__()。

对象的引用计数

  • 当有1个变量保存了对象的引用时,此对象的引用计数就会加1。
  • 当使用 del 删除变量指向的对象时,如果对象的引用计数不为1,比如3,那么此时只会让这个引用计数减1,即变成2,当再次调用 del 时,变为1,如果再调用一次 del,此时才是真的把对象进行删除,才会调用 __del__() 方法。
 1 class Animal:
 2 
 3     def __init__(self, name):
 4         self.name = name
 5         
 6     def __del__(self):
 7         print("'{}'对象要被干掉了".format(self.name))
 8         
 9 
10 dog = Animal("哈皮狗")
11 del dog  # '哈皮狗'对象要被干掉了
12 
13 cat1 = Animal("蓝猫")  # 对象引用计数为1
14 cat2 = cat1  # 对象引用计数为2
15 cat3 = cat2  # 对象引用计数为3
16 
17 del cat3  # 对象引用计数为3-1=2
18 del cat2  # 对象引用计数为2-1=1
19 del cat1  # '蓝猫'对象要被干掉了 

 

__new__() 方法

示例1:

 1 class Animal:
 2 
 3     def __new__(cls, name):  # cls后的形参个数需与__init__()的self后的形参个数保持一致
 4         print("这是new方法")
 5         return object.__new__(cls)  # 返回Animal的实例对象
 6 
 7     def __init__(self, name):
 8         print("这是init方法")
 9         self.name = name
10 
11     def __del__(self):
12         print("%s has benn killed" % self.name)
13     # 程序结束时也会自动被执行
14 
15 
16 a = Animal("dog")

执行结果:

这是new方法
这是init方法
dog has benn killed

示例2:若__new__()方法没有返回值

 1 >>> class Animal:
 2 ...     def __init__(self):
 3 ...             print("init方法")
 4 ...     def __new__(cls):
 5 ...             print("new方法")
 6 ...
 7 >>> a = Animal()
 8 new方法  # 注意,未执行init方法
 9 >>> print(a)
10 None  # 未创建出实例对象

总结:

  • 当创建对象时,会默认先调用 __new__() 方法,再调用 __init__() 方法
  • __new__() 方法是在生成对象之前的动作,至少需要一个参数 cls(代表要实例化的类),此参数在实例化时由 python 解释器自动提供。
  • __new__() 方法必须要有返回值,返回实例化出来的实例对象,即实例对象的生成是在__new__()中return所返回的。这点在自己实现 __new__() 方法时要特别注意,可以 return 父类 __new__() 的实例,或者直接是 object 的 __new__() 的实例。
  • __init__() 方法有一个参数 self,就是这个 __new__() 返回的实例,即__init__()是在对象生成之后完善对象的属性/功能。__init__() 在 __new__() 的基础上可以完成一些其他初始化的动作,__init__() 不需要返回值。 

 

__str__() 方法

当使用 print() 输出对象的时候,只要自己定义了 __str__() 方法,那么就会打印在这个方法中 return 的数据。

 1 class Car:
 2 
 3     def __init__(self, name, color):
 4         self.name = name
 5         self.color = color
 6 
 7     def __str__(self):
 8         msg = "嘿,我的车款是%s,颜色是%s" % (self.name, self.color)
 9         return msg
10 
11 
12 bmw = Car("宝马", "红色")
13 print(bmw)  # 嘿,我的车款是宝马,颜色是红色

 

4. 类属性和实例属性

  • 在前面的示例中接触到的就是实例属性(对象属性)。
  • 而类属性就是类对象所拥有的属性,它被所有的类对象和实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量类似。
  • 对于公有的类属性,在类外可以通过类对象和实例对象访问
  • 对于实例属性,无法通过类对象访问

示例:类属性

1 >>> class People:
2 ...     name = "Tom"  # 类属性
3 ...
4 >>> p = People()
5 >>> print(p.name)  # 通过实例对象访问类属性
6 Tom
7 >>> print(People.name)  # 通过类对象访问类属性
8 Tom

示例:实例属性

 1 >>> class People:
 2 ...     def __init__(self):
 3 ...             self.address = "shenzhen"
 4 ...
 5 >>> p = People()
 6 >>> print(p.address)  # 只能通过实例对象访问实例属性
 7 shenzhen
 8 >>> print(People.address)  # 无法通过类对象访问实例属性
 9 Traceback (most recent call last):
10   File "<stdin>", line 1, in <module>
11 AttributeError: type object 'People' has no attribute 'address'

类属性与实例属性的修改

  • 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。
  • 如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
 1 >>> class People:
 2 ...     name = "Tom"
 3 ...
 4 >>> p = People()
 5 >>> p.name = "Jerry"  # 实则修改实例属性
 6 >>> print(p.name)  # 实例属性屏蔽掉同名的类属性
 7 Jerry
 8 >>> print(People.name)  
 9 Tom
10 >>> del p.name  # 删除实例属性,即删除 Jerry,恢复 Tom
11 >>> print(p.name)
12 Tom
13 >>> People.name = "Jack"  # 在类外,通过类对象修改类属性
14 >>> print(People.name)
15 Jack
16 >>> print(p.name)
17 Jack

 

5. 类方法和静态方法

类方法

  • 类方法是类对象所拥有的方法,需要用修饰器 @classmethod 来标识其为类方法。
  • 对于类方法,第一个参数必须是类对象,一般以 cls 作为第一个参数(可以用其他名称,但业内习惯以“cls”命名)。
  • 类方法能够通过实例对象和类对象去访问。
 1 >>> class People:
 2 ...     country = "China"
 3 ...     @classmethod
 4 ...     def getCountry(cls):
 5 ...             return cls.country
 6 ...
 7 >>> p = People()
 8 >>> p.getCountry()  # 通过实例对象访问类方法
 9 'China'
10 >>> People.getCountry()  # 通过类对象访问类方法
11 'China'

静态方法

需要通过修饰器 @staticmethod 进行修饰,静态方法可以不定义形参。

当既不需要访问实例属性和实例方法,也不需要访问类属性和类方法时,就可以使用静态方法。

 1 >>> class People:
 2 ...     country = "China"
 3 ...     @staticmethod
 4 ...     def getCountry():
 5 ...         return People.country
 6 ...
 7 >>> People.getCountry()  # 通过类对象访问静态方法
 8 'China'
 9 >>> p = People()
10 >>> p.getCountry()  # 通过实例对象访问静态方法
11 'China'

总结

从类方法、实例方法以及静态方法的定义形式可以看出:

  • 类方法的第一个参数是类对象 cls,那么通过 cls 引用的必定是类对象的属性和方法。
  • 实例方法的第一个参数是实例对象 self,那么通过 self 引用的是实例对象的属性或方法。
  • 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。

 

6. 私有属性、私有化、属性 property

私有属性

如果有一个对象,当需要对其进行修改属性时,有 2 种方法:

  1. 对象名.属性名 = 数据  ——> 直接修改。
  2. 对象名.方法名()  ——> 间接修改,能够制定修改规则。

为了更好的保存属性安全,即不能随意修改,一般的处理方式是第 2 种:将属性定义为私有属性,并添加一个可以调用的方法,供调用。

  • 私有的属性,不能通过外部对象直接访问,但是可以通过方法访问;私有的方法,不能通过对象直接访问。
  • 一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用。
  • Python 中没有像 C++ 中用 public 和 private 这些关键字来区别公有属性和私有属性。它是以属性命名方式来区分,如果在属性名前面加了两个下划线 '__',则表明该属性是私有属性(方法也是一样,方法名前面加了两个下划线的话表示该方法是私有的)。

示例:对私有的实例属性进行修改

 1 class People:
 2 
 3     def __init__(self, name):
 4         self.__name = name
 5         
 6     def getName(self):
 7         return self.__name
 8         
 9     def setName(self, newName):
10         if len(newName) > 5:
11             self.__name = newName
12         else:
13             print("error:名字长度需要大于5位")
14             
15             
16 xiaoming = People("xiaoming")
17 xiaoming.setName("xiao")  # error:名字长度需要大于5位
18 print(xiaoming.getName())  # xiaoming
19 xiaoming.setName("xiaodan")
20 print(xiaoming.getName())  # xiaodan

示例:对私有的类属性进行修改

 1 class People:
 2 
 3     __country = "China"  # 私有的类属性
 4     
 5     @classmethod
 6     def getCountry(cls):
 7         print(cls.__country)
 8         
 9     @classmethod
10     def setCountry(cls, country):
11         cls.__country = country 
12         
13 
14 People.getCountry()  # China
15 People.setCountry("Fairy")
16 People.getCountry()  # Fairy

私有化

  • xx:公有变量。
  • _xx:单前置下划线,私有化变量,表示 from module import * 时禁止导入,而 import module 时可以导入。
  • __xx:双前置下划线,私有化属性或方法,无法在类的外部直接访问,也无法被子类继承。
  • __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ ,不要自己发明这样的名字。
  • xx_:单后置下划线,用于避免与 Python 关键词的冲突。

 示例:

 1 class Person:
 2     
 3     def __init__(self, name, age, hobby):
 4         self.name = name
 5         self._age = age
 6         self.__hobby = hobby
 7         
 8     def showPerson(self):
 9         print(self.name)
10         print(self._age)
11         print(self.__hobby)
12         
13     def dowork(self):
14         self._work()
15         self.__off()
16         
17     def _work(self):
18         print("_work")
19         
20     def __off(self):
21         print("__off")
22         
23 
24 class Student(Person):
25 
26     def changeStudent(self, name, age, hobby):
27         self.name = name
28         self._age = age
29         self.__hobby = hobby
30 
31     def showStudent(self):
32         print(self.name)
33         print(self._age)
34         print(self.__hobby)
35 
36     @staticmethod
37     def testBug():
38         _Bug.showBug()
39         
40 
41 # 模块内可以访问,当 from module import * 时,则不导入
42 class _Bug:
43     
44     @staticmethod
45     def showBug():
46         print("showBug")
47         
48         
49 s = Student("jack", 25, "football")
50 s.showPerson()  # 子类可以通过父类方法访问父类的私有属性
51 print("-------------------")
52 # s.showStudent()  子类无法继承和直接访问父类的__hobby,因此会报错
53 s.changeStudent("rose", 21, "piano")
54 s.showPerson()  # rose
55 print("-------------------")
56 s.showStudent()  # rose;此时访问不报错,是因为子类创建了 __hobby 变量
57 print("-------------------")
58 Student.testBug()       

执行结果:

E:\python_pra>python test.py
jack
25
football
-------------------
rose
21
football
-------------------
rose
21
piano
-------------------

总结:

  • 父类中属性名为 __名 的,子类无法继承也无法直接访问(可以通过父类方法间接访问)。
  • 如果在子类中向 __名 赋值,那么会在子类中定义的一个与父类相同名字的属性。
  • _名的变量、函数、类在使用 from xxx import * 时都不会被导入。

名字重整

通过 name mangling 机制就可以访问私有的属性或方法(即名字重整,目的就是以防子类意外重写基类的方法或者属性,如:_Class__object)。

属性 property

使用方式一: 变量名 = property(get方法, set方法)

 1 class Money:
 2 
 3     def __init__(self):
 4         self.__money = 0
 5         
 6     def setMoney(self, value):
 7         if isinstance(value, int):
 8             self.__money = value
 9             
10     def getMoney(self):
11         return self.__money
12         
13     # 该变量名供赋值或取值使用     
14     money = property(getMoney, setMoney)
15         
16 m = Money()
17 
18 # 传统方式:调用方法进行赋值和取值
19 m.setMoney(100)
20 print(m.getMoney())  # 100
21 
22 # 使用property方式,相当于把方法进行了封装,开发者在对属性设置数据的时候更方便
23 m.money = 200  # 相当于调用了m.setMoney(200)
24 print(m.money)  # 相当于调用了getMoney(),结果为200

使用方式二:使用装饰器

 1 class Money:
 2 
 3     def __init__(self):
 4         self.__money = 0
 5     
 6     # 注意两个方法名相同,并供赋值或取值使用
 7     # 相当于get方法
 8     @property
 9     def money(self):
10         return self.__money
11     
12     # 相当于set方法
13     @money.setter
14     def money(self, value):
15         if isinstance(value, int):
16             self.__money = value
17         
18         
19 m = Money()
20 
21 m.money = 200  
22 print(m.money)  # 200

 

7. 继承

在程序中,继承描述的是事物之间的所属关系

例如在现实生活中猫和狗都属于动物,在程序中则可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承自狗,如下所示:

0a8d0e2a0b7ce88ee27d65d9a899fecb.png  

注意:私有属性/方法不会被子类继承。

单继承

 1 class Animal:
 2 
 3     def __init__(self, name="动物", color="白色"):
 4         self.__name = name
 5         self.color = color
 6         
 7     def __test(self):
 8         print(self.__name)
 9         print(self.color)
10         
11     def test(self):
12         print(self.__name)
13         print(self.color)
14         
15 
16 # 子类 Dog,继承自父类 Animal
17 class Dog(Animal):
18     
19     def dogTest1(self):
20         # 无法继承父类的私有属性
21         # print(self.__name)
22         print(self.color)
23         
24     def dogTest2(self):
25         # 无法继承父类的私有方法
26         # self.__test()
27         self.test()
28         
29 
30 a = Animal()
31 print(a.color)  # 白色
32 
33 d = Dog(name="旺财", color="黄色")
34 d.dogTest1()  # 黄色
35 d.dogTest2()  # 旺财,黄色

多继承

所谓多继承,即子类有多个父类,并且具有它们的特征。

 1 # 父类A
 2 class A:
 3     def printA(self):
 4         print("---A---")
 5 
 6 # 父类B  
 7 class B:
 8     def printB(self):
 9         print("---B---")
10         
11 # 子类C,继承自父类A、B     
12 class C(A, B):
13     def printC(self):
14         print("---C---")
15         
16 
17 c = C()
18 c.printA()  # 子类调用父类A的方法
19 c.printB()  # 子类调用父类B的方法
20 c.printC()  

问:在多继承中,如果多个父类中,有一个同名的方法,那么通过子类去调用的时候,调用哪个?

 1 class base:
 2     def test(self):
 3         print("---base---")
 4 
 5 # 继承base类
 6 class A(base):
 7     def test(self):
 8         print("---A---")
 9 
10 # 继承base类  
11 class B(base):
12     def test(self):
13         print("---B---")
14 
15 
16 # 继承A、B类
17 class C(A, B):
18     pass
19         
20 
21 c = C()
22 c.test()
23 print(C.__mro__)  # 可以查看C类的对象搜索方法时的先后顺序

输出结果:

---A---
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.base'>, <class 'object'>)

重写父类的方法

所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。

 1 class base:
 2     def say(self):
 3         print("---Im base---")
 4 
 5 
 6 class A(base):
 7     def say(self):
 8         print("---Im A---")
 9 
10 
11 a = A()        
12 a.say()  # ---Im A---

 调用父类的方法

如果子类中存在与父类相同名字的方法,那么可以使用如下方法来调用父类的该同名方法。

 1 class base:
 2     def say(self):
 3         print("---Im base---")
 4 
 5 
 6 class A(base):
 7     def say(self):
 8         # 调用父类的同名方法
 9         super().say()
10 
11 
12 a = A()        
13 a.say()  # ---Im base---

 

8. 多态

  • 所谓多态,就是定义时的类型和运行时的类型不一样,此时就成为多态。
  • 多态的概念是应用于 Java 和 C# 这一类强类型语言中,而 Python 崇尚“鸭子类型”。

示例:Python 伪代码实现 Java 或 C# 的多态

 1 class base(object):
 2     def show(self):
 3         print('base.show')
 4 
 5 
 6 class A(base):
 7     def show(self):
 8         print('A.show')
 9 
10 
11 class B(base):
12     def show(self):
13         print('B.show')
14 
15 
16 # 由于在Java或C#中定义函数参数时,必须指定参数的类型,如base
17 # 为了让Func函数既可以执行A对象的show方法,又可以执行B对象的show方法,所以,定义了一个A和B类的父类base
18 # 而实际传入的参数是:A对象和B对象
19 def Func(base obj):
20     "Func函数需要接收一个base类型或者base子类的类型"
21     print obj.show()
22 
23 a_obj = A()
24 Func(a_obj) # 在Func函数中传入A类的对象a_obj,执行a的show方法,结果:a.show
25 
26 b_obj = B()
27 Func(b_obj) # 在Func函数中传入B类的对象b_obj,执行b的show方法,结果:b.show

 示例:Python的 “鸭子类型”

 1 class base(object):
 2     def show(self):
 3         print('base.show')
 4 
 5 
 6 class A(base):
 7     def show(self):
 8         print('A.show')
 9 
10 
11 class B(base):
12     def show(self):
13         print('B.show')
14 
15 
16 # 形参为 obj 即可
17 def Func(obj):
18     "传入的obj实例对象具有show()方法即可"
19     obj.show()
20 
21 a_obj = A()
22 Func(a_obj)  # A.show
23 
24 b_obj = B()
25 Func(b_obj)  # B.show

 

posted @ 2020-02-20 20:12  Juno3550  阅读(162)  评论(0编辑  收藏  举报