Python的类、实例、构造函数、类的数据封装、类的方法、类的访问限制、类的继承和多态、实例属性和类属性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgcr654321/article/details/82729522

类:

面向对象最重要的概念就是类(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类中,每个实例都拥有各自的namescore这些数据。我们可以直接在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类,只知道创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

类的访问限制:

上面的Student类中,外部代码还是可以自由地修改一个实例的namescore属性。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,变成了一个私有变量(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("动物在跑")

现在我们想编写DogCat类时,这时我们就可以从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属性。

总结:

由上面例子可以看出,在编写程序时,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/82729522
今日推荐