Program Arcade Games
With Python And Pygame

Chapter 12: 类的入门

类和对象是非常强大的编程工具。它们让编程变得更简单。事实上,你应该已经很熟悉类和兑现的概念了。 一个类是一个对象的“类别”。就像 “人” 或者 “图像image”。一个对象是一个类的具体实例。就像“玛丽”是“人”的一个实例。一个p>

对象有各种属性,比如人的名字,身高,年龄。 对象也有方法。方法定义了对象可以做什么,比如跑,跳,坐。

12.1 为什么要学习类?

视频: 为什么学类?

每个冒险游戏中的角色需要数据: 名字,位置,力量,他们是否举着手臂,它们要去的方向,等等。 再加上角色还有动作do。他们要跑,跳,打,说。

没有类,我们的Python代码需要想这样存储数据:

name = "Link"
sex = "Male"
max_hit_points = 50
current_hit_points = 50

为了让这个角色可以有行为,我们需要把这些数据传递给一个函数:

def display_character(name, sex, max_hit_points, current_hit_points):
    print(name, sex, max_hit_points, current_hit_points)

现在想象创建一个程序,它有一组变量包含了包含游戏中需要的每个角色,怪兽,和物品。然后哦我们需要创造和它们相关的函数。 我们现在已经涉及到了一大堆数据。突然之间这听起来一点都不好玩。

但是等下,还在变得更糟! 随着游戏的扩展,我们的角色可能需要更读的属性。 下面例子中,我么添加了我们bd>max_speed:

name = "Link"
sex = "Male"
max_hit_points = 50
current_hit_points = 50
max_speed = 10

def display_character(name, sex, max_hit_points, current_hit_points, max_speed):
    print(name, sex, max_hit_points, current_hit_points)

在上面的例子中,只有一个函数。但是在大型视频游戏中,我们可能对角色就有上百的函数。每次给角色添加一个新的属性,就需要我们遍历每个函数,给它们都添加这么一个参数。 这会产生 巨大的工作量。 而且我们可能需要把max_speed添加到各种不同哦角色例如各种野兽。 所以我们需要一种更好的办法。我们的程序需要把这些数据属性都打包起来,便于管理。

12.2 定义和创建简单的类简单

视频:定义简单的类

一种更好的管理多个数据属性的办法是 定义一种结果能够包含所有信息。 然后我们能给这些信息的集合一个名字,比如 Character或者Address。通过使用 ,Python和其他现代语言都可以轻松完成。

例如,我们可以定义一个类来表示游戏中的一个角色:

class Character():
    """ This is a class that represents the main character in a game. """
    def __init__(self):
        """ This is a method that sets up the variables in the object. """
        self.name = "Link"
        self.sex = "Male"
        self.max_hit_points = 50
        self.current_hit_points = 50
        self.max_speed = 10
        self.armor_amount = 8

这是另一个例子,我们定义了一个类来包含一个地址所需的数据。:

class Address():
    """ Hold all the fields for a mailing address. """
    def __init__(self):
        """ Set up the address fields. """
        self.name = ""
        self.line1 = ""
        self.line2 = ""
        self.city = ""
        self.state = ""
        self.zip = ""

在上面的代码中, Address类的名字。砸类中的变量,比如namecity, 被称作attributes(属性)或者fields(域)。 (请注意在声明类和函数时的相同和不同)

和函数与变量不同,类的名字应该以大写字母开头。尽管小写字母开头也是可以的,但是不推荐。

def __init__(self):是一个特殊函数,叫做 constructor(构造函数),会在类创建的时候自动运行。我们会待会儿单独讨论下构造函数。

self就像是是my(我的)意思。当在类Address之内是,我么讨论的是我的 name,我的city,等等。 我们不想在Address类的定义之外使用self.来指代一个Address的属性。为什么?因为就像“我的”,它意味着其他说的时候指的完全是另一个人啊!

