1.面向对象编程与Python
把有形和无形的事物抽象成“对象”,如书本 乐器,自然语言 时间等。
编程解决现实中问题,第一步就是将现实世界中的对象和类如实反映到程序中。将抽象后的数据和函数封装在一起,就构成了类。一个类被定义好后,它就可以被实例化为一个具体的对象---就像一些Python自带的库函数一样。
Python是一门纯粹的面向对象的语言。在python中,一切皆对象。数字、字符串、元组、列表、字典、函数、方法、类、模块等都是对象。
2.Python中的类
类是对一组具有相同属性与方法的对象的抽象。一个类有一些列的属性和方法,在python中,可以把属性看做类内定义的一个或几个变量(比如列表中的元素),而方法则相当于类内定义的一系列函数(如列表的append函数)。
ls=[] # 定义一个空列表 ls.append(2) print(ls) # 输出 [2]
我们可以自己定义类
class Journalist: """ Take this as an example """ def __init__(self,name): self.name=name def get_name(self): return self.name def speed(self,speed): d={} d[self.name]=speed return d
上面代码:第一行class Journalist 类似之前用 def 声明函数一样,是在声明一个类,其关键词为class。通常,类的名称要用大写字母开头-----如果是两个单词组合的话,最好每个单词都用大写字母开头,便于提高代码可读性,如HongKongJournalist。
类里面的代码,定义的就是这个类的 方法 ,方法的定义方式和一半的函数几乎相同。
区别:所有的函数都包括一个参数 self。注意:类的所有方法,其参数列表都必须包括self,并且默认作为第一个参数。这里可以理解为C++中的this指针。
def _init_(self,name)这个方法比较特殊----命名方式很特比,开头和结尾都是双下划线,这就是这个类的构造函数,又叫初始化函数。初始化:让一个类的实例对象在生成的时候有一个基本的状态。在Python的类中,要用类构件一个实例对象,就必须调用初始化函数。
在初始化函数中,self.name=name的含义就是要建立实例的一个属性。这个属性的名字是name,C++定义的类中可以声明成员变量-----而我们已经知道python中的变量是不用声明的,因此在python中我们使用这样直接赋值的方式来建立实例的属性。
def get_name(self) 和 del color(self,color)是类的另外两个方法---除了第一个函数参数必须是self之外,它们的定义昂视根一般的函数完全一样,像self.name这样的调用方法,只能在类中使用。
3.建立类的实例对象
class Journalist: """ Take this as an example """ def __init__(self, name): self.name = name def get_name(self): return self.name def speed(self, speed): d = {} d[self.name] = speed return d if __name__ == "__main__": western=Journalist('Wallace') print(western.name) name=western.get_name() print(name) his_speed=western.speed(100) print(his_speed)
4.类的方法详细介绍
在上面例子中,初始化函数的self作为默认参数,虽然在定义方法时要显式写出来,但在调用是不需要给它传值。而name则需要传值------Journalist('Wallace')就是为初始化函数中的name参数传值。(“传值”的说法不是很严谨——python只允许传引用。)
self作用:是我们所建立的实例对象本身。当用实例调用方法时,解释器会把实例传递给方法,因此不需要显示给self传参。
在调用方法时,可通过给方法传递参数改变实例对象的属性的值——his_speed=western.speed(100)。
通过类建立一个实例对象,再通过实例来调用它的属性和方法。
5.类的属性与数据
注意是类的属性而非实例对象的属性,类的属性又叫静态变量 或 静态数据。等价于C++这样写:
#include<string> using std::string; class Journalist { public: static string name; }; Journalist::name="Wallace";
class Journalist: """ Take this as an example """ name = 'Wallace' height=[] print(Journalist.name) Journalist.speed=100 print(Journalist.speed) hongkong=Journalist() print(hongkong.name) hongkong.name="Zhangbaohua" print(hongkong.name) print(Journalist.name) hongkong.height.append(160) print(hongkong.height) print(Journalist.height)
结果如右
当类中变量引用的是可变对象的时候,类属性和实例属性都能直接修个这个对象,从而影响另一方的值。
通过类增加的属性可以影响到实例对象。
给实例对象添加的属性无法影响到类。
6.关于方法的跟多细节
通过要用实例对象来调用方法,对于下面这个类,用对象来调用方法。
class Elder: def get_name(self): print("Toad") he = Elder() print(he.get_name()) # 输出 Toad
类中的方法就是一个函数,只不过这个函数的第一个参数必须是self。这个self参数表示调用方法的实例对象。使用实例对象调用方法时,python解释器会把实例对象作为第一个参数传给该方法,实际上我们还可以显示的给方法传参:
print(Elder.get_name(he)) # 输出 Toad
还有一类方法,并不以self为第一个参数——现在考虑之前定义的Journalist类:
class Journalist: """ Take this as an example """ name = 'Wallace' def __init__(self): self.name = "Zhangbaohua" @classmethod def get_class_name(cls): return cls.name
上面新定义的方法get_class_name,它只有一个参数cls,并没有参数self,上面还有一个@classmethod标记修饰——即所谓的类方法。它有一个参数cls(这里的参数叫什么都可以),作用是返回参数的name属性。它既可以被类本身调用,也可以被实例对象调用:
hongkong = Journalist() print(Journalist.get_class_name()) # 输出 Wallace print(hongkong.get_class_name()) # 输出 Zhangbaohua
除了类方法,还有一种类似的方法:静态方法,参见如下代码
import random def get_num(): a = random.randint(1926, 10000) return a class Elder: def __init__(self): self.life = get_num() def get_name(self): print("Toad")
在类中使用了类外面的函数get_num()——类和类外函数的耦合,在编码上并不是一种很好的习惯:这种做法给程序的维护带来了潜在的麻烦。而所谓的“静态方法”,就是把get_num()这样的函数放进类中。
使用了@staticmethod 装饰器后,现在get_num是函数的一个静态方法——它实际上相当于一个独立的函数,跟类本身并没有什么关系,即我们用一种简单粗暴的方法解决了上面的问题——用静态方法,把函数“包装”进了类。
7.面向对象编程的其他概念
抽象:数据抽象(某类对象的特性和状态)和行为抽象(某类对象的功能或行为特性)。
封装:即类(class),其中的属性和方法都是类的成员。
假设我们用C++语言来定义一个clock类,如下:
class Clock { public: Clock(); // C++ 中的初始化函数,相当于 Python 中的 __init__ void setTime(int newH,int newM,int newS);//在 C++ 和 JAVA 等语言中,函数定义需要写明参数的类型和返回值类型,这一点跟 Python 不一样 void showTime(); private: int hour,minute,second; };
public和private关键字,这个定义的效果:外界的程序不能直接访问三个成员变量,只能通过2个函数来间接查看或者修改三个变量的值。这样就可以实现对成员访问权限的合理控制,让不同类之间的相互影响(耦合)减少到最低限度,进而增强数据的安全性,并简化程序编写工作。
举例:对于Clock类,如果不做封装的话外界的程序直接对3个变量进行访问,假设有人要把表示时间的变量的值设置成一个根本不可能出现的值(如设置成999小时999分钟),这就可能让程序出错。而进行封装之后,外界想要重新设置时间就必须通过setTime()函数——这样我们就可以在函数中增加检查输入合法性的代码,避免不合法数据的恶意输入。
python也支持数据的封装,也就是“私有化”,并且写法比C++简单。同样是上面的Clock类,用python写如下:
class Clock: def __init__(self): self.__hour = 0 self.__minute = 0 self.__second = 0 def showTime(self): print(self.__hour, self.__minute, self.__second)
我们只要在一个想要私有化的属性前面加上双下划线_就可以将其私有化,等同于C++中的private关键字。这样一来,当我们想要在外界直接访问私有属性,就会报错:
C:\DATA\GitHub\jisuankeWork\gitlab\python_programming\python3\new_add\oop_in_py (new_add) λ python test.py Traceback (most recent call last): File "test.py", line 9, in <module> print(c.__hour) AttributeError: 'Clock' object has no attribute '__hour'
私有属性只能通过类内的方法来访问,即“封装”。不光是私有属性,还可以定义私有方法,定义的方法跟私有属性相同:
class Clock: def __init__(self): self.__hour=0 self.__minute=0 self.__second=0 def showTime(self): print(self.__hour,self.__minute,self.__second) def __getSecond(self): # 私有方法 return self.__second
继承与多态
继承:新建一个继承了原有类的新类,具有原有类的新类,具有原有类的所有特征的同时,又具有自身的新特性。通过类的这种的这种层次结果,可以很好地反应出特殊概念与一般概念的对应关系。
父类/基类:被其他类继承的类
子类/派生类:继承自一个父类的类
在python中,继承可以通过这样的写法实现:
class P: pass class C(P): pass
类c是继承自类p的一个子类——如果父类定义了一些属性和方法,则子类就会拥有父类定义的所有内容。
事实上,我们自己定义的任何一个类,都是继承自python中内置的基类object。如上面的类P,我们可以在交互式命令行下,用内置方法_base_查看它的基类:
>>> class P: ... pass ... >>> P.__base__ <class 'object'> >>>
多态:广义上指同一段程序,可以直接处理多种类型对象的能力。如打乒乓球、打篮球中的“打”,就是对多种运动行为的抽象。
在C++中类似python不用声明一个变量类型的写法:
auto i=1; //注意必须在定义变量时初始化,形如auto i;的写法不允许!
从类型系统的角度看,C++和JAVA术语静态类型语言,而Python是一种动态类型语言,变量的类型是在解释器运行的时候通过类型推导所确定的(需要注意的是解释器实际上仍然会对代码进行编译)。
如,我们可以这样定义一个函数:
def print_length(item): print(len(item))
然后我们就可以把任何一种用len求长度的对象传给print_length函数,得到正确的结果。
1. print_length("123") # 输出 3 2. print_length([1,2]) # 输出 2
如果是C++之类的语言,在定义函数时需要用到函数重载或者模板机制,对于Python来说显然方便多。
8.HR管理
题目:
1