面向对象自身的概念,没接触过的同学自行google百度吧,这里就不提了。
目录
... 类和实例
... 属性
... 方法
... 访问限制
... 内置属性
... 内置方法
... @property
... 动态绑定方法
封装
类和实例
Python类的定义:
class 类名:
pass
支持多继承,基类跟在类名后面的小括号中,多个基类用逗号分隔:
class 类名(基类1, 基类2):
pass
类可以有docstrings,与函数一样,建议docstrings必写。
类中可以定义 __init__
方法,你可以认为它是初始化方法或构造函数,但需要注意的是,这个方法是在类的实例创建后立即调用的,也就是说,此方法执行时,类的实例对象已经创建好了。
类的每个实例方法(包括 __init__
方法)的第一个参数,都是指向当前类实例的引用,参数名习惯上使用 self (建议不要使用其它名字)。方法定义的时候必须明确指定第一个参数为self,调用时不需要传递,因为Python会自动传递,但是在子类调用父类方法时,必须传递此参数。
父类的方法不会在子类方法执行前自动调用(包括 __init__
方法),如果子类需要扩展父类的行为,需要显示调用父类的方法。
实例化一个类,写法与函数调用一致,不需要new之类的关键字,参数传递__init__
方法定义的参数,返回新创建的类实例对象。
我们不需要特别关注垃圾回收的问题,不需要考虑什么时候明确地释放实例,因为当指派给它的变量超出作用域时,会被自动释放。具体原理,感兴趣的同学可以查一下:引用计数,标记-清除。
经典类,新式类
新式类从Python2.2开始引入,用于统一 class 和 type 的概念。新式类继承自object,它仅仅代表一个用户创建的类型:
class A:
pass
class B(object):
pass
a = A()
b = B()
# 输出:<type 'classobj'> __main__.A <type 'instance'>
print type(A), a.__class__, type(a)
# 输出:<type 'type'> <class '__main__.B'> <class '__main__.B'>
print type(B), b.__class__, type(b)
还有重要的一点,在多继承中,两种类使用的查找算法不同,具体见下一节继承
在python3中只保留了新式类,去除了经典类。
属性
直接在类中定义的是类属性(类变量),可在类的所有实例中共享,使用 类名.
调用;在方法中定义且以self.
开头的是实例属性(实例变量),对每个实例是独立的,使用 实例名.
或 self.
调用 。实例属性一般在__init__
方法中初始化。
# -*- coding: UTF-8 -*-
class TestClass:
'测试类,了解类变量和实例变量'
# 类变量
className = 'className1'
def __init__(self,name):
# 实例变量
self.name = name
def print_name(self):
print '{0}, {1}, {2} '.format(TestClass.className, self.className, self.name)
tc = TestClass('instName1')
tc.print_name() # 输出: className1, className1, instName1
# 修改实例变量
tc.name = 'instName2'
# 定义了一个与类变量同名的实例变量
tc.className = 'instName3'
tc.print_name() # 输出: className1, instName3, instName2
# 修改类变量
TestClass.className = 'className2'
tc.print_name() # 输出: className2, instName3, instName2
# 删除实例变量
del tc.className
tc.print_name() # 输出: className2, className2, instName2
看出上面的玄机了吗?想想看下面的输出吧:
class TestClass2:
'测试类,了解类变量和实例变量'
# 类变量
alist1 = []
alist2 = []
def __init__(self,name):
# 实例变量
self.alist1 = []
self.alist1.append(name)
self.alist2.append(name)
tc2 = TestClass2('aItem')
print tc2.alist1, TestClass2.alist1
tc2.alist1 = []
print tc2.alist1, TestClass2.alist1
print tc2.alist2, TestClass2.alist2
tc2.alist2 = []
print tc2.alist2, TestClass2.alist2
方法
类中可以定义三种方法:实例方法,类方法,静态方法。
我们前面看到的都是实例方法,第一个参数是实例对象 self(self 是约定俗成,不是关键字),必须通过实例去调用。
类方法的定义需要在方法前加 @classmethod
的标记符,第一个参数是类本身,一般写作: cls,类和实例都可以调用。
@classmethod
def test_class(cls):
print cls
静态方法的定义需要在方法前加 @staticmethod
的标记符,没有隐含传递的参数:
@staticmethod
def test_static():
print 'a static method'
访问限制
当属性或方法以双下划线 __
开头时,表明是私有属性或私有方法,外部不可以访问。但有一些以双下划线 __
开头且以双下划线 __
结尾的属性或方法是内置的,所以我们尽量不要用前后双下划线的方式命名自己的属性方法。
内置属性
我们前面看到过的 __doc__
,__class__
都是类的内置属性,下面介绍另外一个常用的属性:__slots__
。
在前面的例子中我们注意到,创建一个类的实例后,可以随意给该实例绑定属性,如果觉得这样会不好控制,那么我们可以通过给 __slots__
赋一个tuple,以限制实例可绑定的属性:
class Student(object):
# 只有name和grade属性
__slots__ = ('name','grade')
def __init__(self,name,grade = 1):
self.name = name
self.grade = grade
s = Student('Lili')
s.age = 7 # 这里会报错:AttributeError: 'Student' object has no attribute 'age'
需要注意,如果子类没有定义 __slots__
,那么还是可以绑定任意属性;一旦子类也定义了 __slots__
,那么子类可以绑定自身的 __slots__
以及父类的 __slots__
。
内置方法
__init__(self,..)
就是一个内置方法,它在实例创建后立即运行,并不需要手工调用。
__new__(cls,*args,**kwd)
可能更适合叫初始化方法,因为它是用来生成实例对象的,在 __init__
之前执行。网上的例子一般用它来实现单例模式。
class Singleton(object):
__instance = None
count = 0
def __init__(self):
print 'init...',self
Singleton.count = Singleton.count + 1
def __new__(cls, *args, **kwd):
print 'new...'
if cls.__instance is None:
cls.__instance = object.__new__(cls, *args, **kwd)
return cls.__instance
s1 = Singleton()
s2 = Singleton()
print s1.count, s2.count
s1.name = 'linhl'
print s1.name, s2.name
输出结果:
new...
init... <__main__.Singleton object at 0x00000000029B0B00>
new...
init... <__main__.Singleton object at 0x00000000029B0B00>
2 2
linhl linhl
大家可以将__new__
方法注释掉看看运行结果。
__str__(self)
和 __repr__(self)
:
>>> class A(object):
pass
>>> a = A()
>>> print a
<__main__.A object at 0x000000000289A860>
>>> a
<__main__.A object at 0x000000000289A860>
如上直接打印的实例很不好看,我们可以通过实现__str__(self)
和 __repr__(self)
使得实例打印得漂亮一点:
>>> class A(object):
def __str__(self):
return 'A Class'
__repr__ = __str__
>>> a = A()
>>> print a # 相当于 str(a), str()会调用__str__()
A Class # 由 __str__() 返回
>>> a # 相当于 repr(a), repr()会调用__repr__()
A Class # 由 __repr__() 返回
__len__(self)
会由len()
调用,返回实例的长度:
>>> class A(object):
def __len__(self):
return 10
>>> len(A())
10
__cmp__(stc,dst)
通过返回大于、等于或小于0的数,来比较stc和dst的大小:
>>> class A(object):
def __init__(self,age):
self.age = age
def __cmp__(stc,dst):
if stc.age == dst.age:
return 0
elif stc.age < dst.age:
return -1
else:
return 1
>>> a = A(10)
>>> b = A(12)
>>> a == b
False
>>> a > b
False
>>> a < b
True
__iter__(self)
和 next(self)
可以使得我们定义的普通的类变成一个迭代器:
class Fab(object):
def __init__(self, max):
self.max = max
self.a, self.b = 0, 1
# 一般都是返回自身
def __iter__(self):
return self
def next(self):
self.a, self.b = self.b, self.a + self.b
if self.a > self.max:
raise StopIteration()
return self.a
直观的,我们可以这样用这两个方法:
f = Fab(10)
fetch = iter(f) # iter()中默认调用__iter__()
while True:
try:
n = fetch.next()
except StopIteration: # 遇到 StopIteration 异常则退出
break
print n
如果每次都这么用就太麻烦了,Python给我们提供了语法糖:
f = Fab(10)
for n in f:
print n
虽然我们可以在类里定义一个list来达到类似的目的,但当数据量大的时候,需要考虑内存问题,用如上的next则可以复用内存。
__getitem__(self,key)
使得我们可以像list一样获取指定索引位置key的元素:
class Week(object):
def __init__(self):
self.__weekdays = ('星期日','星期一','星期二','星期三','星期四','星期五','星期六')
def __getitem__(self,key):
if isinstance(key,int):
return self.__weekdays[key]
elif isinstance(key,slice): # 支持切片
return self.__weekdays[key.start:key.stop]
w = Week()
print w[5]
print w[2:4]
__getattr__(self,name)
, __getattribute__(self,name)
这两个方法很像,既然是两个方法,那肯定是有区别的:
class Test(object):
def __init__(self,name):
self.name = name
def __getattr__(self, value):
# 只有当address属性不存在时才返回 zj
if value == 'address':
return 'zj'
if value == 'age':
return 7
raise AttributeError('Test 类没有属性 {0}'.format(value))
def __getattribute__(self, value):
# name属性始终返回 wang
if value == 'name':
return 'wang'
if value == 'address':
return object.__getattribute__(self, value)
raise AttributeError('Test 类没有属性 {0}'.format(value))
test = Test('li')
print test.name # 始终输出:wang
print test.address # 输出:zj
test.address = 'sh' # 绑定address属性
print test.address # 输出:sh
print test.age # 输出:7
print test.grade # 得到AttributeError: Test 类没有属性 grade
__getattr__
在找不到需要的属性时调用,返回属性的值,或者抛出 AttributeError
异常。__getattribute__
只要定义了,就会无条件执行,如果同时也定义了__getattr__
(如上例),__getattr__
不会被调用,除非被显示调用或者__getattribute__
引发了AttributeError
@property
给实例属性赋值时,一般会需要做一些校验。如果每次赋值都要写同样的校验逻辑,显然不符合我们的编程思想,那如果把校验逻辑提成独立的方法,调用起来又有点麻烦,不像直接用属性那么简单。可以用 @property
装饰器来帮助我们:
class Student(object):
__slots__ = ('name','__grade')
def __init__(self,name,grade = 1):
self.name = name
# 私有属性
self.__grade = grade
# 定义grade的get方法
@property
def grade(self):
return self.__grade
# 定义grade的set方法,如果不定义此方法则grade是只读属性
@grade.setter
def grade(self, value):
if not isinstance(value,int):
raise ValueError('grade 必须是数字!')
elif value < 1 or value > 6:
raise ValueError('grade 只能在 1 ~ 6 之间!')
else:
self.__grade = value
s = Student('Lili')
# 像使用一般属性一样调用
s.grade = 11 # 这里会报错: ValueError: grade 只能在 1 ~ 6 之间!
动态绑定方法
使用 MethodType
可以给类动态绑定方法:
from types import MethodType
def print_student(self):
print 'Name: {0} Grade: {1}'.format(self.name,self.grade)
class Student(object):
__slots__ = ('name','grade')
def __init__(self,name,grade = 1):
self.name = name
self.grade = grade
Student.print_student = MethodType(print_student,None,Student)
s = Student('Lili')
s.print_student() # 输出: Name: Lili Grade: 1
继承
教程里多以Animal为例解释继承,我们也拿来主义。现在定义一个Animal类:
class Animal(object):
def run(self):
print 'I can run.'
def sound(self):
raise NotImplementedError
我们考虑的动物都可以跑,都可以发出声音,但每种动物的叫声不同,所以这个类提供了一个run方法和一个sound方法,但sound方法没有实现。下面定义一个Cat类,一个Dog类,都继承自Animal,这两个子类分别实现了自己的sound方法,同时默认地继承了run方法:
class Animal(object):
def __init__(self,name):
self.name = name
def run(self):
print '{0} can run.'.format(self.name)
def sound(self):
raise NotImplementedError
class Cat(Animal):
def sound(self):
print 'meow meow'
class Dog(Animal):
def sound(self):
print 'woof woof'
cat = Cat('Cat')
cat.run() # 输出: Cat can run.
cat.sound() # 输出: meow meow
dog = Dog('Dog')
dog.run() # 输出: Dog can run.
dog.sound() # 输出: woof woof
如果需要在子类中调用父类的方法,要用 父类.方法名()
的方式去调,同时需要传递self
参数。子类不能调用父类中的私有属性和私有方法。
传统概念中,面向对象一般是不支持多继承的,否则调用子类中某个继承自父类的方法时,是调用父类A的方法呢,还是父类B的方法呢?但Python是支持多继承的,它又是怎么处理这个问题的呢?
对于经典类,查找基于深度优先算法:
class Grandpa:
def eyelid(self):
print 'single-fold eyelid'
class Father(Grandpa):
pass
class Mother(Grandpa):
def eyelid(self):
print 'double-fold eyelids'
class Child(Father,Mother):
pass
c = Child()
c.eyelid()
c.eyelid()
打印的是 single-fold eyelid
。但我们知道双眼皮是显性基因,这里妈妈是双眼皮,孩子应该也是双眼皮才对,而这里的深度优先算法绕过了Mother类中实现的eyelid方法,取了Father的父类Grandpa中的eyelid方法。
如果我们把Grandpa改为新式类(继承自object),结果会怎样呢?新式类采用的是广度优先算法,所以会打印 double-fold eyelids
。 有说新式类是C3算法,具体我真没看明白,不过这种写法是会造成一定理解上的混乱,能避免就避免吧。
多态
面向对象中,多态是指根据对象的类型用不同方式处理,产生不同的结果。我们通过一段java代码来理解一下:
public class Animal{
public void sound(){
System.out.println("Animal sound");
}
}
public class Cat extends Animal{
public void sound(){
System.out.println("Cat: meow meow");
}
}
public class Dog extends Animal{
public void sound(){
System.out.println("Dog: woof woof");
}
}
public class Test{
public static Animal getAnimal(int i){
if(i%2 == 0)
return new Cat();
else
return new Dog();
}
public static void main(String[] args){
//随机获取
Animal animal = getAnimal((new Random(100)).nextInt());
animal.sound();
}
}
即使没有基础,基本上也看得明白上面这段代码的意思。main中的animal可能是Cat,也可能是Dog,但不管它到底是什么,它都是一个动物,可以调用sound()方法,而在运行期就可以打印出对应的正确的语句。
Python因为是动态类型语言,定义变量的时候并不申明变量的类型,多态性表现得不像java这么直接,但它确实可以起到相同的作用。我们接着上一节举的Animal的例子写:
def print_sound(animal):
animal.sound()
print_sound(cat)
print_sound(dog)
看下结果如何呢?有人说,这里与是否继承自Animal类没有关系,只要有sound方法就行:
class OtherClass(object):
def sound(self):
print 'OtherClass sound'
print_sound(OtherClass())
不要太钻牛角尖,我们的目标是实现功能,解决问题,这就足够了。