为了更好的将不同的类的关系可视化,程序员经常会画图表。一张Address类的图标如图例所示12.1。 我们可以看到类的名字和各属性的列表在最上方。每个属性的右边是它的数据类型,比如字符串或者整数。

fig.address_example_3
Figure 12.1: 类的图表

类的代码定义了一个类但是并没又真正地创造它的一个实例。代码告诉计算机一个地址有什么属性和它们的初始值会是多少。 我们并没有真的拥有一个地址。 我们可以定义一个类而不创建,就像我们可以定义一个函数而不调用。 要创建一个类并设置属性,请看下面的例子:

# 创建一个地址
home_address = Address()

# 设置地址的属性
home_address.name = "John Smith"
home_address.line1 = "701 N. C Street"
home_address.line2 = "Carver Science Building"
home_address.city = "Indianola"
home_address.state = "IA"
home_address.zip = "50125"

第二行创建了一个地址类的实例。请注意类的名字Address是如何使用的,以及紧跟的圆括号。 变量名只要符合命名规则都可以。

要设置一个类里的属性,程序必须使用运算符。 这个运算符就是home_address和属性名称之间的句点。 看一下5到10行是如何用点运算符设置每个属性的值的。

在使用类时一个非常常见的错误是忘记是哟的是使用哪一个实例。 如果只创建了一个地址,很自然地会假设计算机知道你所使用的地址 但这不并不是事实。请看虾米的例子:

class Address():
    def __init__(self):
        self.name = ""
        self.line1 = ""
        self.line2 = ""
        self.city = ""
        self.state = ""
        self.zip = ""

# 创建一个地址
my_address = Address()

# 警告! 这并不能设置地址的名字!
name = "Dr. Craven"

# 这也不能设置地址的名字
Address.name = "Dr. Craven"

# 这是可行的
my_address.name = "Dr. Craven"

我们可以创建第二个地址,并同时使用两个实例的属性,如下例所示:

lass Address():
    def __init__(self):
        self.name = ""
        self.line1 = ""
        self.line2 = ""
        self.city = ""
        self.state = ""
        self.zip = ""

# 创建一个地址
home_address = Address()

# 设置地址的属性
home_address.name = "John Smith"
home_address.line1 = "701 N. C Street"
home_address.line2 = "Carver Science Building"
home_address.city = "Indianola"
home_address.state = "IA"
home_address.zip = "50125"

# 创建另一个地址
vacation_home_address = Address()

# 设置地址的属性
vacation_home_address.name = "John Smith"
vacation_home_address.line1 = "1122 Main Street"
vacation_home_address.line2 = ""
vacation_home_address.city = "Panama City Beach"
vacation_home_address.state = "FL"
vacation_home_address.zip = "32407"

print("The client's main home is in " + home_address.city)
print("His vacation home is in " + vacation_home_address.city)

11行创建了Address类的第一个实例; 22行创建了第二个实例。 变量名home_address指向第一个实例,而vacation_home_address指向第二个。

25到30行设置了新的实例的各个属性。32行打印了家庭地址的城市,因为home_address出现在点运算符之前。 33行打印了度假地址因为vacation_home_address出现在点运算符之前。

在例子中,Address被称为是因为它定义了对一个数据对象的新的类别。而变量home_addressvacation_home_address分别指向对象,因为它们指向了 Address类中的具体实例。对象的一个简单定义就是,它是一个类的一个具体实例。例如“Bob”和“Nancy”是Human类的实例。

通过使用www.pythontutor.com我们可以将代码的执行进行视觉化。 这里有三个变量。第一个指向Address类的定义。另外两个指向不同地址的对象和它们的数据。

fig.two_addresses
Figure 12.2: 两个地址

把许多数据的属性放入一个类,让对函数数据的传入和传出便捷不少。在下面的代码中,函数以参数取了一个地址,并将其打印到了屏幕上。 这样就没有必要把每个地址的属性作为参数传递了。

