tmaczt 2019-04-06
面向对象编程和函数式编程(面向过程编程)都是程序设计的方法,不过稍有区别。
面向过程编程:
在多函数程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数访问。每个函数都可以具有它们自己的局部数据,将某些功能代码封装到函数中,日后便无需重复编写,仅调用函数即可。从代码的组织形式来看就是根据业务逻辑从上到下垒代码 。
面向对象编程:
面向对象编程中,将函数和变量进一步封装成类,类才是程序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地改变。类和和类的实例(也称对象)是面向对象的核心概念,是和面向过程编程、函数式编程的根本区别。
并不是非要用面向对象编程,要看你的程序怎么设计方便,但是就目前来说,基本上都是在使用面向对象编程。
类的基本用法
面向对象是通过定义class类来定义,这么说面向对象编程就是只使用class类,在class类中有封装,继承的功能,并且还可以构造要传入的参数,方便控制。
案例一
一些专业术语概念,既然有面向对象编程这个高大上的定义了,自然要搭配一些高大上的概念。
类(Class): 用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
实例:也称对象。通过类定义的初始化方法,赋予具体的值,成为一个”有血有肉的实体”。
实例化:创建类的实例的过程或操作。
实例变量:定义在实例中的变量,只作用于当前实例。
类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
数据成员:类变量、实例变量、方法、类方法、静态方法和属性等的统称。
方法:类中定义的函数。
静态方法:不需要实例化就可以由类执行的方法
类方法:类方法是将类本身作为对象进行操作的方法。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对父类的方法进行改写,这个过程也称override。
封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制
继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
多态:根据对象类型的不同以不同的方式进行处理。
类与实例
调用类的三种方法
实例方法
静态方法
静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。它属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)
经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法. 比如更改环境变量或者修改其他类的属性等能用到静态方法. 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难。
类方法
类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)
实际案例
如果要构造一个类,接受一个网站和这个网站的状态码,然后打印出来。就像这样:
这样就是使用实例方法,虽然可以实现,但是有的时候传入的参数并不是(‘langzi’,200)这样的格式,而是(‘langzi-200’)这样的,那该怎么做?首先要把这个拆分,但是要使用实例方法实现起来很麻烦,这个时候就可以使用类方法。
类的特性
封装
封装是指将数据与具体操作的实现代码放在某个对象内部,外部无法访问。必须要先调用类的方法才能启动。
案例
继承
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
比如,我们已经编写了一个名为Animal的class,有一个run方法可以直接打印:
当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run方法:
当子类和父类都存在相同的run方法时,我们说,子类的run覆盖了父类的run,在代码运行的时候,总是会调用子类的run。这样,我们就获得了继承的另一个好处:多态。
多态
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
当我们传入Animal的实例时,run_twice就打印出:
当我们传入Dog的实例时,run_twice就打印出:
当我们传入Cat的实例时,run_twice就打印出:
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:
当我们调用run_twice时,传入Tortoise的实例:
你会发现,新增一个Animal的子类,不必对run_twice做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run方法,这就是多态的意思:
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run方法,而具体调用的run方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice等函数。
总结:继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;
有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收;
旧的方式定义Python类允许不从object类继承,但这种编程方式已经严重不推荐使用。任何时候,如果没有合适的类可以继承,就继承自object类。
魔法方法
在上面有提到除了init之外还有iter,reverse的方法,这里就详细说下除了init初始化还有哪些别的方法。
具体使用
1.doc
说明性文档和信息。Python自建,无需自定义。
2.init
实例化方法,通过类创建实例时,自动触发执行。
3.module__ 和 __class
module 表示当前操作的对象在属于哪个模块。
class 表示当前操作的对象属于哪个类。
这两者也是Python内建,无需自定义。
4.del
析构方法,当对象在内存中被释放时,自动触发此方法。
注:此方法一般无须自定义,因为Python自带内存分配和释放机制,除非你需要在释放的时候指定做一些动作。析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
5.call
如果为一个类编写了该方法,那么在该类的实例后面加括号,可会调用这个方法。
注:构造方法的执行是由类加括号执行的,即:对象 = 类名,而对于call 方法,是由对象后加括号触发的,即:对象 或者 类
可以用Python内建的callable函数进行测试,判断一个对象是否可以被执行。
callable(Student)
运行结果:
True
6.dict
列出类或对象中的所有成员!非常重要和有用的一个属性,Python自建,无需用户自己定义。
7.str
如果一个类中定义了str方法,那么在打印对象时,默认输出该方法的返回值。这也是一个非常重要的方法,需要用户自己定义。
下面的类,没有定义str方法,打印结果是:
8、getitem__、_setitem_、__delitem
取值、赋值、删除这“三剑客”的套路,在Python中,我们已经见过很多次了,比如前面的@property装饰器。
Python中,标识符后面加圆括号,通常代表执行或调用方法的意思。而在标识符后面加中括号,通常代表取值的意思。Python设计了getitem、setitem、delitem这三个特殊成员,用于执行与中括号有关的动作。它们分别表示取值、赋值、删除数据。
也就是如下的操作:
如果有一个类同时定义了这三个魔法方法,那么这个类的实例的行为看起来就像一个字典一样,如下例所示:
9.iter
这是迭代器方法!列表、字典、元组之所以可以进行for循环,是因为其内部定义了 iter这个方法。如果用户想让自定义的类的对象可以被迭代,那么就需要在类中定义这个方法,并且让该方法的返回值是一个可迭代的对象。当在代码中利用for循环遍历对象时,就会调用类的这个iter方法。
普通的类:
返回一个个迭代对象:
最好的方法是使用生成器:
10、len
在Python中,如果你调用内置的len函数试图获取一个对象的长度,在后台,其实是去调用该对象的len方法,所以,下面的代码是等价的:
Python的list、dict、str等内置数据类型都实现了该方法,但是你自定义的类要实现len方法需要好好设计。
11.repr
这个方法的作用和str很像,两者的区别是str返回用户看到的字符串,而repr返回程序开发者看到的字符串,也就是说,repr是为调试服务的。通常两者代码一样。
12.add__: 加运算_sub_: 减运算_mul_: 乘运算_div_: 除运算_mod_: 求余运算 __pow: 幂运算
这些都是算术运算方法,需要你自己为类设计具体运算代码。有些Python内置数据类型,比如int就带有这些方法。Python支持运算符的重载,也就是重写。
13.author作者信息
14.slots
Python作为一种动态语言,可以在类定义完成和实例化后,给类或者对象继续添加随意个数或者任意类型的变量或方法,这是动态语言的特性。例如:
但是!如果我想限制实例可以添加的变量怎么办?可以使slots限制实例的变量,比如,只允许Foo的实例添加name和age属性。
需要提醒的是,slots定义的属性仅对当前类的实例起作用,对继承了它的子类是不起作用的。想想也是这个道理,如果你继承一个父类,却莫名其妙发现有些变量无法定义,那不是大问题么?如果非要子类也被限制,除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
成员保护与访问机制
有些对象你不想外部访问,即使是通过调用类对象也无法访问,那就请认真学完本章节。
私有成员
运行结果:
zhao
如果要在类中调用这个私有成员,可以这么用
运行结果:
使用get-set-del方法操作私有成员
思考: 既然是私有变量,不让外部访问,为何有要在后面调用又改变呢?因为可以对私有变量进行额外的检测,处理,加工等等。比如判断value的值,使用isinstance然后做if-else判断。
使用私有变量可以对内部变量进行保护,外部无法改变,但是可以对它进行检测处理。
这里引申一下私有成员的保护机制,使用__age对私有变量其实就是—>obj._obj__age的样子进行保护,说白了你直接使用obj._obj__age就可以直接调用内部私有变量age了。
Propety装饰器
把类的方法伪装成属性调用的方式,就是把类里面的一个函数,变成一个属性一样的东西~
一开始调用类的方法要使用圆括号,现在变成了属性进行读取设置存储。
举个例子来说明:
常用的调用方法
使用装饰器
当然这种调用方法有些麻烦,每次都是一个一个去实例类与对象,有个更加简单直观的方法。
更加减半的使用property函数
除了使用装饰器的方式将一个方法伪装成属性外,Python内置的builtins模块中的property函数,为我们提供了第二种设置类属性的手段。
通过语句age = property(get_age, set_age, del_age, “年龄”)将一个方法伪装成为属性。其效果和装饰器的方法是一样的。
property函数的参数: