对象和类
# 什么是对象
Python 里的所有数据都是以对象形式存在的,无论是简单的数 字类型还是复杂的代码模块。然而,Python 特殊的语法形式巧妙地将实现对象机制的大量 细节隐藏了起来。输入 num = 7 就可以创建一个值为 7 的整数对象,并且将这个对象赋值 给变量 num。事实上,在 Python 中,只有当你想要创建属于自己的对象或者需要修改已有 对象的行为时,才需要关注对象的内部实现细节。
# 使用class定义类
下面我们简单定义一个类,并使用这个类
class Person():
pass
one = Person()
2
3
4
下面我们来使用构造函数,构造函数这里初始化了一个类,然后设置了值
class Person():
def __init__(self,name):
self.name = name
one = Person("小游")
print(one.name)
--------
小游
2
3
4
5
6
7
8
# 继承
只需要在类的括号里面加上父类的对象就可以实现继承了
class Father():
name = "父类"
def say(self):
print(self.name)
class Person(Father):
pass
one = Person()
one.say()
--------
父类
2
3
4
5
6
7
8
9
10
11
12
# 覆盖方法
在子类中,可以覆盖任何父类的方 法,包括 init()
class Father():
name = "父类"
def say(self):
print(self.name)
class Person(Father):
name = '子类'
def say(self):
print(self.name)
one = Person()
one.say()
----------
子类
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加新方法
子类还可以添加父类中没有的方法。
class Father():
name = "父类"
def say(self):
print(self.name)
class Person(Father):
name = '子类'
def say(self):
print(self.name)
def hello(self):
print("hello")
one = Person()
one.hello()
------
hello
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用super来使用父方法
class Father():
name = "父类"
def say(self):
print(self.name)
class Person(Father):
name = '子类'
def say(self):
super().say()
def hello(self):
print("hello")
one = Person()
one.say()
---------
子类
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用属性对特性进行设置和访问
有一些面向对象的语言支持私有特性。这些特性无法从对象外部直接访问,我们需要编写 getter 和 setter 方法对这些私有特性进行读写操作。
Python 不需要 getter 和 setter 方法,因为 Python 里所有特性都是公开的,使用时全凭自觉。 如果你不放心直接访问对象的特性,可以为对象编写 setter 和 getter 方法。但更具 Python 风格的解决方案是使用属性(property) 。
当我们尝试访问name时会自动调用get_name方法
class Duck():
def __init__(self, input_name):
self.hidden_name = input_name
def get_name(self):
print('inside the getter')
return self.hidden_name
def set_name(self, input_name):
print('inside the setter')
self.hidden_name = input_name
name = property(get_name, set_name)
one = Duck("小鸭子")
one.name
--------
inside the getter
2
3
4
5
6
7
8
9
10
11
12
13
14
15
另外一个方式就是使用修饰符,下面这个方式和上面是一样的
class Duck():
def __init__(self, input_name):
self.hidden_name = input_name
@property
def name(self):
print('inside the getter')
return self.hidden_name
@name.setter
def name(self, input_name):
print('inside the setter')
self.hidden_name = input_name
one = Duck("小鸭子")
one.name
2
3
4
5
6
7
8
9
10
11
12
13
14
与直接访问特性相比,使用 property 还有一个巨大的优势:如果你改变了某个特性的定义, 只需要在类定义里修改相关代码即可,不需要在每一处调用修改。
# 使用名称保护代码私有性
前面的 Duck 例子中,为了隐藏内部特性,我们曾将其命名为 hidden_name。其实,Python 对那些需要刻意隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)
class Duck():
def __init__(self, input_name):
self.__name = input_name
@property
def name(self):
print('inside the getter')
return self.__name
@name.setter
def name(self, input_name):
print('inside the setter')
self.__name = input_name
one = Duck("小鸭子")
one.name
2
3
4
5
6
7
8
9
10
11
12
13
14
如果我们想访问私有变量,则会报错
print(one.__name)
------
Traceback (most recent call last):
File "E:/CODE/Python/入门/main.py", line 15, in <module>
print(one.__name)
AttributeError: 'Duck' object has no attribute '__name'
2
3
4
5
6
# 方法类型
在类的定义中, self 作为第一个参数的方法都是实例方法(instance method)。它们在 创建自定义类时最常用。实例方法的首个参数是 self,当它被调用时,Python 会把调用该 方法的对象作为 self 参数传入
与之相对,类方法(class method)会作用于整个类,对类作出的任何改变会对它的所有实 例对象产生影响。在类定义内部,用前缀修饰符 @classmethod 指定的方法都是类方法。与 实例方法类似,类方法的第一个参数是类本身。在 Python 中,这个参数常被写作 cls,因 为全称 class 是保留字,在这里我们无法使用。下面的例子中,我们为类 A 定义一个类方 法来记录一共有多少个类 A 的对象被创建:
class A():
count = 0
def __init__(self):
A.count += 1
def exclaim(self):
print("I'm an A!")
@classmethod
def kids(cls):
print("A has", cls.count, "little objects.")
easy_a = A()
breezy_a = A()
wheezy_a = A()
A.kids()
-------------
A has 3 little objects.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面的代码中,我们使用的是 A.count(类特性),而不是 self.count(可能是对象 的特性)。在 kids() 方法中,我们使用的是 cls.count,它与 A.count 的作用一样。
静态方法如下,我们可以直接调用,不需要初始化
class A():
@staticmethod
def kids():
print("这个是静态方法")
A.kids()
-----
这个是静态方法
2
3
4
5
6
7
8
# 鸭子类型
Python 对实现多态(polymorphism)要求得十分宽松,这意味着我们可以对不同对象调用 同名的操作,甚至不用管这些对象的类型是什么。
class Quote():
def __init__(self, person, words):
self.person = person
self.words = words
def who(self):
return self.person
def says(self):
return self.words + '.'
class QuestionQuote(Quote):
def says(self):
return self.words + '?'
class ExclamationQuote(Quote):
def says(self):
return self.words + '!'
hunter = Quote('小游', "我敲代码")
print(hunter.who(), '说:', hunter.says())
hunter = QuestionQuote('小游', "我敲代码")
print(hunter.who(), '问:', hunter.says())
hunter = ExclamationQuote('小游', "我敲代码")
print(hunter.who(), '说:', hunter.says())
----------
小游 说: 我敲代码.
小游 问: 我敲代码?
小游 说: 我敲代码!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 特殊方法
class Word():
def __init__(self, text):
self.text = text
def equals(self, word2):
return self.text.lower() == word2.text.lower()
first = Word('ha')
second = Word('HA')
third = Word('eh')
print(first.equals(second))
---------
True
2
3
4
5
6
7
8
9
10
11
12
13
我们成功定义了 equals() 方法来进行小写转换并比较。但试想一下,如果能通过 if first == second 进行比较的话岂不更妙?这样类会更自然,表现得更像一个 Python 内置的类。
class Word():
def __init__(self, text):
self.text = text
def __eq__(self, other):
return self.text.lower() == other.text.lower()
first = Word('ha')
second = Word('HA')
third = Word('eh')
print(first==second)
----------
True
2
3
4
5
6
7
8
9
10
11
12
13
太神奇了!是不是如同魔术一般?仅需将方法名改为 Python 里进行相等比较的特殊方法名 eq() 即可。表 6-1 和表 6-2 列出了最常用的一些魔术方法
__eq__(self, other) self == other
__ne__(self, other) self != other
__lt__(self, other) self < other
__gt__(self, other) self > other
__le__(self, other) self <= other
__ge__(self, other) self >= other
# 和数学有关的魔术方法
__add__(self, other) self + other
__sub__(self, other) self - other
__mul__(self, other) self * other
__floordiv__(self, other) self // other
__truediv__(self, other) self / other
__mod__(self, other) self % other
__pow__(self, other) self ** other
# 其他种类的魔术方法
__str__(self) str(self)
__repr__(self) repr(self)
__len__(self) len(self)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 组合
如果你想要创建的子类在大多数情况下的行为都和父类相似的话(子类是父类的一种特 殊情况,它们之间是 is-a 的关系),使用继承是非常不错的选择。建立复杂的继承关系确 实很吸引人,但有些时候使用组合(composition)或聚合(aggregation)更加符合现实的 逻辑(x 含有 y,它们之间是 has-a 的关系)。
class Bill():
def __init__(self, description):
self.description = description
class Tail():
def __init__(self, length):
self.length = length
class Duck():
def __init__(self, bill, tail):
self.bill = bill
self.tail = tail
def about(self):
print('描述:', bill.description, ' 长度:',
tail.length)
tail = Tail('描述信息')
bill = Bill('长度信息')
duck = Duck(bill, tail)
duck.about()
------
描述: 长度信息 长度: 描述信息
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面这个代码其实就是把两个类组合到一个Duck这个大类里去了
# 何时使用类和对象而不是模块
有一些方法可以帮助你决定是把你的代码封装到类里还是模块里。
- 当你需要许多具有相似行为(方法)但不同状态(特性)的实例时,使用对象是最好的 选择。
- 类支持继承,但模块不支持。
- 如果你想要保证实例的唯一性,使用模块是最好的选择。不管模块在程序中被引用多少 次,始终只有一个实例被加载。(对 Java 和 C++ 程序员来说,如果读过 Erich Gamma 的《设 计模式:可复用面向对象软件的基础》,可以把 Python 模块理解为单例。)
- 如果你有一系列包含多个值的变量,并且它们能作为参数传入不同的函数,那么最好 将它们封装到类里面。举个例子,你可能会使用以 size 和 color 为键的字典代表一张 彩色图片。你可以在程序中为每张图片创建不同的字典,并把它们作为参数传递给像 scale() 或者 transform() 之类的函数。但这么做的话,一旦你想要添加其他的键或者 函数会变得非常麻烦。为了保证统一性,应该定义一个 Image 类,把 size 和 color 作 为特性,把 scale() 和 transform() 定义为方法。这么一来,关于一张图片的所有数据 和可执行的操作都存储在了统一的位置。
- 用最简单的方式解决问题。使用字典、列表和元组往往要比使用模块更加简单、简洁且 快速。而使用类则更为复杂。
命名元组
命名元组是元组的子类,你既可以通过名称(使用 .name)来访问其中的值,也可以通过 位置进行访问(使用 [offset]),我们使用命名元组来实现DUCK类
from collections import namedtuple
Duck = namedtuple('Duck', 'bill tail')
duck = Duck('wide orange', 'long')
print(duck)
Duck(bill='wide orange', tail='long')
print(duck.bill)
print(duck.tail)
-----------
Duck(bill='wide orange', tail='long')
wide orange
long
2
3
4
5
6
7
8
9
10
11
12
13
我们甚至可以使用字典来构造命名元祖
from collections import namedtuple
Duck = namedtuple('Duck', 'bill tail')
parts = {'bill': 'wide orange', 'tail': 'long'}
duck2 = Duck(**parts)
print(duck2)
---------
Duck(bill='wide orange', tail='long')
2
3
4
5
6
7
8
命名元组是不可变的,但你可以替换其中某些域的值并返回一个新的命名元组:
from collections import namedtuple
Duck = namedtuple('Duck', 'bill tail')
parts = {'bill': 'wide orange', 'tail': 'long'}
duck2 = Duck(**parts)
duck3 = duck2._replace(bill="参数1",tail="参数2")
print(duck2)
print(duck3)
-------
Duck(bill='wide orange', tail='long')
Duck(bill='参数1', tail='参数2')
2
3
4
5
6
7
8
9
10
11
我们可以向字典中添加新的值,但是无法对命名元祖进行操作。
使用命名元组的好处:
- 它无论看起来还是使用起来都和不可变对象非常相似;
- 与使用对象相比,使用命名元组在时间和空间上效率更高;
- 可以使用点号(.)对特性进行访问,而不需要使用字典风格的方括号;
- 可以把它作为字典的键