# 打印一个地址到屏幕上
def print_address(address):
    print(address.name)
    # 如果地址里有一个line1,打印它
    if len(address.line1) > 0:
        print(address.line1)
    # 如果地址里有一个line2,打印它
    if len(address.line2) > 0:
        print( address.line2 )
    print(address.city + ", " + address.state + " " + address.zip)

print_address(home_address)
print()
print_address(vacation_home_address)

12.3 给类添加方法

视频:方法

除了属性,类还可以拥有方法。 方法是存在于类中的函数。下面的代码展开了先前Dog类的例子,添加了一个犬吠的方法。

class Dog():
    def __init__(self):
        self.age = 0
        self.name = ""
        self.weight = 0

    def bark(self):
        print("Woof")

方法的定义在6-7行。 类中的方法的定义看起来和函数的定义一模一样。 重要的区别是在第6行添加了一个参数 self。类中的方法的第一个参数必须是self。 这个参数是必须的,尽管函数并不使用它。

西面是在给类创建方法是要牢记的事项:

方法的调用,和引用一个对象的属性相类似。请看下面的代码示例:

my_dog = Dog()

my_dog.name = "Spot"
my_dog.weight = 20
my_dog.age = 3

my_dog.bark()

第一行创建了一条狗。3-5行设置了对象的属性。第7行调用了bark方法。请注意尽管 bark方法有一个参数, self, 调用并没有传递任何东西。这是因为第一个参数是用来指向对象本色的。 在背后,Python做了像这样的一个调用:

# 例子, 实际上并不合法
Dog.bark(my_dog)

如果bark方法需要指向任一属性,它需要使用self作为引用的变量。 例如,我们可以改变Dog的类,让它在狗叫的时候,打印出狗的名字。 在下面的代码中,可以通过点运算符和 self的指向来访问名字这一属性。

    def bark(self):
        print("Woof says", self.name)

属性是形容词,方法是动词。把类画成图大概如图例所示12.3.

fig.dog_2_1
Figure 12.3: 狗的类

12.3.1 避免这个错误

把所有要所有方法内所有东西都放进一个定义。不雅定义两次。 For example:

# 错误的:
class Dog():
    def __init__(self):
        self.age = 0
        self.name = ""
        self.weight = 0

    def __init__(self):
        print("New dog!")

计算机会自动无视第一个__init__,而取最后的定义。我们其实应该这样做:

# 正确的:
class Dog():
    def __init__(self):
        self.age = 0
        self.name = ""
        self.weight = 0
        print("New dog!")

12.3.2 例子:球的类

这段代码可用于Python/Pygame来画一个球。 把所有的参数放入一个类让数据管理变得更简单了。 Ball类的图标如图例所示12.4

fig.ball_2_1
Figure 12.4: 球的类
class Ball():
    def __init__(self):
        # --- 类的属性 ---
        # Ball position
        self.x = 0
        self.y = 0

        # 球的矢量
        self.change_x = 0
        self.change_y = 0

        # 球的尺寸其秋       self.size = 10

        # 球的颜色
        self.color = [255,255,255]

	# --- 类的方法 ---
    def move(self):
        self.x += self.change_x
        self.y += self.change_y

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, [self.x, self.y], self.size )

下面的代码需要放在主循环之前来创建一个球并设置其属性:

theBall = Ball()
theBall.x = 100
theBall.y = 100
theBall.change_x = 2
theBall.change_y = 1
theBall.color = [255,0,0]

这段代码要放在主循环内来移动和绘制球:

theBall.move()
theBall.draw(screen)

12.4 引用

视频:引用

这里将是我们区别真正的程序员和想要成为程序员的地方。 理解类的引用。 下看一下下段代码:

class Person:
    def __init__(self):
        self.name = ""
        self.money = 0

bob = Person()
bob.name = "Bob"
bob.money = 100

