一、类和实例
1.类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
2.对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
3.类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
4.实例变量:定义在方法中的变量,只作用于当前实例的类。
5.对“类”和“对象”的使用:
类就是一个模板,模板里可以包含多个函数,函数里实现一些功能。
对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。
# class Foo: # 完成类的创建
# # 类中的函数
# def bar(self):
# # 功能阐述
# pass
# # =========完毕==============
# obj = Foo()
# 创建对象的时候,记得在后面加个括号
"""
注意,按照Python通用规则,Class用驼峰式表示(HelloWorld)
而其他的obj等等,都用‘_’隔开(this_is_object),在创建对象的时候要加括号
类中的函数第一个参数必须是self,类中定义的函数叫做“方法”,叫自我指代参数。
self 是个什么鬼呢?它是为了指代它所存在的类Class之中。
比如我们如果有好几个不同的obj被创建成同一个类,
那么有了self,我们的class Foo就能很好的知道哪个指的是自己,不会乱
"""
# 创建类
# class Foo:
# def bar(self):
# print('Bar')
# def hello(self,name):
# print('i am %s' %name) #注意在python中,%s表示格式化一个对象为字符串,%d表示整数,这里表示格式化name为一个字符串
#
# #根据Foo创建的对象
# obj = Foo()
# obj.bar()
# obj.hello('tom')
class Foo:
# 这里我们可以创建一个类级别的变量
# 它不会随着此类创建的变量变化而变化
name = 'Jan'
def bar(self):
print('Bar')
def hello(self,name):
print('you are %s'%self.name)
print('i am %s'%name)
print(' ')
# 根据Foo创建对象
obj1 =Foo()
obj2 = Foo()
obj1.hello('August')
obj2.hello('July')
"""
所以说,这个 self 就是个代指。
代指了自己所在的class。
你可以由 self 点进所指class本身的函数。
由此可见,self 本身作为一个代词,并不一定要叫self。
你也可以用个其他什么来代替。只不过,必须得是这个类的所有子方法的第一个参数
"""
# 用其他代词来进行指代
class Foo:
#这里我们创建一个类级别的变量,它不会由此类的创建而发生变化,
name = 'jane'
def bar(july):
print('Bar')
def hello(july,name):#这里将self改成july,但是其作为第一参数的位置没有变,依旧是Foo Class 的自我指代
print('you are %s' %july.name)
print('i am %s' %name)
print(' ')
# 根据Foo创建的对象
obj1 = Foo()
obj2 = Foo()
obj1.hello('August')
obj2.hello('July')
2.构造函数:构造函数,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。
跟所有OOP语言一样,python也是有构造函数的,默认为:
# #构造函数 # class Foo: # def __init__(self): #这就是构造函数,他的职责就是在模型创建的初期就完成一些动作 # #简单的说就是自定义的初始化步骤,在object创建之初,就将object初始化 # #同样,它也需要self来指定本身这个class # self.name='Jan' # def hello(self,name): # print('you are %s'%self.name) # print('i am %s'%name) # print(' ') # #当你创建一个Foo类的时候,Init会被自动的跑一遍 # # obj = Foo() # #在这个例子中,我们默认给self自己的name变量,赋值为‘Jan’ # #此刻,当我们调用Foo的hello()方法的时候,hello自己name变量就被赋值为'July' # obj.hello('July')
3.init是可以带更多的参数的,用以初始化我们的class本身。
比如说,你要初始化一个类的时候要用到一些外部参数:
#创建类 class Foo: def __init__(self,name2):#可以在这两里附加上一些参数 #这些参数将是你创建一个Foo类时的一个必要条件 self.name = name2 def hello(self,name): print('you are %s'%self.name) print('i am %s' %name) print(' ') #当你创建一个Foo类的时候,init会自动跑一遍 #此刻可以不直接跑Foo,需要填入一个参数:name2 obj1 = Foo('jack') obj1.hello('stone')
由楼上这些例子,我们大概可以知道整个Python的OOP概念了:
Class(类)就是一个把一堆Object(对象?)集合起来的地方。
在这里,无论是变量还是方法,他们享有基本一样的层级概念。只不过,方法要做一点事儿,而变量直接就是一个值。
二、访问限制
我们刚刚看到,在调用obj的时候,可以直接调出name或者使用hello()。那么我们怎么知道什么时候可以调用他们,什么时候不可以呢?
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,
就变成了一个私有变量(private),只有内部可以访问,外部不能访问
举个学生的例子,我们可以用一个学生类来存储学生的信息,但是我们在外部可以接触到name,那么其实我们就是可以直接修改name的,这是不安全的
三、继承和多态以及多重继承
通过对象直接调用
我们可以在存完一个内容以后,在类以外的地方,通过这个类的对象,来直接”点“调用
class Student:
# 假定我们初始化一个Student类的时候要做的就是,记录下每个学生的名字和年龄
def __init__(self, name, age):
self.name = name
self.age = age
# 至此,我们用self指代student本身,并用name和age存下了他们的年龄和名字
# === 完毕 ===
#此时,我们新建一个学生
obj1 = Student('July', 18)
print(obj1.name) # 直接调用obj1对象的name属性
print(obj1.age) # 直接调用obj1对象的age属性
obj2 = Student('Aug', 73)
print(obj2.name) # 直接调用obj2对象的name属性
print(obj2.age) # 直接调用obj2对象的age属性
通过self间接调用
执行类中某一个方法时,通过self来调用了类自己的变量
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age)
# === 完毕 ===
#此时,我们新建一个学生
obj1 = Student('July', 18)
obj1.detail() #Python默认将obj1传给self,所以其实我们做的是obj1.detail(obj1)
# 那么,detail()内部的样貌其实就是:
# print(obj1.name)
# print(obj1.age)
obj2 = Student('Aug', 73)
obj2.detail()
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
继承
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容(爸爸有的儿子都有)。
例如,每个学生都有名字和年龄,木有问题。我们可以把这个作为我们的父亲类。
但是,每个学生自己,可能有自己不同的”方法“,比如,每个人有每个人不同的外号,不同的口号,不同的饮食习惯,不同的。。。。。
# 我们首先创建一个学生类,这个类是所有学生的爸爸
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age)
# 然后,我们创建一个小学生类,小学生特点是,LOL sala无敌
class PrimaryStudent(Student):#因为是继承于学生类,所以我们写在括号内
# 这里我们可以不写构造函数,于是我们就是直接沿用Student类的构造函数
def lol(self): # 我们有一些新的独有的方法,会被叠加起来
print('不服sala!')
# 接下来,我们创建一个大学生类,大学生特点是,额,每个人都有个妹子。。
class CollegeStudent(Student):
def __init__(self, name, age, gf): #这里,我们改写一下构造函数
# 于是爸爸的init会被直接overwrite
self.name = name
self.age = age
self.gf = gf
def gf_detail(self):
print(self.gf)
# 来,我们来创建一下
obj1 = PrimaryStudent('小王', 7)
obj1.lol() # 独有的方法
obj1.detail()#继承与爸爸的方法
obj2 = CollegeStudent('王思聪', 29, '张雨馨')
obj2.detail()
obj2.gf_detail()
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
这样可以极大的提高效率,减少代码的重复。
问题来了,如果我想多认个干爹呢?
Python和Java/C#的不同就是,Python可以多类继承,也就是,可以认很多干爹
但是干爹多了,就出了问题了。继承的时候,从谁先开始?
有两种方式,分别是深度优先和广度优先
-
当本身的类是经典类的时候,就按照深度优先方式查找继承的方法 (即,找到一个爸爸,继续找这个爸爸的爸爸,爸爸的爸爸的爸爸。。。)
-
当本身的类是新式类的时候,就按照广度优先的方式查找 (即,找到一个爸爸,再找下一个爸爸,再找下一个爸爸,平辈之间查找)
那么为什么有经典类和新类之分呢?
这是个历史遗留问题,新类 统一了类(class)和类型(type),所以其实也是社区推荐的写法,只不过。。很多程序员都很懒。。
在2.2之前,比如2.1版本中,类和类型是不同的,如a是ClassA的一个实例,那么a.__class__返回 ‘ class __main__.ClassA‘ ,type(a)返回总是<type 'instance'>。而引入新类后,比如ClassB是个新类,b是ClassB的实例,b.__class__和type(b)都是返回‘class '__main__.ClassB' ,这样就统一了。
于是乎,在新版的Python中,这个经典类和新类的区别已经不存在,都统一使用广度优先。
我们先假设我们还活在python2.2的时代:
#经典类的写法
class c1:
pass
class c2(c1):
pass
#新类的写法
class N1(object):
pass
class N2(N1):
pass
可见,新类的标志就是,大家的老祖宗继承于一个系统级的类,叫Object
具体的,我们来看看:
- 经典类
class D:
def bar(self):
print('D.bar')
class C(D):
def bar(self):
print('C.bar')
class B(D):
pass
class A(B, C):
pass
a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
- 新类
class D(object):
def bar(self):
print('D.bar')
class C(D):
def bar(self):
print('C.bar')
class B(D):
pass
class A(B, C):
pass
a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
当然,对我们先现在而言,两种写法都得出C.bar ;这说明,已经木有区别了
class F1:
pass
# 假设,S1是我们的正统类,它继承于根正苗红的F1,是我们的正统类
class S1(F1):
def show(self):
print('S1.show')
# S2是路人甲,是个歪瓜裂枣,但是他自己也有一个叫show的方法。
class S2:
def show(self):
print('S2.show')
# 在Java或C#中定义函数参数时,必须指定参数的类型,也即是说,我们如果用
# Java写下面的Func,需要告知,obj是F1类还是其他什么东西。
# 如果限定了F1,那么S2是不可以被采纳的。
# 然而,在Python中,一切都是Obj,它不care你到底是什么类,直接塞进去就可以
def Func(obj):
"""Func函数需要接收一个F1类型或者F1子类的类型"""
obj.show()
s1_obj = S1()
Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
用type()
type(123)
type('str')
type(None)
type(abs)
class a:
def __init__(self):
pass
type(a)
如何用语句判断是不是一种type呢?
type(123)==type(456)
type('abc')==type('123')
type('abc')==type(123)
type('abc')==str
type([])==list
四、获取对象信息
用isinstance()
isinstance()可以告诉我们,一个对象是否是某种类型(包括继承关系)。
class A:
pass
class B(A):
pass
class C(B):
pass
k=A()
g=B()
y=C()
isinstance(y, C)
isinstance(y, B)
同理,isinstance()也可以当type()用
isinstance('a', str)
使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
dir('ABC')
类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
'ABC'.__len__()
我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:
class MyObject:
def __len__(self):
return 100
obj = MyObject()
len(obj)
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
class MyObject:
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
紧接着,可以测试该对象的属性:
hasattr(obj, 'x') #有木有属性'x'
obj.x
hasattr(obj, 'y') # 有属性'y'吗?
setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(obj, 'y') # 有属性'y'吗?
getattr(obj, 'y') # 获取属性'y'
obj.y # 获取属性'y'
可以传入一个default参数,如果属性不存在,就返回默认值:
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
也可以获得对象的方法:
hasattr(obj, 'power') # 有属性'power'吗?
getattr(obj, 'power') # 获取属性'power'
fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
fn # fn指向obj.power
fn() # 调用fn()与调用obj.power()是一样的
五、实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
但是,如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:
class Student(object):
name = 'Student'
我们来用一个例子说明一下实例与类的属性差异:
class Student(object):
name = 'Student'
s = Student() # 创建实例s
print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
print(Student.name) # 打印类的name属性
s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
模块和包
Python的程序由包(package)、模块(module)和函数组成。包是由一系列模块组成的集合。模块是处理某一类问题的函数和类的集合。
包就是一个完成特定任务的工具箱,Python提供了许多有用的工具包,如字符串处理、图形用户接口、Web应用、图形图像处理等。这些自带的工具包和模块安装在Python的安装目录下的Lib子目录中。
注意:
包必须至少含有一个__int__.py文件按,该文件的内容可以为空。__int__.py用于标识当前文件夹是一个包。
模块
在python中一个文件可以被看成一个独立模块,而包对应着文件夹,模块把python代码分成一些有组织的代码段,通过导入的方式实现代码重用。
导入模块时,是按照sys.path变量的值搜索模块,sys.path的值是包含每一个独立路径的列表,包含当前目录、python安装目录、PYTHONPATH环境变量,搜索顺序按照路径在列表中的顺序(一般当前目录优先级最高)。
想看自己的Python路径,大家可以
import sys
print(sys.path)
如果你发现你在某个地方写的文件(包)import错误,你就可以看看这个sys.path是否囊括了你那批文件的根目录。
导入模块
使用import语句(不管是你自己写的,还是你下载的别人的)
import module1
import module2
import module3
import module1,module2,module3
这两种方式的效果是一样的,但是第一种可读性比第二种好,推荐按照下面的顺序导入模块,并且一般在文件首部导入所有的模块
python标准库
第三方模块
应用程序自定义模块
使用from-import语句导入模块的属性
单行导入
from module import name1,name2,name3
多行导入
from module import name1,name2,name3
导入全部属性(由于容易覆盖当前名称空间中现有的名字,所以一般不推荐使用,适合模块中变量名很长并且变量很多的情况)
from module import *
自定义导入模块名称
就是为了用的时候方便好记。
import simplejson as json
包
包将有联系的模块组织在一起,有效避免模块名称冲突问题,让应用组织结构更加清晰。 一个普通的python应用程序目录结构:
app/
__init__.py
a/
__init__.py
a.py
b/
__init__.py
b.py
app是最顶层的包,a和b是它的子包,可以这样导入:
from app.a import a
from app.b.b import test
a.test()
test()
上面代码表示:导入app包的子包a和子包b的属性test,然后分别调用test方法。
每个目录下都有__init__.py文件,这个是初始化模块,from-import语句导入子包时需要它,可以在里面做一些初始化工作,也可以是空文件。ps:__init__.py定义的属性直接使用 顶层包.子包 的方式导入,如在目录a的__init__.py文件中定义init_db()方法,调用如下:
from app import a
a.init_db()
实战
我们现在已经完全掌握了使用包,自己定义类,组件一个可运行的程序的方法
现在我们可以专注于Machine Learning方面,来看看实战是怎么运用这些知识的。
from sklearn import svm, datasets
class Dataset:
# 我们创造一个dataset的类,这个类会帮我们下载相关的数据集,
# 并给我们分类好x,y
def __init__(self, name):
# 告诉类,我们需要哪一个数据集
# 我们有两个选择,一个是'iris'一个是'digits'
self.name = name
def download_data(self):
# 从sklearn的自带集中下载我们指定的数据集
if self.name == 'iris':
# 这里是sklearn自带的数据集下载方法,更多信息可以参照官网
self.downloaded_data = datasets.load_iris()
elif self.name == 'digits':
self.downloaded_data = datasets.load_digits()
else:
# 如果不是我们预想的两种数据集,则报错
print('Dataset Error: No named datasets')
def generate_xy(self):
# 通过这个过程来把我们的数据集分为原始数据以及他们的label
# 我们先把数据下载下来
self.download_data()
x = self.downloaded_data.data
y = self.downloaded_data.target
print('
Original data looks like this:
', x)
print('
Labels looks like this:
', y)
return x,y
def get_train_test_set(self, ratio):
# 这里,我们把所有的数据分成训练集和测试集
# 一个参数要求我们告知,我们以多少的比例来分割训练和测试集
# 首先,我们把XY给generate出来:
x, y = self.generate_xy()
# 有个比例,我们首先得知道 一共有多少的数据
n_samples = len(x)
# 于是我们知道,有多少应该是训练集,多少应该是测试集
n_train = n_samples * ratio
# 好了,接下来我们分割数据
X_train = x[:n_train]
y_train = y[:n_train]
X_test = x[n_train:]
y_test = y[n_train:]
# 好,我们得到了所有想要的玩意儿
return X_train, y_train, X_test, y_test
# ====== 我们的dataset类创造完毕=======
接下来,我们在main中code以下来调用我们自己写的类:
# 比如,我们使用digits数据集
data = Dataset('digits')
# 接着,我们可以用0.7的分割率把xy给分割出来
X_train, y_train, X_test, y_test = data.get_train_test_set(0.7)
同样,我们也不一定需要自己创造类,我们可以引用第三方库里的类, 比如这里,我们用SVM作为我们的分类器,去训练我们的算法 我们就直接建造一个object,使他成为SVM类
clf = svm.SVC()
这里 clf 是 classifier的简称,SVC指的是SVM的classification版本。
因为我们的数据集都是分类问题,所以我们使用SVC()
接下来,我们fit我们的数据(也就是训练我们的数据)
显然,做fit的时候,我们只可以使用训练集
clf.fit(X_train, y_train)
好,现在你的clf已经训练好了,我们来看看它的表现:
我们随便取test集中的一个数据点,并对应它的真实label
test_point = X_test[12]
y_true = y_test[12]
我们来看看,我们的clf给出的预测是什么:
clf.predict(test_point)
再看看真正的label应该是什么:
y_true
正确!
那么这样,你们已经学会如何训练数据集并作出新的预测了。
把所有的测试集都导入clf,让他pridict,并看看跟真实的label相差多少。