什么是对象
Python中所有的数据都是以对象的形式存在,无论是简单的数字类型还是复杂的代码模块。在Python中,当我们想要创建属于自己的对象或者修改已有对象的行为时,才需要关注对象的内部实现细节。
对象既包含数据(变量,更习惯称之为特性,attribute),也包含代码(函数,也称为方法)。它是某一类具体事物的特殊实例。。例如,整数 7 就是一个包含了加法、乘法之类方法的对 象,这些方法在 2.2 节曾经介绍过。整数 8 则是另一个对象。这意味着在 Python 里,7 和 8都属于一个公共的类,我们称之为整数类。
当要创建一个没有创建过的新对象时,必须首先定义一个类,用以指明该类型的对象包含的内容(特性和方法)。
可以把对象想象成名词,那么方法就是动词。对象代表这一个独立的事物,它的方法则定义了它时如何与其他事物相互作用的。
与模块不同,我们可以同时创建很多同类的对象,它们的特性值可能各不相同。对象就像是包含了代码的超级数据结构。
实用class定义类
类(class)就是制作对象的模具。例如,Python 的内置类 String 可以创建像 'cat' 和 'duck' 这样的字符串对象。在python中想要创建对象,要首先通过class关键字创建一个类。
class Person():
def __init__(self,name):
self.name = name
hunter = Person('Elmer Fudd')
name参数作为对象的特性存储在了对象里,可以直接进行读写
print('The nighty hunter:',hunter.name)
The nighty hunter: Elmer Fudd
其中的__init__()
是python中特殊的对象初始化方法,用于根据类的定义创建实例对象。self参数指向了这个正在被创建的对象本身。当在类声明里定义__init__()
方法时,第一个参数必须为self。name为对象的一个属性。
上面的代码做了以下工作
创建Person类的定义
在内存中实例化(创建)一个新的对象
调用对象的__init__方法,将新创建的对象作为self传入,并将另一个参数('Elmer Fudd')作为那么传
将name的值存入对象
返回这个新的对象
将名字hunter与这个对象关联
这个新对象与任何其他的 Python 对象一样。你可以把它当作列表、元组、字典或集合中的 元素,也可以把它当作参数传递给函数,或者把它做为函数的返回结果
在类的定义中, __init__
并不是必须的,只有当需要区分由该类创建的不同对象时,才需要指定__init__
方法。
继承
继承能够实现代码的有效复用。我们习惯的将原始的类称为父类,超类或者基类,将新的类称作孩子类,子类或衍生类。
通过一个栗子了解继承:
class Car():
def exclaim(self):
print("I'm a Car!")
class Yugo(Car):
pass
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_car.exclaim()
give_me_a_yugo.exclaim()
I'm a Car!
I'm a Car!
覆盖方法
对父类的方法进行覆盖重新后重新生成对象,调用方法
class Car():
def exclaim(self):
print("I'm a Car!")
class Yugo(Car):
def exclaim(self):
print("I'm a Yugo! Much like a Car, but more Yugo-ish.")
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_car.exclaim()
give_me_a_yugo.exclaim()
I'm a Car!
I'm a Yugo! Much like a Car, but more Yugo-ish.
新创建的子类自动继承了父类的所有信息,通过重写父类的方法,能够实现方法的覆盖。
在子类中能够覆盖任何父类的方法,包括__init__()
。下面是修改__init__()
。
我们创建以Person类,在创建两个子类MDPerson和JDPerson:
class Person():
def __init__(self,name):
self.name = name
class MDPerson(Person):
def __init__(self,name):
self.name = name
class JDPerson(Person):
def __init__(self,name):
self.name = name
person = Person('Fudd')
doctor = MDPerson('Fudd')
lawyer = JDPerson('Fudd')
print(person.name)#结果 Fudd
print(doctor.name)#结果Doctor:Fudd
print(lawyer.name)#结果Fudd:Esquire
在上面的栗子中,子类的初始方法__init__()
接受的参数和父类Person一样,但是存储到对象内部name特性的值却不同。
添加新方法
子类可以添加父类没有的方法,当父类调用子类的方法的时候会报错。
class Car():
def exclaim(self):
print("I'm a car")
class Yugo(Car):
def exclaim(self):
print("I'm a Yugo! Much like a Car, but more Yugo-ish")
def need_a_push(self):
print("A little help here?")
give_me_a_car = Car()
give_me_a__yugo = Yugo()
give_me_a__yugo.need_a_push()#结果 A little help here?give_me_a_car.need_a_push()#结果
Traceback (most recent call last): File "G:/pyCharm/weather.py", line 12, in <module> give_me_a_car.need_a_push()AttributeError: 'Car' object has no attribute 'need_a_push'
使用super从父类得到帮助
上面我们完成了在子类中重新覆盖父类的方法,除此之外我们还可以使用super()来调用父类的方法。
下面的小栗子将会定义一个新的类EmailPerson,用于表示有电子邮箱的Person。
class Person():
def __init__(self,name):
self.name = nameclass
EmailPerson(Person):
def __init__(self,name,email):
super().__init__(name)
self.email = email
bob = EmailPerson('BObFraples','bob@frapples.com')
print(bob.name)
BOb Fraples
print(bob.email)
bob@frapples.com
在子类中定义__init__()
方法时候,父类的__init__()
方法会被覆盖。因此,在子类中,父类的初始化方法并不会被自动调用,我们必须显式调用它。以上代码做了:
- 通过super()方法获取了父类Person的定义
- 子类的
__init__()
调用了Person.__init__()
方法。它会自动的将self参数传递给父类。因此,我们只需输入其余的参数即可。在上面的栗子中,Person()能够接受的其余参数是name。 - self.email = email 使子类和父类有了区别
- 创建EmailPerson类对象,然后访问name和email属性
为什么不像下面这样定义 EmailPerson 类呢?
class EmailPerson(Person):
def __init__(self, name, email):
self.name = name
self.email = email
确实可以这么做,但这有悖我们使用继承的初衷。我们应该使用 super() 来让 Person 完成 它应该做的事情,就像任何一个单纯的 Person 对象一样。除此之外,不这么写还有另一个 好处:如果 Person 类的定义在未来发生改变,使用 super() 可以保证这些改变会自动反映 到 EmailPerson 类上,而不需要手动修改。
self的自辩
Python中必须把self设置为实例方法的第一个参数。Python使用self参数来找到正确的对象所包含的特性和方法。
还记得前面例子中的 Car 类吗?再次调用 exclaim() 方法:
car = Car()
car.exclaim()
I'm a Car!
Python 在背后做了以下两件事情:
- 查找car对象所属的类(Car)
- 把car对象作为self参数传给Car类所包含的exclaim()方法
使用属性对特性进行访问和设置
Python里所有特性都是公开的,不过我们可以设置属性对特性进行设置和访问。
注意:我们在这里将property译作属性,将attribute译作特性#
在下面的栗子中,我们定义一个duck类,仅仅包含一个hidden_name特性。我们不希望别人直接访问这两个特性,因此定义两个方法:getter方法和setter方法,并将这两个方法设置为name属性。
class Duck():
def __init__(self,input_name):
self.hidden_name = input_name
def get_name(self):
print('insert the getter')
return self.hidden_name
def set_name(self,input_name):
print('insert the setter')
self.hidden_name = input_name
name = property(get_name,set_name)
fowl = Duck('Howard')
#当我们尝试访问对象的name特性时候,get_name()会被自动调用
print(fowl.name)
#也可以显式的调用get_name()
print(fowl.get_name())
#当对name属性进行赋值的时候,set_name()会被自动调用
fowl.name = 'Daffy'print(fowl.name)
#也可以显式的调用set_name()
fowl.set_name = 'Daffy'
print(fowl.name)
另一种定义属性的方法是使用修饰符(decorator)。下面的栗子会定义两个不同的方法,它们的的名字都叫name(),但包含不同的修饰符:
- @property,用于指示getter方法
- @name.setter,用于指示setter方法
代码:
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
fowl = Duck('Howard')
#我们仍然可以像之前一样访问特性,只是没有了get_name()和set_name()方法。
print(fowl.name)
fowl.name = 'Donald'
print(fowl.name)
注意:#这里我们仍然可以通过fowl.hidden_name 进行读写操作
之前的栗子我们使用name属性指向类中存储的某一特性,除此之外是,属性还可以指向一个计算结果值。我们定义一个circle类,它包含radius特性以及一个计算属性diameter:
class Circle():
def __init__(self,radius):
self.radius = radius
@property
def diameter(self):
return 2 * self.radius
c = Circle(5)
print(c.radius)
#可以像访问特性(例如radius)一样访问属性diameter:
print(c.diameter)
#如果我们没有指定某一特性的setter属性(@diameter.setter),那么无法从类的外部对它的值进行设置。对于只读的特性非常有用。
c.diameter = 20
Traceback (most recent call last):
File "G:/pyCharm/weather.py", line 10, in <module>
c.diameter = 20
AttributeError: can't set attribute
使用名称重整保护私有特性
Python对那些需要刻意隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)。
我们把之前的hidden_name改名为__name:
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
#代码能够正常工作
fowl = Duck('Howard')
fowl.name
fowl.name = 'Donald'
fowl.name
#但是此时我们无法在外部访问__name特性了:
print(fowl.__name)
Traceback (most recent call last):
File "G:/pyCharm/sources/daily.py", line 13, in <module>
print(fowl.__name)
AttributeError: 'Duck' object has no attribute '__name'
这种命名规范本质上并没有把特性变成私有,但 Python 确实将它的名字重整了,让外部 的代码无法使用。名称重整是怎么实现的:
fowl._Duck__name
'Donald'
我们并没有得到 inside the getter,成功绕过了 getter 方法。尽管如我们所 见,这种保护特性的方式并不完美,但它确实能在一定程度上避免我们无意或有意地对特 性进行直接访问。
方法的类型
有些数据(特性)和函数(方法)是类的一部分,还有一些是由类创建的实例的一部分。
在类的定义中,以 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.
上面的代码中,我们使用的是 A.count(类特性),而不是 self.count(可能是对象 的特性)。在 kids() 方法中,我们使用的是 cls.count,它与 A.count 的作用一样。
类定义中的方法还存在着第三种类型,它既不会影响类也不会影响类的对象。它们出现在 类的定义中仅仅是为了方便,否则它们只能孤零零地出现在代码的其他地方,这会影响代 码的逻辑性。这种类型的方法被称作静态方法(static method),用@staticmethod修饰,它既不需要self参数也不需要class参数。
class Coyote():
@staticmethod
def commercial():
print('This CoyoteWeapon has been brought to you by Acme')
#我们甚至不用创建任何Coyote类的对象就可以调用这个方法
Coyote.commercial()
鸭子类型
Python对实现多态(polymorphsim)要求十分宽松,意味着我们可以对不同的对象调用同名的操作,甚至不用管这些对象的类型是什么。
我们来为三个 Quote 类设定同样的初始化方法 init(),然后再添加两个新函数:
- who()返回保存的person字符串的值
- says()返回保存的words字符串的内容,并填上指定的标点符号。
代码如下:
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('Alili',"I'm hunting wabbits")
print(hunter.who(),"says:",hunter.says())
#Alili says: I'm hunting wabbits.
hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
print(hunted1.who(), 'says:', hunted1.says())
#Bugs Bunny says: What's up, doc?
hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
print(hunted2.who(),"says:",hunted2.says())
#Daffy Duck says: It's rabbit season!
我们不需要改变 QuestionQuote 或者 ExclamationQuote 的初始化方式,因此没有覆盖它们 的 __init__()
方法。Python 会自动调用父类 Quote 的初始化函数 __init__()
来存储实例 变量 person 和 words,这就是我们可以在子类 QuestionQuote 和 ExclamationQuote 的对象 里访问 self.words 的原因
三个不同版本的 says() 为上面三种类提供了不同的响应方式,这是面向对象的语言中多态 的传统形式。Python 在这方面走得更远一些,无论对象的种类是什么,只要包含 who() 和 says(),你便可以调用它。##我们再来定义一个 BabblingBrook 类,它与我们之前的猎人猎 物(Quote 类的后代)什么的没有任何关系
class BabblingBrook():
def who(self):
return 'Brook'
def says(self):
return 'Baabble'
brook = BabblingBrook()
#现在对不同对象执行who()和says()方法,其中有一个(brook)与其他类型额对象毫无关联:
def who_says(obj):
print(obj.who(),"syas:",obj.says())
who_says(hunter)
#Alili syas: I'm hunting wabbits.
who_says(hunted1)
#Bugs Bunny syas: What's up, doc?
who_says(hunted2)
#Daffy Duck syas: It's rabbit season!
who_says(brook)
#Brook syas: Baabble
特殊方法
Python中的特殊方法的名称以双下划线(__)开头和结束。没错,我们已经见过其中一个:__ init__
,它根据类的定义以及传入的参数对新创建的对象进行初始化。
假设你有一个简单的 Word 类,现在想要添加一个 equals() 方法来比较两个词是否一致, 忽略大小写。也就是说,一个包含值 'ha' 的 Word 对象与包含 'HA' 的是相同的。
下面的代码是第一次尝试,创建一个普通方法 equals()。self.text 是当前 Word 对象所包 含的字符串文本,equals() 方法将该字符串与 word2(另一个 Word 对象)所包含的字符串 做比较:
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
print(first.equals(third))#False
我们成功定义了 equals() 方法来进行小写转换并比较。但试想一下,如果能通过 if first == second 进行比较的话岂不更妙?这样类会更自然,表现得更像一个 Python 内置的类。 把前面例子中的 equals() 方法的名称改为 __eq__()
:
class Word():
def __init__(self,text):
self.text = text
def __eq__(self,word2):
return self.text.lower() == word2.text.lower()
#创建三个包含不同字符串的对象
first = Word('ha')
second = Word('HA')
third = Word('eh')
#进行比较
print(first == second)#True
print(first == third)#False
这里我们仅仅将方法名修改为 eq()很神奇的就出现了。
和比较相关的魔术方法
方法名 使用
`__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
不仅数字类型可以使用像 +(魔术方法 __add__()
)和 -(魔术方法 __sub__()
)的数学运算 符,一些其他的类型也可以使用。例如,Python 的字符串类型使用 + 进行拼接,使用 * 进 行复制
其他种类的魔术方法
方法名 使用
`__str__(self)` str(self)
`__repr__(self)` repr(self)
`__len__(self)` len(self)
除了 __init__()
外,你会发现在编写类方法时最常用到的是__str__()
,它用于定义如何 打印对象信息。print() 方法,str() 方法都会用到__str__()
。交互式解释器则用 __repr__()
方法输出变量。如果在你的类 既没有定义__str__()
也没有定义 __repr__()
,Python 会输出类似下面这样的默认字符串:
first = Word('ha')
first
<__main__.Word object at 0x1006ba3d0>
print(first)
<__main__.Word object at 0x1006ba3d0>
我们将 `__str__()` 和 `__repr__() `方法都添加到 Word 类里,让输出的对象信息变得更好看些:
class Word():
def __init__(self, text):
self.text = text
def __eq__(self, word2):
return self.text.lower() == word2.text.lower()
def __str__(self):
return self.text
def __repr__(self):
return 'Word("' self.text '")'
first = Word('ha')
first # uses __repr__
Word("ha")
print(first) # uses __str__
ha
组合
如果你想要创建的子类在大多数情况下的行为都和父类相似的话(子类是父类的一种特 殊情况,它们之间是 is-a 的关系),使用继承是非常不错的选择。建立复杂的继承关系确 实很吸引人,但有些时候使用组合(composition)或聚合(aggregation)更加符合现实的 逻辑(x 含有 y,它们之间是 has-a 的关系)。一只鸭子是鸟的一种(is-a),它有一条尾巴 (has-a)。尾巴并不是鸭子的一种,它是鸭子的组成部分。下个例子中,我们会建立 bill 和 tail 对象,并将它们都提供给 duck 使用:
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('This duck has a',bill.description,'bill and a',tail.length,'tail')
tail = Tail('long')
bill = Bill('wide orage')
duck = Duck(bill,tail)
duck.about()
#结果This duck has a wide orage bill and a long tail
注:本文内容来自《Python语言及其应用》欢迎购买原书阅读