第37讲:面向对象编程

一 面向对象编程

1 一些名词

  • OOA:面向对象分析
  • OOP:面向对象编程
  • OOD:面向对象设计

2 self是什么——python的self参数

  • 由来:Python 规定,无论是构造方法还是实例方法,最少要包含一个参数,并没有规定该参数的具体名称,将其命名为 self,只是程序员之间约定俗成的一种习惯。
  • 功能:Python 会自动绑定类方法的第一个参数指向调用该方法的对象。即同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法
  • 简单来说:self代表类的实例,而非类
  • 例如:
    1 class Test:
    2     def prt(self):
    3         print(self)
    4         print(self.__class__)
    5 
    6 t = Test()
    7 t.prt()
    View Code
  • 执行结果:
    1 <__main__.Test instance at 0x100771878>
    2  __main__.Test
    View Code
  • 从执行结果可看出:self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。

 3 python的魔法方法——__init__(self)方法

  • 魔法方法特征:被双下划线包围、自动调用
  • 功能:用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。
  • 语法:
  • 1 def __init__(self,...):
    2     代码块
    • 注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。
  • 特点:
    • __init__() 方法必须包含一个名为 self 的参数,且必须作为第一个参数,还有,在创建类对象时,无需给 self 传参即可。
    • __init__() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割。创建对象时调用类的构造方法的过程中,需要手动传递参数。
    • self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。
  • 注意:
    • 每个类必须有一个构造函数,即使它只依赖于默认构造函数。
    • 当创建没有构造函数的类时,Python会自动创建一个不执行任何操作的默认构造函数
    • 当我们想要把类中需要自动绑定的一些属性通过构造函数来初始化实现时,我们会显示定义类中的构造函数。
    • 只要实例化一个类(创建对象),构造函数就被会自动调用,不管它是人为显式定义的,还是调用的默认的空的构造函数
  • 举例:
     1 class Ball(object):
     2     
     3     def __init__(self,name):
     4         self.name = name
     5     
     6     def kick(self):
     7         print(f"我叫{self.name},该死的,谁踢我......")
     8 
     9 a = Ball()
    10 a.setName('球A')
    11 b = Ball()
    12 b.setName('球B')
    13 c = Ball()
    14 c.setName('土豆')
    15 a.kick()
    16 c.kick()
    class37_1.py

4 名字改编/名字重整(name mangling)——用来区分类中的属性和变量是共有还是私有

  • 名称前没有下划线:默认这些python类中的变量和方法都是公有(public)的,可以通过类对象正常访问;
  • 名称以双下划线“__”开头:表示这些python类中的变量和方法都是私有(private)的,只能在类内部使用,在类外部不能通过类对象进行访问;
    • 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
      • 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
      • 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
      • 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
      • 注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。
    • 这种变形需要注意的问题是:
      • 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
      • 变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
    • 举例:
       1 class Person1(object):
       2     # 属性
       3     name = '小甲鱼'
       4     
       5     def geet_Name(self):
       6         print(self.name)
       7 
       8 class Person2(object):
       9     
      10     __name = '小甲鱼'
      11     
      12     def getName(self):
      13         return self.__name
      14 
      15 
      16 marry1 = Person1()
      17 marry2 = Person2()
      18 
      19 print(marry1.name)
      20 # print(marry2.__name)                        # 该句在运行过程中会报错
      21 print(marry2.getName())
      22 print(marry2._Person2__name)                  # 名字改编/重整
      class37_2.py
  • 名称以单下划线“_”开头的类属性和类方法:通常将他们看作私有(private)的,但是它们可以通过类对象正常访问;
  • 名称前后分别以双下划线开头和结尾:这些类方法都是python内部定义的,用于python内部调用,我们自己定义类属性或者类方法时,禁止使用这种形式。

 

二 课后习题

测试题部分:

0. 以下代码体现了面向对象编程的什么特征?

1 >>> "FishC.com".count('o')
2 1
3 >>> [1, 1, 2, 3, 5, 8].count(1)
4 2
5 >>> (0, 2, 4, 8, 12, 18).count(1)
6 0

答:体现了面向对象编程的多态特征。(我回答的是封装。。。)

1. 当程序员不想把同一段代码写几次,他们发明了函数解决了这种情况。当程序员已经有了一个类,而又想建立一个非常相近的新类,他们会怎么做呢?
答:他们会定义一个新类继承已有的这个类,这样子就只需要简单添加和重写需要的方法即可。

