类:
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
如:
class Person(object):
def __init__(self, name, lang="golang", website="www.google.com"):
self.name = name
self.lang = lang
self.website = website
self.email = "[email protected]"
laoqi = Person("LaoQi")
info = Person("qiwsir", lang="python", website="qiwsir.github.io")
print("laoqi.name=", laoqi.name)
print("info.name=", info.name)
print("-------")
print("laoqi.lang=", laoqi.lang)
print("info.lang=", info.lang)
print("-------")
print("laoqi.website=", laoqi.website)
print("info.website=", info.website)
上面是一个类定义,并实例化的例子。
class
后面紧接着是类名,即Person
,类名通常是大写开头的单词,(object)
表示该类是从哪个类继承下来的,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
类中有一个定义的构造函数__init__(),且部分参数的值已经指定。
接下来我们实例化了两个实例laoqi和info,其中info实例化时更新了所有参数的值,覆盖了构造函数中某些参数中已经指定的值。
构造函数:
上面例子中的一部分如下,就是构造函数。
class Person:
def __init__(self, name, lang="golang", website="www.google.com"):
self.name = name
self.lang = lang
self.website = website
self.email = "[email protected]"
__init__()就是一个构造函数,它的命名是用“__”开始和结束。在类中,基本结构写在__init__()这个函数里面,故这个函数称为构造函数,担负着对类进行初始化的任务。
self的作用:
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
self起到了这个作用:接收实例化过程中传入的所有数据,这些数据是通过构造函数后面的参数导入的。self本身就是一个实例(准确说法是应用实例),因为它所对应的就是具体数据。
类的实例化:
laoqi = Person("LaoQi")
info = Person("qiwsir", lang="python", website="qiwsir.github.io")
上面两句语句即根据Preson类实例化了两个实例laoqi和info。
类的数据封装:
面向对象编程的一个重要特点就是数据封装。
如,在下面的Student
类中,每个实例都拥有各自的name
和score
这些数据。我们可以直接在Student
类的内部定义访问数据的方法,这样,就把“数据”给封装起来了。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
student1 = Student("zhangsan", 90)
student1.print_score()
运行截图如下:
类的方法:
类的功能函数就是方法。方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据。
调用一个方法的途径为:
定义类和类中的方法;
创建一个实例或者说将类实例化;
最后用这个实例调用方法。
在类中定义一个方法,除了第一个参数是self
外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self
不用传递,其他参数正常传入。
如上例的:
student1.print_score()
我们从外部看Student
类,只知道创建实例需要给出name
和score
,而如何打印,都是在Student
类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
类的访问限制:
上面的Student类中,外部代码还是可以自由地修改一个实例的name
、score
属性。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,变成了一个私有变量(private),只有内部可以访问,外部不能访问。
如:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s:%s' % (self.__name, self.__score))
student1 = Student("zhangsan", 90)
student1.print_score()
print(student1.__name)
这样我们仍然可以调用实例的print_score()方法,但我们无法从外部修改和访问实例变量.__name
和实例变量.__score
了。
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
运行截图如下:
如果我们想访问私有变量,我们可以在类中增加相应的方法。想修改私有变量,也可以在类中增加相应的方法。
如:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s:%s' % (self.__name, self.__score))
def read_name(self):
print(self.__name)
def read_score(self):
print(self.__score)
def edit_name(self):
self.__name = input("please enter new name:")
student1 = Student("zhangsan", 90)
student1.print_score()
student1.read_name()
student1.read_score()
student1.edit_name()
student1.read_name()
运行截图如下:
通过方法修改参数在,可以在方法中对参数做检查,避免传入无效的参数。
如:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s:%s' % (self.__name, self.__score))
def read_name(self):
print(self.__name)
def read_score(self):
print(self.__score)
def edit_score(self):
if 0 <= int(input("请输入新的分数:")) <= 100:
self.__score = input("请输入新的分数:")
else:
print("分数超出范围!")
student1 = Student("zhangsan", 90)
student1.print_score()
student1.read_name()
student1.read_score()
student1.edit_score()
student1.read_score()
运行截图如下:
注意:
在Python中,如果不是在类中的变量,其名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
类的继承:
当我们新建一个类时,可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
如:
我们已经编写了一个名为Animal的class:
class Animal(object):
def run(self):
print("动物在跑")
现在我们想编写Dog
和Cat
类时,这时我们就可以从Animal类中继承:
class Animal(object):
def run(self):
print("动物在跑")
class Dog(Animal):
pass
class Cat(Animal):
def run(self):
print("猫在跑")
dog1 = Dog()
cat1 = Cat()
dog1.run()
cat1.run()
新建一个类时,要继承类则类名后的括号内为父类名。
如:class Dog(Animal):
上面的Dog和Cat子类,Dog类继承了父类Animal类的全部功能,而Cat子类重新覆写了父类Animal类的run()方法。
运行截图如下:
当然,我们也可以对子类增加一些方法。
如:
class Animal(object):
def run(self):
print("动物在跑")
class Dog(Animal):
def eat(self):
print("狗在吃")
class Cat(Animal):
def run(self):
print("猫在跑")
dog1 = Dog()
cat1 = Cat()
dog1.run()
dog1.eat()
cat1.run()
运行截图如下:
类的多态:
在上面的例子中,Cat子类覆写了父类Animal类的run()方法,在子类调用其run()方法时,总是会调用其覆写的run()方法。这就叫多态。
当我们定义一个Animal class的时候,我们实际上就定义了一种数据类型Animal class。
如:
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
我们可以用isinstance()
函数判断变量是否为某个数据类型。
如:
class Animal(object):
def run(self):
print("动物在跑")
class Dog(Animal):
def eat(self):
print("狗在吃")
class Cat(Animal):
def run(self):
print("猫在跑")
dog1 = Dog()
cat1 = Cat()
print(isinstance(dog1, Dog))
print(isinstance(cat1, Cat))
print(isinstance(cat1, Animal))
print(isinstance(dog1, Animal))
运行截图如下:
我们可以看到一个子类的实例不仅是子类的数据类型,还是父类的数据类型!但是反过来则不行。
即Dog
可以看成Animal
,但Animal
不可以看成Dog
。
我们再举一个例子来理解多态的好处:
class Animal(object):
def run(self):
print("动物在跑")
class Dog(Animal):
def run(self):
print("狗在跑")
class Cat(Animal):
def run(self):
print("猫在跑")
def run_twice(animal):
animal.run()
animal.run()
dog1 = Dog()
cat1 = Cat()
animal = Animal()
run_twice(animal)
run_twice(dog1)
run_twice(cat1)
运行截图如下:
可以看到不同的子类的实例调用了自己各自覆写的run()方法。这样我们不需要对run_twice()做任何修改,就可以打印出是哪种动物在跑。
由于Animal
类型有run()
方法,因此,传入的任意类型,只要是Animal
类或者子类,就会自动调用各自实际类型的run()
方法,如果子类没有覆写run()方法,就调用父类Animal类的run()方法,这就是多态。
这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的。
这就是著名的“开闭”原则:
对扩展开放:允许新增Animal
子类;
对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数。
继承还可以一级一级地继承下来,实现多级继承。
多态在静态语言和动态语言上的区别:
对于静态语言(如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了。
如:
class Timer(object):
def run(self):
print('Start...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
实例属性和类属性:
在Python中我们可以给实例绑定新属性,方法是通过实例变量,或者通过self
变量。
如:
class Student(object):
def __init__(self, name):
self.name = name
student1 = Student("zhangsan")
student1.score = 90
print(student1.score)
如果Student
类本身需要绑定一个属性呢,我们可以直接在class中定义属性,这种属性是类属性,归Student
类所有。
当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。
如:
class Student(object):
name = 'Student'
s = Student()
print(s.name)
print(Student.name)
s.name = 'Alice'
print(s.name)
print(Student.name)
del s.name
print(s.name)
运行截图如下:
第一句print(s.name)打印时先查找实例s的name属性,但s并没有name属性,所以继续查找并打印出类Student的name属性;
第二句print(Student.name)即打印出类Student的name属性;
给s.name赋值Alice后,第三句print(s.name)打印出了s的name属性;
第四句print(Student.name)依旧打印出类Student的name属性;
下面我们删除掉s的name属性,再次print(s.name),因为s的name属性没有了,所以又继续查找并打印出类Student的name属性。
总结:
由上面例子可以看出,在编写程序时,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。