课堂小笔记
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
self是什么?
Python的self就相当于C++的this指针。类是图纸,对象是实例化的东西。由同一个类可以生成无数个对象,这些对象长得都很相似,都是来源于同一个类的属性和方法,当一个对象的方法被调用的时候,对象会将自身作为第一个参数传给这个self参数,接收到这个self的时候Python就知道你是哪一个对象在调用方法了。
为什么都是调用kick方法,为啥结果不一样。因为在调用的时候,a.kick()的这里他有第一个参数,这个参数是隐藏的,就是把a这个对象的标志传进去;那setName()这里的self就接收到了,self.name就会去找到这个a对象的name属性,然后kick把他赋值打印出来。是Python默默在工作的。
(类里面没有定义的属性都可以赋值,因为类是共有属性,对象可以有自己的私有属性)
你听说过Python的魔法方法吗?
被双下划线包围。
__init__(self),称为构造方法,魔力体现在:只要在实例化一个对象的时候,那么这个方法就会在对象被创建的时候自动调用。(传说中的构成函数?)
共有和私有
苍井空是世界的,老婆是自己的!
name mangling 名字改编,名字重整。在Python中定义私有变量只需要在变量名或函数名前加上“__”两个下划线,那么这个函数或者变量就变为私有的了。
可以通过p._Person__name
是伪私有。
课后测试题及答案
测试题:
0. 以下代码体现了面向对象编程的什么特征?
>>> "FishC.com".count('o') 1 >>> [1, 1, 2, 3, 5, 8].count(1) 2 >>> (0, 2, 4, 8, 12, 18).count(1) 0
都可以调用同一个方法,但是结果不一定相同。
答:体现了面向对象编程的多态特征。
1. 当程序员不想把同一段代码写几次,他们发明了函数解决了这种情况。当程序员已经有了一个类,而又想建立一个非常相近的新类,他们会怎么做呢?
非常相近的新类,可以用继承吧,继承原先的类,再自己修改
答:他们会定义一个新类继承已有的这个类,这样子就只需要简单添加和重写需要的方法即可。例如已有龟类,那么如果要新定义一个甲鱼类,我们只需要让甲鱼类继承已有的龟类,然后重写壳的属性为“软的”即可(据说甲鱼的壳是软的)。
2. self参数的作用是什么?
用来接收对象的,可以让Python知道是哪个对象在使用方法和属性
答:绑定方法,据说有了这个参数,Python 再也不会傻傻分不清是哪个对象在调用方法了,你可以认为方法中的 self 其实就是实例对象的唯一标志。
3. 如果我们不希望对象的属性或方法被外部直接引用,我们可以怎么做?
可以使用“__”双下划线来使其私有化,虽然是伪私有,因为还是可以通过_类名__方法/属性 来引用。
答:我们可以在属性或方法名字前边加上双下划线,这样子从外部是无法直接访问到,会显示AttributeError错误。
>>> class Person: __name = '小甲鱼' def getName(self): return self.__name >>> p = Person() >>> p.__name Traceback (most recent call last): File "<pyshell#56>", line 1, in <module> p.__name AttributeError: 'Person' object has no attribute '__name' >>> p.getName() '小甲鱼'
我们把getName方法称之为“访问器”。Python事实上是采用一种叫“name mangling”技术,将以双下划线开头的变量名巧妙的改了个名字而已,我们仍然可以在外部通过“_类名__变量名”的方式访问:
>>> p._Person__name'小甲鱼'
当然我们并不提倡这种抬杠较真粗暴不文明的访问形式……!
4. 类在实例化后哪个方法会被自动调用?
__init__()
答:__init__方法会在类实例化时被自动调用,我们称之为魔法方法。你可以重写这个方法,为对象定制初始化方案。
5. 请解释下边代码错误的原因:
class MyClass: name = 'FishC' def myFun(self): print("Hello FishC!") >>> MyClass.name 'FishC' >>> MyClass.myFun() Traceback (most recent call last): File "<pyshell#6>", line 1, in <module> MyClass.myFun() TypeError: myFun() missing 1 required positional argument: 'self' >>>
没有一个对象传给self参数,
答:首先你要明白类、类对象、实例对象是三个不同的名词
我们常说的类指的是类定义,由于“Python无处不对象”,所以当类定义完之后,自然就是类对象。在这个时候,你可以对类的属性(变量)进行直接访问(MyClass.name)。
一个类可以实例化出无数的对象(实例对象),Python 为了区分是哪个实例对象调用了方法,于是要求方法必须绑定(通过 self 参数)才能调用。而未实例化的类对象直接调用方法,因为缺少 self 参数,所以就会报错。
动动手
0. 按照以下要求定义一个游乐园门票的类,并尝试计算2个成人+1个小孩平日票价。
平日票价100元
周末票价为平日的120%
儿童半票
答案:
class Ticket(): def __init__(self, weekend=False, child=False): self.exp = 100 if weekend: self.inc = 1.2 else: self.inc = 1 if child: self.discount = 0.5 else: self.discount = 1 def calcPrice(self, num): return self.exp * self.inc * self.discount * num >>> adult = Ticket() >>> child = Ticket(child=True) >>> print("2个成人 + 1个小孩平日票价为:%.2f" % (adult.calcPrice(2) + child.calcPrice(1))) 2个成人 + 1个小孩平日票价为:250.00
1. 游戏编程:按以下要求定义一个乌龟类和鱼类并尝试编写游戏。(初学者不一定可以完整实现,但请务必先自己动手,你会从中学习到很多知识的^_^)
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("有一条鱼儿被吃掉了...")