2. self参数的作用是什么?
答:绑定方法,据说有了这个参数,Python 再也不会傻傻分不清是哪个对象在调用方法了,你可以认为方法中的 self 其实就是实例对象的唯一标志。

3. 如果我们不希望对象的属性或方法被外部直接引用,我们可以怎么做?D
答:我们可以在属性或方法名字前边加上双下划线,这样子从外部是无法直接访问到,会显示AttributeError错误。

Python事实上是采用一种叫“name mangling”技术,将以双下划线开头的变量名巧妙的改了个名字而已,我们仍然可以在外部通过“_类名__变量名”的方式访问,当然我们并不提倡这种抬杠较真粗暴不文明的访问形式……


4. 类在实例化后哪个方法会被自动调用?t
答:__init__方法会在类实例化时被自动调用,我们称之为魔法方法。你可以重写这个方法,为对象定制初始化方案。

5. 请解释下边代码错误的原因

 1 class MyClass:
 2         name = 'FishC'
 3         def myFun(self):
 4                 print("Hello FishC!")
 5                 
 6 >>> MyClass.name
 7 'FishC'
 8 >>> MyClass.myFun()
 9 Traceback (most recent call last):
10   File "", line 1, in 
11     MyClass.myFun()
12 TypeError: myFun() missing 1 required positional argument: 'self'
13 >>>

答:首先你要明白类、类对象、实例对象是三个不同的名词。
我们常说的类指的是类定义,由于“Python无处不对象”,所以当类定义完之后,自然就是类对象。在这个时候,你可以对类的属性(变量)进行直接访问(MyClass.name)。
一个类可以实例化出无数的对象(实例对象),Python 为了区分是哪个实例对象调用了方法,于是要求方法必须绑定(通过 self 参数)才能调用。而未实例化的类对象直接调用方法,因为缺少 self 参数,所以就会报错。

 

动动手:

 0. 按照以下要求定义一个游乐园门票的类,并尝试计算2个成人+1个小孩平日票价。

1 平日票价100元
2 周末票价为平日的120%
3 儿童半票

答:面向对象编程的难点在于思维的转换。

我的代码:

 1 class Ticket(object):
 2     
 3     daily = ['Mon','Tue','Wed','Thu','Fri']
 4     weekend = ['Sat','Sun']
 5     
 6     dailyFare = 100
 7     weekendFare = dailyFare * 1.2
 8     childFare = 0
 9     
10     def dayFare(self,day):
11         if day in self.daily:
12             self.adultFare = self.dailyFare
13             self.childFare = self.dailyFare / 2
14         if day in self.weekend:
15             self.adultFare = self.weekendFare
16             self.childFare = self.weekendFare / 2
17         
18         print(f"成人的票价为{self.adultFare}")
19         print(f"小孩的票价为{self.childFare}\n")
20     
21     def allFare(self,adult,child):
22         self.adult = adult
23         self.child = child
24         self.fare = self.adult * self.adultFare + self.child * self.childFare
25         print(f"{self.adult}个成人和{self.child}个儿童的总票价为:{self.fare}")
26 
27 ticket = Ticket()
28 print("请输入今天是星期几,如('Mon','Tue','Wed','Thu','Fri','Sat','Sun'):")
29 day = input("星期:")
30 day_fare = ticket.dayFare(day)
31 adult = int(input("请输入成人的个数:"))
32 child = int(input("请输入小孩的个数:"))
33 all_fare = ticket.allFare(adult,child)
ex37-0.py

小甲鱼的代码:

 1 class Ticket():
 2     
 3     def __init__(self, weekend=False, child=False):
 4         self.exp = 100
 5         if weekend:
 6             self.inc = 1.2
 7         else:
 8             self.inc = 1
 9         if child:
10             self.discount = 0.5
11         else:
12             self.discount = 1
13     
14     def calcPrice(self, num):
15         return self.exp * self.inc * self.discount * num
16 
17 adult = Ticket()
18 child = Ticket(child=True)
19 adultNum = int(input("请输入成人的个数:"))
20 childNum = int(input("请输入小孩的个数:"))
21 fare = adult.calcPrice(adultNum) + child.calcPrice(childNum)
22 print(f"{adultNum}个成人和{childNum}个小孩的平日票价为{fare}")
ex37_02.py

 

