Python学习笔记(七)对象和类

什么是对象

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语言及其应用》欢迎购买原书阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容