创建类和对象
1. 创建一个类
谈起面向对象,对于大部分程序员来说都是耳熟能详的玩意(比如我这个java码农),这个面向对象编程说白了无非就是类和对象,方法和成员变量,封装等等。
Python作为一门面向对象的语言,肯定对于这些的支持是没问题,下面我们来说一下Python的面向对象编程的问题。首先是Python的声明一个类然后创建一个对象,这个是最基本的玩意。代码如下:
class Student(object):
def __init__(self,name):#构造函数
print("构造函数启动")
self.name=name
def sayHi(self):
print("Hello World",self.name)
p = Student("rookie")
p.sayHi()
# 输出 Hello World rookie
和java其实差不多,只是注意所有方法必须显示写出self(也就是java和c++的this)
2. 实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
但如果如果Student类本身需要绑定一个属性呢?(也就是java的静态属性)可以直接在class中定义属性,这种属性是类属性,归Student类所有:
class Student(object):
name = 'Student'
print (Student.name)
#输出 Student
那如果用实例去访问类属性呢?
class Student(object):
name = 'Student'
s = Student() # 创建实例s
print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
# 输出 Student
print(Student.name) # 打印类的name属性
# 输出 Student
s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
# 输出 Michael
print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
# 输出 Student
del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
# 输出 Student
从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
3.类方法、实例方法、静态方法
类方法:是类对象所拥有的方法,需要用修饰器"@classmethod"来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以"cls"作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。
class people:
country = 'china'
#类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
p = people()
print p.getCountry() #可以用过实例对象引用
print people.getCountry() #可以通过类对象引用
类方法还有一个用途就是可以对类属性进行修改:
class people:
country = 'china'
#类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls,country):
cls.country = country
p = people()
print p.getCountry() #可以用过实例对象引用
print people.getCountry() #可以通过类对象引用
p.setCountry('japan')
print p.getCountry()
print people.getCountry()
运行结果:
china
china
japan
japan
结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变。
实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为'self'的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。
静态方法:需要通过修饰器"@staticmethod"来进行修饰,静态方法不需要多定义参数。
class people:
country = 'china'
@staticmethod
#静态方法
def getCountry():
return people.country
print (people.getCountry() )
对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性;如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
封装和访问控制
1.访问控制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:
bart = Student('Bart Simpson', 98)
print (bart.score)
98
bart.score = 59
print (bart.score)
59
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取或修改name和score怎么办?可以给Student类增加get_name和set_score这样的方法。
需要注意的是,在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用name、score这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
那双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name
来访问__name变量
>>> bart._Student__name
'Bart Simpson'
2.特殊的类属性
对于任何类C,有以下的特殊属性及方法:
C.__name__ 类C的名字(string)
C.__doc__ 类C的文档字符串
C.__bases__ 类C的所有父类构成元素(包含了以个由所有父类组成的元组)
C.__dict__ 类C的属性(包含一个字典,由类的数据属性组成)
C.__module__ 类C定义所在的模块(类C的全名是'__main__.C',如果C位于一个导入模块mymod中,那么C.__module__ 等于 mymod)
C.__class__ 实例C对应的类
x=C()
x.__init__() 初始化一个实例,在实例创建之后立即调用,并且这个方法没有返回值(无return语句)
x.__repr__() 字符串的“官方”表示方法 >>> x <==> >>>x.__repr__()
x.__str__() 字符串的非正式值 等同于 print xx.__new__() 一般是用来生成一个不可变实例,控制实际创建的进程
继承与多态
1. 继承
继承给人的直接感觉是这是一种复用代码的行为。继承可以理解为它是以普通的类为基础建立专门的类对象,子类和它继承的父类是IS-A的关系。一个简单而不失经典的示例如下:
class Animal(object):
def __init__(self):
self.Name="Animal"
def move(self,meters):
print ("%s moved %sm." %(self.Name,meters) )
class Cat(Animal): #Cat是Animal的子类
def __init__(self): #重写超类的构造方法
self.Name="Garfield"
## def move(self,meters): #重写超类的绑定方法
## print "Garfield never moves more than 1m."
class RobotCat(Animal):
def __init__(self): #重写超类的构造方法
self.Name="Doraemon"
## def move(self,meters): #重写超类的绑定方法
## print "Doraemon is flying."
obj=Animal()
obj.move(10) #输出:Animal moved 10m.
cat=Cat()
cat.move(1) #输出:Garfield moved 1m.
robot=RobotCat()
robot.move(1000) #输出:Doraemon moved 1000m.
2. 多重继承
不同于Java,Python是支持多重类继承的。多重继承机制有时很好用,但是它容易让事情变得复杂。一个多重继承的示例如下:
class Animal:
def eat(self,food):
print ("eat %s" %food )
class Robot:
def fly(self,kilometers):
print ("flyed %skm." %kilometers )
class RobotCat(Animal,Robot): #继承自多个超类
def __init__(self):
self.Name="Doraemon"
robot=RobotCat() # 一只可以吃东西的会飞行的叫哆啦A梦的机器猫
print (robot.Name)
robot.eat("cookies") #从动物继承而来的eat
robot.fly(10000000) #从机器继承而来的fly
如你所看到的那样,多重继承的好处显而易见,我们可以轻而易举地通过类似“组合”的方式复用代码构造一个类型。
有个需要注意的地方,如果一个方法从多个超类继承,那么务必要小心继承的超类(或者基类)的顺序,多重继承的结果因为继承的顺序而有所不同,Python在查找给定方法或者特性时访问超类的顺序被称为MRO(Method Resolution Order,方法判定顺序)。
3. 鸭子类型
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个eat()方法就可以了:
class Persion(object):
def run(self):
print('Persion eat...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
高级特性
1.使用slots
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:
class Student(object):
pass
然后,尝试给实例绑定一个属性:
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name属性,不允许添加score属性.
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'没有被放到slots中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
2. 使用@property
如果类的每个属性都要设置set和get方法是非常麻烦的,那有没有方便的办法呢?
肯定是有了,那就是@property注解,通过这个注解可以很方便的定义类属性,示例如下:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s=Student()
s.score=10
print (s.score)
通过@property
修饰的属性不是直接暴露的,而是通过getter和setter方法来实现的。可以在@score.setter
修饰的set方法来规定输入范围.
而且还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。