1. 游戏编程:按以下要求定义一个乌龟类和鱼类并尝试编写游戏。(初学者不一定可以完整实现,但请务必先自己动手,你会从中学习到很多知识的^_^)

 1 假设游戏场景为范围(x, y)为0<=x<=100<=y<=10
 2 游戏生成1只乌龟和10条鱼
 3 它们的移动方向均随机
 4 乌龟的最大移动能力是2(Ta可以随机选择1还是2移动),鱼儿的最大移动能力是1
 5 当移动到场景边缘,自动向反方向移动
 6 乌龟初始化体力为100(上限)
 7 乌龟每移动一次,体力消耗1
 8 当乌龟和鱼坐标重叠,乌龟吃掉鱼,乌龟体力增加20
 9 鱼暂不计算体力
10 当乌龟体力值为0(挂掉)或者鱼儿的数量为0游戏结束

我的代码:

 1 import random as r
 2 
 3 class Turtle(object):
 4     
 5     boundary_x = [0,10]
 6     boundary_y = [0,10]
 7     turtle = []
 8     
 9     def createTurtle(self):
10         self.turtle_x = r.randint(self.boundary_x[0],self.boundary_x[1])
11         self.turtle_y = r.randint(self.boundary_y[0],self.boundary_y[1])
12         self.turtle = [self.turtle_x,self.turtle_y]
13         # print(f"乌龟所在的位置坐标为({self.turtle[0]},{self.turtle[1]})")
14     
15     def move(self,turtlePhysical):
16         self.turtlePhysical = turtlePhysical
17         self.step = r.choice([-2,-1,1,2])
18         self.i = r.choice([0,1])
19         if self.turtle[self.i] == 0 and self.step > 0 :
20             self.turtle[self.i] = self.turtle[self.i] + self.step
21         elif self.turtle[self.i] == 10 and self.step > 0 :
22             self.turtle[self.i] = self.turtle[self.i] - self.step
23         elif self.turtle[self.i] == 0 and self.step < 0 :
24             self.turtle[self.i] = self.turtle[self.i] - self.step
25         elif self.turtle[self.i] == 10 and self.step < 0 :
26             self.turtle[self.i] = self.turtle[self.i] + self.step
27         else:
28             self.turtle[self.i] = self.turtle[self.i] + self.step
29         # print(f"乌龟所在的位置坐标为({self.turtle[0]},{self.turtle[1]})")
30         self.turtlePhysical[0] -= 1
31         # print(self.turtlePhysical[0])
32 
33 class Fish(object):
34     
35     boundary_x = [0,10]
36     boundary_y = [0,10]
37     fish = []
38     
39     def createFish(self,num):
40         self.num = num
41         i = 0
42         while i < self.num:
43             self.fish_x = r.randint(self.boundary_x[0],self.boundary_x[1])
44             self.fish_y = r.randint(self.boundary_x[0],self.boundary_x[1])
45             self.fish1 = [self.fish_x,self.fish_y]
46             self.fish.append(self.fish1)
47             # print(f"第{i+1}只鱼的位置坐标为({self.fish_x},{self.fish_y})")
48             i += 1
49         # print(self.fish)
50     
51     def move(self,num):
52         self.num = num
53         i = 0
54         while i < self.num:
55             self.step = r.choice([-1,1])
56             self.j = r.choice([0,1])
57             if self.fish[i][self.j] == 0 and self.step > 0:
58                 self.fish[i][self.j] = self.fish[i][self.j] + self.step
59             elif self.fish[i][self.j] == 0 and self.step < 0:
60                 self.fish[i][self.j] = self.fish[i][self.j] - self.step
61             elif self.fish[i][self.j] == 10 and self.step > 0:
62                 self.fish[i][self.j] = self.fish[i][self.j] - self.step
63             elif self.fish[i][self.j] == 10 and self.step < 0:
64                 self.fish[i][self.j] = self.fish[i][self.j] + self.step
65             else:
66                 self.fish[i][self.j] = self.fish[i][self.j] + self.step
67             # print(f"第{i+1}只鱼的位置坐标为({self.fish[i][0]},{self.fish[i][1]})")
68             i += 1
69         # print(self.fish)
70 
71 turtlePhysical = [100]
72 turtle0 = Turtle()
73 fish0 = Fish()
74 fishNum = int(input("请输入共有几条鱼:"))
75 
76 turtle0.createTurtle()
77 fish0.createFish(fishNum)
78 
79 while True:
80     
81     turtle0.move(turtlePhysical)
82     fish0.move(fishNum)
83     
84     def eatFish():
85         
86         for each in range(len(fish0.fish)):
87             if turtle0.turtle[0] == fish0.fish[each][0] and turtle0.turtle[1] == fish0.fish[each][1] :
88                 fish0.fish.pop(each)
89                 turtlePhysical[0] += 20
90         each = 0
91         print(f"乌龟的坐标是:{turtle0.turtle}")
92         print(f"鱼的坐标是{fish0.fish}")
93         print(turtlePhysical[0])
94         print(len(fish0.fish))
95     
96     eatFish()
97     
98     if turtlePhysical[0] == 0 or len(fish0.fish) == 0:
99         break
ex37_1.py

