1.类到底是什么
提到类,将其用作类型的同义词。从很多方面来说,这正是类的定义——一种对象。每个对象都属于特定的类,并被称为该类的实例。
类:类表示一组(或一类)对象,而每个对象都属于特定的类。类的主要任务是定义其实例将包含的方法。
对象:对象由属性和方法组成。属性不过是属于对象的变量,而方法是存储在属性中的函数。相比于其他函数,(关联的)方法有一个不同之处,那就是它总是将其所属的对象作为第一个参数,而这个参数通常被命名为self。
例如,如果你在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。鸟类是一个非常通用(抽象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。你可将“鸟类”视为由所有鸟组成的集合,而“云雀”是其一个子集。一个类的对象为另一个类的对象的子集时,前者就是后者的子类。因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。
在英语日常交谈中,使用复数来表示类,如birds(鸟类)和larks(云雀)。在Python中,约定使用单数并将首字母大写,如Bird和Lark。
通过这样的陈述,子类和超类就很容易理解。但在面向对象编程中,子类关系意味深长,因为类是由其支持的方法定义的。类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法。有鉴于此,要定义子类,只需定义多出来的方法(还可能重写一些既有的方法)。
例如,Bird类可能提供方法fly,而Penguin类(Bird的一个子类)可能新增方法eat_fish。创建Penguin类时,你还可能想重写超类的方法,即方法fly。鉴于企鹅不能飞,因此在Penguin的实例中,方法fly应什么都不做或引发异常。
2.创建自定义类
创建自定义类,下面是一个简单的示例:
__metaclass__ = type # 如果你使用的是Python 2,请包含这行代码
class Person:
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
def greet(self):
print("Hello, world! I'm {}.".format(self.name))
旧式类和新式类是有差别的。现在实在没有理由再使用旧式类了,但在Python 3之前,默认创建的是旧式类。在较旧的Python版本中,要创建新式类,应在脚本或模块开头放置赋值语句__metaclass__ = type,但我不会在每个示例中都显式地包含这条语句。当然,还有其他解决方案,如从新式类(如object)派生出子类。如果你使用的是Python 3,就无需考虑这一点,因为根本没有旧式类了。
新式类与旧式类区别:
object是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
新式类:以object为基类的类,推荐使用
经典类:不以object为基类的类,不推荐
在python3.x中定义类时,如果没有指定父类,会默认使用object作为该类的基类--python3.x中定义的类都是新式类
在python2.x中定义类时,如果没有指定父类,则不会以object作为基类
新式类和经典类在多继承时,如果没有指定父类,则不会以object作为基类
新式类和经典类在多继承时--会影响到方法的搜索顺序
为了保证编写的代码能够同时在python2.x和python3.x运行!今后在定义类时,如果没有父类,建议统一继承自object,例如:
class 类名(object):
pass
上述示例包含三个方法定义,它们类似于函数定义,但位于class语句内。Person当然是类的名称。class语句创建独立的命名空间,用于在其中定义函数。一切看起来都挺好,但你可能想知道参数self是什么。它指向对象本身。那么是哪个对象呢?下面通过创建两个实例来说明这一点。
>>> foo = Person()
>>> bar = Person()
>>> foo.set_name('Luke Skywalker')
>>> bar.set_name('Anakin Skywalker')
>>> foo.greet()
Hello, world! I'm Luke Skywalker.
>>> bar.greet()
Hello, world! I'm Anakin Skywalker.
这个示例澄清了self是什么。对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。我将这个参数命名为self,这非常贴切。实际上,可以随便给这个参数命名,但鉴于它总是指向对象本身,因此习惯上将其命名为self。
显然,self很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象。与以前一样,也可以从外部访问这些属性。
>>> foo.name
'Luke Skywalker'
>>> bar.name = 'Yoda'
>>> bar.greet()
Hello, world! I'm Yoda.
如果foo是一个Person实例,可将foo.greet()视为Person.greet(foo)的简写,但后者的多态性更低。
3.属性、函数和方法
实际上,方法和函数的区别表现在前一节提到的参数self上。方法(更准确地说是关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。
>>> class Class:
... def method(self):
... print('I have a self!')
...
>>> def function():
... print("I don't...")
...
>>> instance = Class()
>>> instance.method()
I have a self!
>>> instance.method = function
>>> instance.method()
I don't...
请注意,有没有参数self并不取决于是否以刚才使用的方式(如instance.method)调用方法。
实际上,完全可以让另一个变量指向同一个方法。
>>> class Bird:
... song = 'Squaawk!'
... def sing(self):
... print(self.song) 6
...
>>> bird = Bird()
>>> bird.sing()
Squaawk!
>>> birdsong = bird.sing
>>> birdsong()
Squaawk!
虽然最后一个方法调用看起来很像函数调用,但变量birdsong指向的是关联的方法bird.sing,这意味着它也能够访问参数self(即它也被关联到类的实例)。
关于面向对象设计的一些思考
将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法。
- 不要让对象之间过于亲密。方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了。
- 慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的bug更难。
- 保持简单。让方法短小紧凑。一般而言,应确保大多数方法都能在30秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。
确定需要哪些类以及这些类应包含哪些方法时,尝试像下面这样做。
(1) 将有关问题的描述(程序需要做什么)记录下来,并给所有的名词、动词和形容词加上标记。
(2) 在名词中找出可能的类。
(3) 在动词中找出可能的方法。
(4) 在形容词中找出可能的属性。
(5) 将找出的方法和属性分配给各个类。
有了面向对象模型的草图后,还需考虑类和对象之间的关系(如继承或协作)以及它们的职责。为进一步改进模型,可像下面这样做。
(1) 记录(或设想)一系列用例,即使用程序的场景,并尽力确保这些用例涵盖了所有的功能。
(2) 透彻而仔细地考虑每个场景,确保模型包含了所需的一切。如果有遗漏,就加上;如果有不太对的地方,就修改。不断地重复这个过程,直到对模型满意为止。
有了你认为行之有效的模型后,就可以着手编写程序了。
面向过程与面向对象比较
面向过程
全局变量1
全局变量2
全局变量3
...
def 函数1():
pass
def 函数2():
pass
def 函数3():
pass
def 函数4():
pass
def 函数5():
pass
面向对象
class 类(object):
属性1
属性2
def 方法1(self):
pass
def 方法2(self):
pass
class 类2(object):
属性3
def 方法3(self):
pass
def 方法4(self):
pass
def 方法5(self):
pass
本文详细介绍了Python中的面向对象编程,包括类的定义、对象的组成、类与对象的关系,以及如何创建自定义类。文章还讨论了新式类与旧式类的区别,并提供了实例解释self参数的作用。最后,作者分享了一些面向对象设计的原则和思考,以及面向对象与面向过程编程的对比。
、新式类与旧式类)&spm=1001.2101.3001.5002&articleId=88045838&d=1&t=3&u=3e5cf199343e47e78cc2328b29b1196a)
290

被折叠的 条评论
为什么被折叠?



