面对对象
类(class):用来描述相同属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。对象时类的实例。
对象:通过类定义的数据结构的实例。对象包括两个数据成员(类变量和成员变量)和方法。
类变量:类变量在整个实例化的对象中是公用的。类百纳领定义在类中且在函数体外。类变量通常不作为成员变量使用。
实例变量:定义在方法中的变量,只作用于当前对象的实例
对“类”和“对象”的使用:
类就是一个模板,模板里可以包含多个函数,函数里事项一些功能。
对象是根据模板创建的实例,,通过实例对象可以执行类中的函数。
#创建类
class Foo:
#类中的函数
def bar(self):
#功能描述
pass
#---------定毕----------
#根据Foo创建对象obj
obj=Foo()
#创建对象的时候,记得在后面加括号
注意,按照Python通用规则,Class用蟒蛇是表示(HelloWorld),而其他的obj,都用‘_’隔开(this_is_object)
类中的函数第一个参数必须是self(不一定是self,必须是指代本类的一个东西),类中定义的函数叫‘方法’
class Foo:
def bar(self):#self指代这个类本身
#功能描述
print('Bar')
def hello(self,name):
print('i am %s'%name)
#根据Foo创建对象obj
obj=Foo()
obj.bar()#执行bar()方法
obj.hello('july')#执行hello()方法
Bar
i am july
self是个什么鬼?他是为了指代它所存在的类class之中,比如我们如果有好几个不同的obj被创建成同一个类。那么有了self,我们的class Foo就能很好的知道哪个指的是自己,不会乱。
#创建类
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('\n')
#根据Foo创建对象
obj1=Foo()
obj2=Foo()
obj1.hello('August')
obj2.hello('July')
you are Jan
I am August
you are Jan
I am July
所以说,这个self就是个代指,代指自己所在的class。你可以由self点进所指class本身的函数。由此可见,self本身作为一个代词,并不一定叫做self。你也可以用个其他什么来代替。只不过必须的是这个类的所有字方法中的第一个参数:
#创建类
class Foo:
#这里我们可以创建一个类级别的变量
#它不会随着由此类创建的变量而变化
name='Jan'
def bar(july):
print('Bar')
def hello(july,name):#我这里把self该做july,但是只要它作为第一个参数的位置没变,它依旧是Foo的自我
print('you are %s'%july.name)#指代类级别上的东西
print('I am %s'%name)
print('\n')
#根据Foo创建对象
obj1=Foo()
obj2=Foo()
obj1.hello('August')
obj2.hello('July')
you are Jan
I am August
you are Jan
I am July
构造函数:构造函数是一种特殊的方法,主要用来在创建对象时初始化对象,即为对象成员赋初始值。
跟所有的OOP语言一样,Python也是有构造函数的,默认为:
#创建类
class Foo:
def __init__(self):#这就是构造函数,它的职责是在模型创建的初期,就完成一些动作
#简单的就是说,自定义初始化步奏
#同样它需要self来指代本身这个class
self.name='Jan'
#这里我们可以创建一个类级别的变量
#它不会随着由此类创建的变量而变化
def bar(july):
print('Bar')
def hello(july,name):#我这里把self该做july,但是只要它作为第一个参数的位置没变,它依旧是Foo的自我
print('you are %s'%july.name)#指代类级别上的东西
print('I am %s'%name)
print('\n')
#当你创建一个Foo类的时候,这个方法会被跑一遍
obj1=Foo()
#在骂我们的例子中,我们默认给self自己的name变量,赋值为'Jan'
#此刻当我们调用Foo的hello方法时,hello自己的变量被赋值为'July'
obj1.hello('July')
you are Jan
I am July
init可以带更多的参数,用以初始化我们的class本身。比如说,你要初始化一个类的时候要用到一些外部参数:
#创建类
class Foo:
def __init__(self,name2):#你可以在这里附加上一些参数,这些参数是你创建一个Foo累的必要条件
self.name=name2
#这里我们可以创建一个类级别的变量
#它不会随着由此类创建的变量而变化
def bar(july):
print('Bar')
def hello(july,name):#我这里把self该做july,但是只要它作为第一个参数的位置没变,它依旧是Foo的自我
print('you are %s'%july.name)#指代类级别上的东西
print('I am %s'%name)
print('\n')
#当你创建一个Foo类的时候,这个方法会被跑一遍
#此刻你不可以直接跑Foo(),你需要填入一个参数:name2
obj1=Foo('Feb')
#在骂我们的例子中,我们默认给self自己的name变量,赋值为'Jan'
#此刻当我们调用Foo的hello方法时,hello自己的变量被赋值为'July'
obj1.hello('July')
you are Feb
I am July
由楼上这些例子,我们可以大概知道整个Python的OOP概念了;Class(类)就是一个把一堆Object(对象)集合起来的地方。在这里,无论是变量换是方法,它们都享有基本层一级的概念。只不过,方法要做一点事儿,而变量就是一个值。
访问限制
我们刚刚看到,在调用obj的时候,可以直接调出name或者使用hello方法。那么我们杂么知道什么时候调用他们,什么时候不可以那?
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样就隐藏了内部的复杂。如果让内部属性不被外部访问,可以把属性的名称前加上两个下划线_ _,在Python中实例的变量名如果以 _ _开头,就变成一个私有变量(private),只有内部可以访问,外部不能访问
举个学生例子,我们可以用一个学生类来储存学生信息,但是我们在外部可以接触到name,那么我们其实就是可以修改name的,这是不安全的。
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def detail(self):
print(self.name)
print(self.age)
LiLei=Student('LiLei',12)
LiLei.age=20
LiLei.detail()
LiLei
20
为了防止这种篡改年龄的情况发生,为了维护着世界的和平,我们需要把关键的信息隐藏好:
class Student:
def __init__(self,name,age):
self.__name=name
self.__age=age
def detail(self):
print(self.__name)
print(self.__age)
LiLei=Student('LiLei',12)
LiLei.__age=20
LiLei.detail()
LiLei
12
看,如此一来,年龄就不会修改。那么如何保证安全,又能被外部修改?我么使用OOP家族传统理念:Getter+Setter
class Student(object):
...
def get_name(self):
return delf.name
def get_age(self):
return delf.age
def set_age(self,age):
self.age=age
至此,我们应该学会使用class定义我们自己的类了;
接下来,我们看看:
在Python中展现面对对象的三大特性:
面对对象的三大特性是指:封装,继承,多态。
封装
指的是把内容封装到某个地方,用于日后调用。他需要:
- 把内容封装在某处
- 从另一处调用被封装的内容
通过对象直接调用
我们可以在存完一个内容之后,在类以外的地方,通过这个类的对象,来直接‘点’调用
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',28)
print(obj2.name)
print(obj2.age)
July
18
Aug
28
通过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',28)
obj1.detail()
July
28
综上所述,对于面对对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者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',28)
obj1.detail()
#然后我们创建一个小学生类,小学生额特点是LOL,sala无敌
class PrimaryStudent(Student):#因为是继承于学生类,所以我们写在括号内
#这里我们可以不写构造函数,于是我们就是直接沿用Student类的构造函数
def lol(self):#我们有一些新的独特方法,会被叠加起来
print('不服sala')
#接下来我们创建一个大学生类,大学生的特点是每个人都有妹子
class CollegeStudent(Student):
def __init__(self,name,age,gf):#这里,我们改写一下构造函数,于是爸爸的__init__方法被覆盖
self.name=name
self.age=age
self.gf=gf
def gf_detail(self):
print(self.gf)
obj1=PrimaryStudent('小王',7)
obj1.detail()
obj1.lol()
obj2=CollegeStudent('王思聪',29,'张雨馨')
obj2.detail()
obj2.gf_detail()
July
28
小王
7
不服sala
王思聪
29
张雨馨
所以,对于前面的对象继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
这样可以极大地提高效率,减少代码的重复。
问题来了,如果我想认多个干爹那
Python和Java不同的就是,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类没有,则在B类中继续找,如果B类中没有则在D类中找,如果D换是没找到则在C类中找如果换为找到则报错。
#所以查找顺序为A->B->D->C
#在上述查找bar的方法中,一旦找到则,则寻找过程立即中断便不会再继续找了
a.bar()
D.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类没有,则在B类中继续找,如果B类中没有则在C类中找,如果C换是没找到则在D类中找如果换没找到则报错。
#所以查找顺序为A->B->C->D
#在上述查找bar的方法中,一旦找到则,则寻找过程立即中断便不会再继续找了
a.bar()
D.bar
当然了,现在而言都是广度优先,两种方法已经没区别了。
多态
Python中不支持多态也用不到多态,多态的概念应用在Java这一类强类型语言中,而Python尚属‘鸭子类型’(Duck typing)
class F1:
pass
class S1(F1):
def show(self):
print('S1.self')
class S2:
def show(self):
print('S2.self')
def Func(obj):
obj.show()
s1=S1()
Func(s1)
s2=S2()
Func(s2)
S1.self
S2.self
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型,有哪些方法呢?
用type()
type(123)
print(type('hello'))
print(type(None))
print(type(abs))
print(type(S1))
int
<type 'str'>
<type 'NoneType'>
<type 'builtin_function_or_method'>
<type 'classobj'>
如何用语句判断是不是一种type呢?
print type(123)==type(456)
print type('hello')==type('456')
print type('hello')==type(123)
print type('hello')==str
print type([])==list
print type(())==tuple
True
True
False
True
True
True
用isinstance()
isinstance()可以告诉我们,一个对象是否是某种类型(包括继承关系)
class A:
pass
class B(A):
pass
class C(B):
pass
a=A()
b=B()
c=C()
print isinstance(c,A)
#同样,isinstance也可当做type使用
print isinstance('abc',str)
True
True
使用dir
如果要获得一个对现的所有属性和方法,可以使用dir()函数它返回一个包含字符串的list,比如,获得一个对象的所有属性和方法。
dir('ABC')
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__getslice__',
'__gt__',
'__hash__',
'__init__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'_formatter_field_name_split',
'_formatter_parser',
'capitalize',
'center',
'count',
'decode',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'index',
'isalnum',
'isalpha',
'isdigit',
'islower',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
类似_ XXX 的属性和方法在Python中有特殊用途,比如 len 方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动调用该对象的 len _()方法,所以下面的代码是等价的。
'ABC'.__len__()
3
我们自己写的类,也想调用len(obj)方法,就自己写一个_ len _()方法;
class MyClass:
def __len__(self):
return 100
obj=MyClass()
len(obj)
100
仅仅把属性和方法列出来是不够的,配合getattr(),setattr()以及hasattr(),我们可以直接操作一个对象的状态。
class MyObject:
def __init__(self):
self.x=9
def power(self):
return self.x*self.x
obj=MyObject()
print hasattr(obj,'x')#有没有属性x
print obj.x
print hasattr(obj,'y')#有没有属性y
setattr(obj,'y',19)#设置y属性
print hasattr(obj,'y')
print getattr(obj,'y')
print obj.y#获取y属性
True
9
False
True
19
19
可以传入一个default参数,如果属性不存在,就返回默认值
print getattr(obj,'z',404)
#也可获得对象的方法
print hasattr(obj,'power')
print getattr(obj,'power')
fn=getattr(obj,'power')#获取属性病赋值
print fn
print fn()#fn()和power()有相同的效果
print type(fn)
print type(fn())
404
True
<bound method MyObject.power of <__main__.MyObject instance at 0x00000000063E06C8>>
<bound method MyObject.power of <__main__.MyObject instance at 0x00000000063E06C8>>
81
<type 'instancemethod'>
<type 'int'>
实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量或者通过self变量:
class Student(object):
def __init__(self,name):
self.name=name
s=Student('Bob')
s.age=18
但是如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性这种属性是类属性,归Student类所有.
class Student:
name='Student'
s=Student()
print s.name#因为实例没有属性,所以会查找类的属性
print Student.name#打印类的属性
s.name='LiMing'#给实例绑定name属性
print s.name#实例属性优先级高于类属性优先级,因此他会屏蔽掉类的属性
print Student.name#但是类属性仍旧没有消失
del s.name#如果删除实例的属性
print s.name#再次调用name,因为没有找到实例属性,类的属性就显示出来了
Student
Student
LiMing
Student
Student
从上面的例子中可以看出,在编写程序的时候,千万不能把实例属性和类属性使用相同的名字,因为你的实例属性会屏蔽类属性,但是当你删除实例属性的时候,再使用相同的名字,访问的仍是类属性。