代码很糟糕,建议不要看!!!

小甲鱼的代码:

 1 import random as r
 2 
 3 legal_x = [0, 10]
 4 legal_y = [0, 10]
 5 
 6 class Turtle:
 7     def __init__(self):
 8         # 初始体力
 9         self.power = 100
10         # 初始位置随机
11         self.x = r.randint(legal_x[0], legal_x[1])
12         self.y = r.randint(legal_y[0], legal_y[1])
13 
14     def move(self):
15         # 随机计算方向并移动到新的位置(x, y)
16         new_x = self.x + r.choice([1, 2, -1, -2])
17         new_y = self.y + r.choice([1, 2, -1, -2])
18         # 检查移动后是否超出场景x轴边界
19         if new_x < legal_x[0]:
20             self.x = legal_x[0] - (new_x - legal_x[0])
21         elif new_x > legal_x[1]:
22             self.x = legal_x[1] - (new_x - legal_x[1])
23         else:
24             self.x = new_x
25         # 检查移动后是否超出场景y轴边界
26         if new_y < legal_y[0]:
27             self.y = legal_y[0] - (new_y - legal_y[0])
28         elif new_y > legal_y[1]:
29             self.y = legal_y[1] - (new_y - legal_y[1])
30         else:
31             self.y = new_y        
32         # 体力消耗
33         self.power -= 1
34         # 返回移动后的新位置
35         return (self.x, self.y)
36 
37     def eat(self):
38         self.power += 20
39         if self.power > 100:
40             self.power = 100
41 
42 class Fish:
43     def __init__(self):
44         self.x = r.randint(legal_x[0], legal_x[1])
45         self.y = r.randint(legal_y[0], legal_y[1])
46         
47     def move(self):
48         # 随机计算方向并移动到新的位置(x, y)
49         new_x = self.x + r.choice([1, -1])
50         new_y = self.y + r.choice([1, -1])
51         # 检查移动后是否超出场景x轴边界
52         if new_x < legal_x[0]:
53             self.x = legal_x[0] - (new_x - legal_x[0])
54         elif new_x > legal_x[1]:
55             self.x = legal_x[1] - (new_x - legal_x[1])
56         else:
57             self.x = new_x
58         # 检查移动后是否超出场景y轴边界
59         if new_y < legal_y[0]:
60             self.y = legal_y[0] - (new_y - legal_y[0])
61         elif new_y > legal_y[1]:
62             self.y = legal_y[1] - (new_y - legal_y[1])
63         else:
64             self.y = new_y
65         # 返回移动后的新位置
66         return (self.x, self.y)
67 
68 turtle = Turtle()
69 fish = []
70 for i in range(10):
71     new_fish = Fish()
72     fish.append(new_fish)
73 
74 while True:
75     if not len(fish):
76         print("鱼儿都吃完了,游戏结束!")
77         break
78     if not turtle.power:
79         print("乌龟体力耗尽,挂掉了!")
80         break
81 
82     pos = turtle.move()
83     # 在迭代器中删除列表元素是非常危险的,经常会出现意想不到的问题,因为迭代器是直接引用列表的数据进行引用
84     # 这里我们把列表拷贝给迭代器,然后对原列表进行删除操作就不会有问题了^_^
85     for each_fish in fish[:]:
86         if each_fish.move() == pos:
87             # 鱼儿被吃掉了
88             turtle.eat()
89             fish.remove(each_fish)
90             print("有一条鱼儿被吃掉了...")
ex37_12.py

嗯,我唯一完成的部分就是生成乌龟和鱼的部分,移动也实现了,但是在边界判断上出了问题,还有体力计算也出了问题。总结经验如下:

1 生成10条鱼没必要在一个类里面一次性实现,最好的办法是定义一个类用来生成一条鱼,然后再把该类实例化后,用for循环生成10条鱼就行了

2 改变坐标的时候最好x轴和y轴分开,不要用choice函数一下子全部改变,这样不仅会对后面边检判断造成困难,还会增加程序复杂度,不好理解

3 边界判断处理的不够彻底,还有很多情况没有考虑到,所以程序运行过程中一直会报索引错误

4 对函数返回值应用的还不够熟练,需要加强

 

你可能感兴趣的:(第37讲:面向对象编程)