python中简述对象和类的关系_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 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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值