类
我们先前看过两种编程模式 - 命令式(使用语句,循环和函数)和 函数式(使用纯函数,高阶函数和递归)。
另一个非常流行的范例是面向对象编程(OOP)。
对象是使用类来创建的,而这些类实际上是 OOP 的模具。
这个类描述了这个对象是什么,但是和对象本身是分开的。换句话说,一个类可以被描述为一个对象的蓝图,描述或定义。
您可以使用相同的类作为创建多个不同对象的蓝图。
类是使用关键字 class 和一个包含类方法的缩进块创建的。
下面是一个简单的类和它的对象的例子
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
rover = Cat("dog-colored", 4)
stumpy = Cat("brown", 3)
print("felix:",felix.__dict__) #dict是用来存储对象属性的一个字典,其键为属性名,值为属性的值.
print("rover:",rover.__dict__)
print("stumpy:",stumpy.__dict__)
这段代码定义了一个名为 Cat 的类,它有两个属性:color 和 legs。
然后这个类被用来创建这个类的3个独立的对象
__ init __
_ _ init __ 方法是一个类中最重要的方法。
这是在创建类的实例(对象)时使用类名称作为函数调用的。
所有的方法都必须以 self 作为自己的第一个参数,虽然它没有被明确地传递,但是 Python 为自己添加了自变量;
在调用方法时,不需要包含它。在一个方法定义中,self 指的是调用该方法的实例
注意: init 是两个下划线, 不是一个下划线 _ init _
类的实例具有属性,这些属性是与它们相关联的数据片段。
在这个例子中,Cat 实例具有属性 color 和 legs。这些可以通过在一个实例之后加一个点和属性名来访问。
在 __ init__ 方法中,可以使用 self.attribute 来设置实例属性的初始值。
例如:
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
print(felix.color)
ginger
在上面的例子中,__ init__ 方法接受两个参数并将它们分配给对象的属性。init 方法被称为类构造函数
方法
类可以定义方法来为其添加功能。
请记住,所有的方法必须有 self 作为他们的第一个参数。
这些方法使用与属性相同的点语法进行访问。
例如:
class Dog:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print("Woof!")
fido = Dog("Fido", "brown")
print(fido.name)
fido.bark()
Fido
Woof!
类还可以具有通过在类的主体内分配变量而创建的类属性。这些可以从类的实例或类本身访问。
例如:
class Dog:
legs = 4
def __init__(self, name, color):
self.name = name
self.color = color
fido = Dog("Fido", "brown")
print(fido.legs)
print(Dog.legs)
4
4
类属性由类的所有实例共享
尝试访问未定义实例的属性会导致 AttributeError。这也适用于你调用未定义的方法。
例如:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
rect = Rectangle(7, 8)
print(rect.color)
AttributeError: ‘Rectangle’ object has no attribute ‘color’
继承
继承提供了一种在类之间共享功能的方法。
想象几个类,Cat,Dog,Rabbit等。虽然它们在某些方面可能有所不同(只有 Dog 可能有 bark 方法),但它们可能在其他方面相似(都具有 color 和 name 的属性)。
这种相似性可以通过使它们全部从包含共享功能的超类 Animal 中继承来表示。
要从另一个类继承一个类,请将超类名放在类名后面的括号中
class Animal:
def __init__(self, name, color):
self.name = name
self.color = color
class Cat(Animal):
def purr(self):
print("Purr...")
class Dog(Animal):
def bark(self):
print("Woof!")
fido = Dog("Fido", "brown")
print(fido.color)
fido.bark()
brown
Woof!
从另一个类继承的类称为子类。
被继承的类被称为超类。
如果一个类继承了另一个具有相同属性或方法的类,它的属性和方法将覆盖它们
class Wolf:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print("Grr...")
class Dog(Wolf):
def bark(self):
print("Woof")
husky = Dog("Max", "grey")
husky.bark()
Woof
在上面的例子中,Wolf 是超类,Dog 是子类
继承也可以是间接的。一个类B继承类A,而类C也可以继承类B
class A:
def method(self):
print("A method")
class B(A):
def another_method(self):
print("B method")
class C(B):
def third_method(self):
print("C method")
c = C()
c.method()
c.another_method()
c.third_method()
A method
B method
C method
但是,不允许循环继承
super 函数是一个与父类继承相关的函数。它可以用来在对象的超类中找到具有特定名称的方法。
例如:
class A:
def spam(self):
print(1)
class B(A):
def spam(self):
print(2)
super().spam()
B().spam()
2
1
super().spam() 是调用超类的 spam 方法
魔术方法
魔术方法是在名称的开始和结尾都有双下划线的特殊方法。
到目前为止,我们唯一遇到的是 __ init__,但还有其他几个。
它们被用来创建不能用普通方法表示的功能。
它们的一个常见用途是运算符重载。
这意味着为自定义类定义运算符,允许使用 + 和 * 等运算符。
例子中魔术方法是 __ add__ 重载 +
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
first = Vector2D(5, 7)
second = Vector2D(3, 9)
result = first + second
print(result.x)
print(result.y)
8
16
__ add__ 方法允许为我们的类中的 + 运算符定义自定义行为。
正如你所看到的,它添加了对象的相应属性并返回一个包含结果的新对象。
一旦定义了,我们就可以将这个类的两个对象相加
常见的魔术方法:
__sub__ 对应 -
__mul__ 对应 *
__truediv__ 对应 /
__floordiv__ 对应 //
__mod__ 对应 %
__pow__ 对应 **
__and__ 对应 &
__xor__ 对应 ^
__or__ 对应 |
注意: 名字前后是双下划线
表达式 x + y 被翻译成 x.__ add__(y)。
然而,如果 x 没有实现 __ add__,并且 x 和 y 的类型不同,则调用 y.__ radd__(x)。
对于刚刚提到的所有魔法方法,都有等价的方法。
例如:
class SpecialString:
def __init__(self, cont):
self.cont = cont
def __truediv__(self, other):
line = "=" * len(other.cont)
return "\n".join([self.cont, line, other.cont])
spam = SpecialString("spam")
hello = SpecialString("Hello world!")
print(spam / hello)
spam
============
Hello world!
在上面的例子中,我们为我们的类 SpecialString 定义了除法操作
Python 也为比较运算提供了魔术方法。
__lt__ 对应 <
__le__ 对应 <=
__eq__ 对应 ==
__ne__ 对应 !=
__gt__ 对应 >
__ge__ 对应 >=
如果 __ ne__ 没有被实现,它将返回 __ eq__ 相反的结果。
其他比较运算符之间没有其他关系。
例如:
class SpecialString:
def __init__(self, cont):
self.cont = cont
def __gt__(self, other):
for index in range(len(other.cont)+1):
result = other.cont[:index] + ">" + self.cont
result += ">" + other.cont[index:]
print(result)
spam = SpecialString("spam")
eggs = SpecialString("eggs")
spam > eggs
>spam>eggs
e>spam>ggs
eg>spam>gs
egg>spam>s
eggs>spam>
可以为重载操作符定义任何自定义行为
有几个神奇的方法使类像容器一样行事。
__len__ 对应 len()
__getitem__ 对应 获取索引
__setitem__ 对应 分配索引值
__delitem__ 对应 删除索引值
__iter__ 对应 迭代对象(例如for循环)
__contains__ 对应 in
还有很多其他的魔术方法,我们不会在这里介绍,比如将 __ call__ 作为函数调用对象,__ int__,__ str__ 等等,将对象转换为内建类型。
例如:
import random
class VagueList:
def __init__(self, cont):
self.cont = cont
def __getitem__(self, index):
return self.cont[index + random.randint(-1, 1)]
def __len__(self):
return random.randint(0, len(self.cont)*2)
vague_list = VagueList(["A", "B", "C", "D", "E"])
print(len(vague_list))
print(len(vague_list))
print(vague_list[2])
print(vague_list[2])
6
7
D
C
我们已经重写了类 VagueList 的 len() 函数来返回一个随机数。
索引函数还根据表达式从列表中返回一个范围内的随机项
对象生命周期
对象的生命周期由对象的创建,操作和销毁几个部分组成。
对象的生命周期
第一阶段是它所属的类的定义。
下一个阶段是调用 __ init__ 时实例的实例化。内存被分配来存储实例。
在调用 __ init__ 方法之前,Python首先调用 _ new_ 方法。
这之后,对象就可以使用了
其他代码则可以通过调用对象和访问其属性来与对象交互。
最终,对象会完成使用,并可以被销毁
当一个对象被销毁时,分配给它的内存被释放,并可用于其他目的。
当引用计数达到零时,就会发生对象的破坏。引用计数是引用一个对象的变量和其他元素的数量。
如果什么都没有引用它(它的引用计数为零),什么都不能与它交互,所以它可以安全地删除。
在某些情况下,两个(或更多)对象只能被彼此引用,因此也可以被删除。
del 语句会将对象的引用计数减少一个,这通常会导致删除。
del 语句的魔术方法是__ del__。
不再需要的对象删除的过程称为垃圾收集。
总之,当一个对象的引用计数被分配一个新的名字或者放在一个容器(列表,元组或者字典)中时,它的引用计数会增加。
当用 del 删除对象的引用计数时,它的引用计数会减少,引用被重新分配或引用超出作用域。当一个对象的引用计数达到零时,Python 会自动删除它。
例如:
a = 42 # 创建对象 <42>
b = a # 计数器计数增加
c = [a] # 计数器计数增加
del a # 计数器计数减少
b = 100 # 计数器计数减少
c[0] = -1 # 计数器计数减少
数据隐藏
面向对象编程的一个关键部分是封装,它涉及将相关的变量和函数打包到一个简单易用的对象中 - 一个类的实例。
一个相关的概念是数据隐藏,它指出一个类的实现细节应该被隐藏,并且为那些想要使用这个类的用户提供一个干净的标准接口。
在其他编程语言中,这通常使用私有方法和属性来完成,这些私有方法和属性阻止对类中某些方法和属性被外部访问。
Python 略有不同。没有对任何一个阶级的部分进行任意的限制。因此,没有办法强制一个方法或属性是严格私密的
但是,有些方法可以阻止人们访问某个类的某些部分
弱的私有方法和属性在开头只有一个下划线。
这表示它们是私有的,不应该被外部代码使用。但是,它大多只是一个约定,并不会阻止外部代码访问它们。
它唯一的实际效果是 from 模块名 import * 不会导入以单个下划线开头的变量。
例如:
class Queue:
def __init__(self, contents):
self._hiddenlist = list(contents)
def push(self, value):
self._hiddenlist.insert(0, value)
def pop(self):
return self._hiddenlist.pop(-1)
def __repr__(self):
return "Queue({})".format(self._hiddenlist)
queue = Queue([1, 2, 3])
print(queue)
queue.push(0)
print(queue)
queue.pop()
print(queue)
print(queue._hiddenlist)
Queue([1, 2, 3])
Queue([0, 1, 2, 3])
Queue([0, 1, 2])
[0, 1, 2]
在上面的代码中,hiddenlist 属性被标记为私有的,但仍然可以在外部代码中访问。
__ repr_ 魔术方法用于实例的字符串表示
强私有方法和属性在名称的开始处有一个双下划线。这导致他们的名字错位,这意味着他们不能从类外访问。
这样做的目的不是确保它们保持私有,而是为了避免错误,如果存在具有相同名称的方法或属性的子类时。
名称 错位 方法仍然可以在外部访问,但是以不同的名称访问。 Spam 类的 __private 方法可以通过 _Spam__private 方法外部访问。
例如:
class Spam:
__egg = 7
def print_egg(self):
print(self.__egg)
s = Spam()
s.print_egg()
print(s._Spam__egg)
print(s.__egg)
7
7
AttributeError: 'Spam' object has no attribute '__egg'
Python 通过内部更改名称来包含类名来保护这些成员
类方法
到目前为止,我们所看到的对象的方法被一个类的实例所调用,然后被传递给方法的 self 参数。
类方法是不同的 - 它们被一个类所调用,类方法传递的 参数是 cls 。
类方法用 classmethod 装饰器标记。
例如:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
@classmethod
def new_square(cls, side_length):
return cls(side_length, side_length)
square = Rectangle.new_square(5)
print(square.calculate_area())
25
new_square 是一个类方法,在类上调用,而不是在类的实例上调用。它返回类 cls 的新对象
从技术上说,参数 self 和 cls 只是惯例; 他们可以改变为其他任何东西。然而,它们是普遍被遵循的,所以坚持使用它们是明智的
静态方法
静态方法与类方法类似,只是它们没有任何额外的参数。
它们用 staticmethod 装饰器标记。
例如:
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@staticmethod
def validate_topping(topping):
if topping == "pineapple":
raise ValueError("No pineapples!")
else:
return True
ingredients = ["cheese", "onions", "spam"]
if all(Pizza.validate_topping(i) for i in ingredients):
pizza = Pizza(ingredients)
除了可以从类的一个实例调用它们之外,静态方法的行为与纯函数类似
静态方法是绑定到类而不是类的对象的方法。它不需要任何实例来执行某些工作。它可以使用类名进行调用。
根据这个定义,最有可能成为静态方法的选项是:
def spam(cls, x, y)
属性
属性提供了一种自定义实例属性访问的方法。
它们是通过将属性装饰器放在一个方法上面创建的,这意味着当访问与方法同名的实例属性时,方法将被调用。
属性的一种常见用法是使属性为只读。
例如:
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@property
def pineapple_allowed(self):
return False
pizza = Pizza(["cheese", "tomato"])
print(pizza.pineapple_allowed)
pizza.pineapple_allowed = True
False
AttributeError: can’t set attribute
属性也可以通过定义 setter/getter 函数来设置。
setter 函数设置相应的属性值。
getter 函数获取相应的属性值。
要定义一个 setter,你需要使用一个与属性相同名字的装饰器,后面跟着 .setter。
这同样适用于定义 getter 函数。
例如:
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
self._pineapple_allowed = False
@property
def pineapple_allowed(self):
return self._pineapple_allowed
@pineapple_allowed.setter
def pineapple_allowed(self, value):
if value:
password = input("Enter the password: ")
if password == "Sw0rdf1sh!":
self._pineapple_allowed = value
else:
raise ValueError("Alert! Intruder!")
pizza = Pizza(["cheese", "tomato"])
print(pizza.pineapple_allowed)
pizza.pineapple_allowed = True
print(pizza.pineapple_allowed)
False
Enter the password to permit pineapple: Sw0rdf1sh!
True
下一步是使用类来表示游戏对象
class GameObject:
class_name = ""
desc = ""
objects = {}
def __init__(self, name):
self.name = name
GameObject.objects[self.class_name] = self
def get_desc(self):
return self.class_name + "\n" + self.desc
class Goblin(GameObject):
class_name = "goblin"
desc = "A foul creature"
goblin = Goblin("Gobbly")
def examine(noun):
if noun in GameObject.objects:
return GameObject.objects[noun].get_desc()
else:
return "There is no {} here.".format(noun)
创建了一个 Goblin 类,它继承自 GameObjects 类。
我们还创建了一个新的函数 examine,它返回对象的描述。
现在我们可以添加一个新的 “examine” 动词到我们的字典
verb_dict = {
"say": say,
"examine": examine,
}
: say Hello!
You said "Hello!"
: examine goblin
goblin
A foul creature
: examine elf
There is no elf here.
:
这段代码补充了 Goblin 类 更多的细节
class Goblin(GameObject):
def __init__(self, name):
self.class_name = "goblin"
self.health = 3
self._desc = " A foul creature"
super().__init__(name)
@property
def desc(self):
if self.health >=3:
return self._desc
elif self.health == 2:
health_line = "It has a wound on its knee."
elif self.health == 1:
health_line = "Its left arm has been cut off!"
elif self.health <= 0:
health_line = "It is dead."
return self._desc + "\n" + health_line
@desc.setter
def desc(self, value):
self._desc = value
def hit(noun):
if noun in GameObject.objects:
thing = GameObject.objects[noun]
if type(thing) == Goblin:
thing.health = thing.health - 1
if thing.health <= 0:
msg = "You killed the goblin!"
else:
msg = "You hit the {}".format(thing.class_name)
else:
msg ="There is no {} here.".format(noun)
return msg
: hit goblin
You hit the goblin
: examine goblin
goblin
A foul creature
It has a wound on its knee.
: hit goblin
You hit the goblin
: hit goblin
You killed the goblin!
: examine goblin
A goblin
goblin
A foul creature
It is dead.
:
这只是一个简单的例子。
你可以创造不同的类(例如精灵,兽人,人类),与他们作战,使他们相互对抗,等等