nancy = Person()
nancy.name = "Nancy"

print(bob.name, "has", bob.money, "dollars.")
print(nancy.name, "has", nancy.money, "dollars.")

上述代码创建了Person()类的两个实例,使用 www.pythontutor.com我们可以视觉化这两个实例,如图例所示12.5.

fig.two_persons
Figure 12.5: 两个人

上面的代码并无新意。但是下面的代码不一样了:

class Person:
    def __init__(self):
        self.name = ""
        self.money = 0

bob = Person()
bob.name = "Bob"
bob.money = 100

nancy = bob
nancy.name = "Nancy"

print(bob.name, "has", bob.money, "dollars.")
print(nancy.name, "has", nancy.money, "dollars.")

看到第9行的区别了吗区别

使用对象时一个常见的概念错误是,假设变量bob Person的一个对象。这里并不是。变量bob是Person对象的一个象em>引用(reference)。也就是说,它存储了对象所在位置的内存地址,而不是对象本身。

如果bob真的就是对象,那么第9行可以创建一个对象的复制品,这样就会存在两个对象。 程序的输出会变成Bob和Nancy都有100美元。但真正运行的时候,程序的输出其实是这样的:

Nancy has 100 dollars.
Nancy has 100 dollars.

bob所存储的是对对象的一个引用。除了引用, 还可以称它为地址, 指针, 或者. 一个引用是一个内存地址,存储了对象所在的地址。这个地址是个16进制数,如果打印出来,可能是0x1e504之类的。 当第9行运行的时候,地址被复制了而不是地址指向的整个对象。参考图例12.6

fig.ref_example1
Figure 12.6: 类的引用

我们可以在www.pythontutor.com内运行来看看两个变量是如何指向同一个对象的。

fig.one_person
Figure 12.7: 一个人,两个指针

12.4.1 函数和引用

来看下面这段代码。第1行创建了一个取数字作为参数的函数。 变量money是一个变量,它变量了传入数据的赋值皮。添加100并不会改变存储在第10行中bob.money中的数字。因此,13行打印出来的是100,而不是200。

def giveMoney1(money):
    money += 100

class Person:
    def __init__(self):
        self.name = ""
        self.money = 0

bob = Person()
bob.name = "Bob"
bob.money = 100

giveMoney1(bob.money)
print(bob.money)

运行在PythonTutor 中我们可以看到哟两个money变量的实例。一个是复制品,并对于giveMoney1函数来说是局部的变量。

fig.function_references_1
Figure 12.8: 函数的引用

再看下一段额娃的代码。这段代码虎造成bob.money增加,并打印出200。

def giveMoney2(person):
    person.money += 100

giveMoney2(bob)
print(bob.money)

这是为什么?因为person中包含了对象地址的复制品,而不是真正的对象。我们可以把它想成一个银行帐号的数字。 函数有一个银行帐号数字的复制品,而不是整个银行帐号的复制品。所以使用帐号数字的复制品来存储100美元会造成银行帐号的余额上涨。

fig.function_references_2
Figure 12.9: 函数的引用的

数组的工作原理也是一样的。一个函数可以取一个数组(列表)作为一个参数;改变那个数组中的值,会更改调用它的同一个数组。只是数字的地址被复制了,而不是整个数组。

12.4.2 复习题

  1. 创建一个叫Cat的类。给它如下属性:名字,颜色,重量。给它一个方法叫做meow
  2. 创建一个它的实例,设置其属性,并调用meow这个方法
  3. 创建一个叫Monster的类。给它一个名字的属性,和一个整数作为生命值这一属性。创建一个方法叫decreaseHealth,取一个amount作为参数并从生命值中减去它。 在方法之内,打印出动物的名字如果它的生命值小于0。

12.5 构造函数

视频:构造函数

下面列出来的Dog类有一个严重的问题。当我们创造一条狗的时候,默认情况下它是没有名字的。狗应该是有名字的! 我们不能允许狗出生时没有名字。 然后下面的代码就让这发生了,狗永远没有一个名字。

class Dog()
    def __init__(self):
        self.name = ""

my_dog = Dog()

Python不想让这发生。 这也是为什么Python的类有一个特殊的函数,在每一个类的实例创建出拉的时候会自动调用。通过添加一个构造函数,一个程序员可以添加一些代码让每次实例创建时自动运行。 如下面这个构造函数的例子:

class Dog():
    def __init__(self):
        self.name = ""

    # 构造函数
    # 当创建这个类西的实例时会调用类型的类型def __init__(self):
        print("A new dog is born!")

# 这会创造一条狗
my_dog = Dog()

构造函数开始于第6行。它必须以__init__命名。 在init之前有两个下划线,之后也有两个下划线。 一种常见的错误是只用一个。常见

构造函数必须以self作为第一个参数,就像类中的其他方法咿呀。 当程序运行的时候,它会在第十行创建狗的一个对象时打印:
A new dog is born!
,这个__init__函数是自动调用的,信息也会打印到屏幕锁。

构造函数可用于对象数据的初始虎。上面Dog类的例子中允许 在创建狗的对象是name属性留成空白。我们如何阻止这样的情形发生? 许多对象在它们创建的时候就应该有值。构造函数就是这个目的。请看下面的代码:

class Dog():

    # 构造函数
    # 当创建这个类型的实例时会调用
    def __init__(self, new_name):
        self.name = new_name

# 这会创造一条狗
my_dog = Dog("Spot")

# 打印名字,检查是不是已经设定好谁都能够nt(my_dog.name)

# 这一行会报错
# 因为名字没有被传入
herDog = Dog()

第6行中的构造函数想在有了一个额外的参数叫做new_name。 这个函数的值会被用来在第7行设置Dog中的name属性。 现在不会再有无名值狗诞生了。第16行尝试了一下。Python会报出一个错误并不执行。 一个常见的错误是,把__init__函数中的参数的命名和属性名一致,以为这样它们的值寄会自动同步。这是不会发生的。

12.5.1 复习题

  1. 类的名称应该是大写字母还算小写字母开头?
  2. 方法的名称应该是大写还是小写字母打头?
  3. 属性的名称应该是大写哈市小写字母开头?
  4. 在类中哪一项应该先列出来,属性还是方法?
  5. 引用的其他名称叫什么?
  6. 实例变量的另一个名称是什么是
  7. 类的一个实例的另一个名字叫什么?
  8. 创建一个叫Star的类,它会在对下每次创建时打印出“A star is born!”
  9. 创建一个叫Monster的类,属性包括生命值和名字。 添加一个构造函数来通过传入的参数来设定对象的生命值和名字。

12.6 继承

视频:继承

使用类和对象的另一个强大功能就是有能力使用inheritance(继承)。可以通过创建一个类并继承其parent class(父类)的所有属性和方法。

例如,一个程序可能创建了一个类叫Boat,它包含了游戏中表示一艘船的所需属性:

class Boat():
    def __init__(self):
        self.tonnage = 0
        self.name = ""
        self.isDocked = True

    def dock(self):
        if self.isDocked:
            print("You are already docked.")
        else:
            self.isDocked = True
            print("Docking")

    def undock(self):
        if not self.isDocked:
            print("You aren't docked.")
        else:
            self.isDocked = False
            print("Undocking")

来测试我们的代码:

我们的程序也许要一艘潜水艇。我们的潜水艇可以做船能做的一切事情,再加上我们需要一个命令叫做submerge。不用继承的话我们有两个选项。



幸运的是,有一种更好的办法。我么的程序可以创建一个child classes(子类),它会继承所有parent class(父类)的属性和方法。子类之后可以更具自己需要添加新的属性和方法。例如:

class Submarine(Boat):
    def submerge(self):
        print("Submerge!")

第一行非常重要。质押在类生命的时候把Boat放在圆括号之内,我们就自动获取了Boat类中所有的属性和方法。如果我们更新Boat,那么子类Submarine也会自动更新。继承就是这么简单!

下面的代码已经图表化,如图例所示12.10.

fig.person1
Figure 12.10: 类图
class Person():
    def __init__(self):
        self.name = ""

class Employee(Person):
    def __init__(self):
        # 先调用父类的构造函数
        super().__init__()

        # 闲杂设置我们自己的变量
        self.job_title = ""

class Customer(Person):
    def __init__(self):
        super().__init__()
        self.email = ""

john_smith = Person()
john_smith.name = "John Smith"

jane_employee = Employee()
jane_employee.name = "Jane Employee"
jane_employee.job_title = "Web Developer"

bob_customer = Customer()
bob_customer.name = "Bob Customer"
bob_customer.email = "send_me@spam.com"

Person放置在第5行和第13行的括号内,程序员就告诉了计算机Person对于EmployeeCustomer来说是一个父类。 这允许程序能在第17行和第21行设置name这一属性。

方法也可以被继承。任何父类拥有的方法,子类都可以获得。但是如果我们有一个同样名字的方法在子类和父类都存在呢?

我们哟两个选项。我们可以都用super()这个关键词来运行它们。 使用super()紧跟一个点运算符,最终可以让你调用父类版本的方法。

上面的代码展示了第一种选项,使用super来表示我们不经运行子类的构造函数,也运行父类的构造函数。

如果你在给子类写一个方法并想调用一个父类的方法,通常这会是子类方法的第一个语句。注意上述例子是怎么写的。

所有的构造函数需要调用父类的构造函数,因为如果你是一个没有父母的孩子那真是太悲惨了。事实上,有些语言是强行执行这条规则的,但是Python没有。

第二种选项? 方法可以被子类overridden(重写)来提供不同的功能。下面的例子就分别展示了两种选项。 Employee.report重写了Person.report因为它从来不掉用或者运行父类的report方法。Customer的report的确会调用父类,所以它的方法report方法会添加Person没有的新功能。

class Person():
    def __init__(self):
        self.name = ""

    def report(self):
        # 基本报告
        print("Report for", self.name)

class Employee(Person):
    def __init__(self):
        # 先调用父类构造函数
        super().__init__()

        # 现在设置我们的变量
        self.job_title = ""

    def report(self):
        # 这里我们重写了report方法,并这样子做这样子       print("Employee report for", self.name)

class Customer(Person):
    def __init__(self):
        super().__init__()
        self.email = ""

    def report(self):
        # 运行父类的report方法:
        super().report()
        # 现在添加我们自己的东西在末尾
        print("Customer e-mail:", self.email)

john_smith = Person()
john_smith.name = "John Smith"

jane_employee = Employee()
jane_employee.name = "Jane Employee"
jane_employee.job_title = "Web Developer"

bob_customer = Customer()
bob_customer.name = "Bob Customer"
bob_customer.email = "send_me@spam.com"

john_smith.report()
jane_employee.report()
bob_customer.report()

12.6.1 Is-A关系和Has-A关系

类与类之间有两种蛀牙的关系。分别是 “is a”和“has a”关系。

一个父类总是一个比子类更广义更抽象版本的类。 这种父子关系被称作is a关系。 例如,一个父类叫动物可以用一个子类叫狗。 狗类可以有一个子类叫贵宾犬。 另一个例子,海豚is a(是一种)动物。反过来并不成立,一种哺乳动物并不一定是一种海豚。所以海豚类不可能是哺乳动物类的父类。 类似的,一个叫桌子的类不会是一个叫椅子的类的父类,因为一张椅子不是一张桌子。

另一种关系叫has a(有一个)一个em>关系。 这类关系由类的属性来实现。一只狗有一个名字,所以狗类有一个属性叫名字。 相类似地,一个人可以有一条狗,这样可以通过一个叫热的类,其中有一个属性叫狗来实现。 反过来的时候,人的类不能有狗的类来推导,因为这会是一种侮辱。

通过先前的例子我们可以看到:

12.7 静态变量 vs. 实例变量

静态变量和实例变量的区别是令人昆虎的。幸运的是,我们现在不需要完全理解其不同。 但是你坚持写程序,你会遇到的。 所以我们在这只是简单介绍一下。

Python还有一些奇怪的地方让我一直很困惑,尤其是在我发布这本书的前几年。所以你可能会看到一些老的视频里的例子是我搞错的地方。

一个instance variable(实例变量)是我们到目前都砸使用的在种类的变量。每个类的实例得到它自己的值。例如,一个满是人的房间里,每个人有自己的年龄。 有些人的年龄也许是相同的,但是我们依然需要单独来记录它们。

在使用实例变量时,我们不能对着一屋子人说“年龄”。 我们必须要具体声明我们在讨论谁的年龄。同时,如果房间里一个人也没有,那么谈论一个人的年龄也是没有意义的。

而对于static variables(静态变量),对类的每个实例赖斯它的值都是相同的。入股一个实例都不存在,还是会有一个静态便俩的值。 例如,我们会有一个count的静态变量来记录Human类里人的存在。没有人? 值是0,但还是存砸的。

在下面的例子中,ClassA创建了一个实例变量。 ClassB创建了一个静态变量。

# 实例变量的例子
class ClassA():
    def __init__(self):
        self.y = 3

# 静态变量的例子
class ClassB():
    x = 7

# 创建类的实例类a = ClassA()
b = ClassB()

# 两种打印静态变量的方法。印# 第二种方法是更适当的。
print(b.x)
print(ClassB.x)

# 一种打印一个实例变量的方法。
# 第二种方法会产生一个错误,因为我们不知道引用的是哪个实例
print(a.y)
print(ClassA.y)

在上面的例子中,第16行和第17行打印出了静态变量。第17行是适当的方法去这么做。不像之前,我们现在能够在使用静态变量时引用类的名字,而不是一个指向个别实例的变量名字。 因为我们在使用类的名字,我们通过观察第17行就立刻能知道我们在使用静态变量。第16行可能是一个实例变量也可能是一个静态变量,所以为避免混淆第17行的方法更好。

第22行打印出了实例变量,就跟先前的例子一样。 第23行会产生一个错误,是因为每个y的实例都是不一样的,我们并没有告知计算机哪个ClassA的实例是我们所讨论的。

12.7.1 实例变量隐藏起静态变量

这是我不喜欢的Python功能。我们可以同时拥有相同名字的静态变量和实例变量。请看虾米的例子:

# 有静态变量的类
class ClassB():
    x = 7

# 创建一个它的实例的b = ClassB()

# 这会打印出7
print(b.x)

# 这也会打印出7
print(ClassB.x)

# 用类的名字来把x设为一个新的值
ClassB.x = 8

# 这也会打印出8
print(b.x)

# 这虎打印会
print(ClassB.x)

# 用实例把x设为一个新的值。
# 等一下! 事实上,这不会把x设成一个新的值!
# 它会创建一个新的变量,x。
# 这个x是一个实例变量。 The static variable is
# 静态变量也叫x。但是它么是不同的变量。
# 这太搞脑筋了,这样写很不好。写# practice.
b.x = 9

# 这会打印出9
print(b.x)

# 这回打印出8。不是9!!!
print(ClassB.x)

允许实例变量隐藏静态变量这一功能已经让我困惑了好多年!

12.8 复习

12.8.1 选择题小测验

Click here for a multiple-choice quiz.

12.8.2 练习表

Click here for the chapter worksheet.

12.8.3 实验

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.