Object-oriented programming in Python

Introduction to object-oriented programming in Python, including packaging, inheritence, and porlimorphism

Posted by Yingshan Li on September 8, 2020

Object-oriented programming (OOP)

Procedural programming is about writing procedures or functions that perform operations on the data, while object-oriented programming is about creating objects that contain both data and functions.

Object-oriented programming has several advantages over procedural programming:

  1. OOP is faster and easier to execute
  2. OOP provides a clear structure for the programs
  3. OOP helps to keep the C++ code DRY “Don’t Repeat Yourself”, and makes the code easier to maintain, modify and debug
  4. OOP makes it possible to create full reusable applications with less code and shorter development time

Class and instance

抽象的能力可能是人类最引以为豪的能力之一了,在日常生活和科学研究中抽象处处可见,例如我们说”人”“苹果”这样的概念就是抽象的,而具体的一个人和一个苹果就是这个抽象的一个实例,不同的人和不同的苹果之间又有一些差异,但是我们仍然能够分辨出来这是一个人还是一个苹果,这是因为不同的苹果之间虽然有差异,但是他们又有很多的本质上的相同点,比如他们都有语言能力等等。这就是我们面向对象编程中类和实例的基本思想。

定义一个类:

class ClassName:
	statement

举个例子:

class human:
	def __init__(self, name, age):
		self.name = name
		self.age = age
	
	def hello(self):
		print("Hello, I'm", self.name)
		
person1 = human("Yingshan", 25)
person2 = human(age=23, name="Lee")

这样就创建了一个人(human)的实例,也就是一个person。

属性和方法

上面那个例子引出了另外两个概念,属性和方法。一个类对象就是具有相同属性和方法的实例的0集合,比如一个人的名字和年龄等等就是人的属性,能够说“hello”就是人特有的方法,当然还可以有其它的属性和方法。

创建类的时候我们通过__init__的特殊函数来绑定这个类的属性,注意前后都有两个下划线。__init__函数第一个参数是self,表示实例本身,然后就是其它的参数,和别的函数没有什么不同。

定义类的方法和定义一个普通函数基本相同,但是第一个参数一定是self,这样在创建一个实例的时候,这个实例自然拥有了这个方法(函数),比如能够说hello。

调用类的属性和方法很简单,只需要用点号引用就好了,object.name

继承和多态

继承和多态应该是最能体现面向对象编程核心思想的了,继承的意思应该比较好理解,前面我们提到了类的概念,就是拥有同样属性和方法的全部实例的集合,比如所有有语言能力的个体抽象成”人”。那么比人更加抽象的概念有哪些呢?很简单,你可以说人是”动物”,也可以接着说人是”真核生物”,还可以接着说人是”生物”。因此,凡是”动物”这个类所拥有的属性,人应该全部拥有,不需要重新定义,只需要直接继承下来就可以了。但是反过来就不行了,因为不是所有的动物都是人类,不是所有的真核生物都是动物。

class animal():
	def mobile(self):
		return ("Yes, animal are mobile")

class human(animal):
	def __init__(self, name, age):
		self.name = name
		self.age = age
	
	def hello(self):
		print("Hello, I'm", self.name)

这样,human这个类就直接继承了animal这个类中的方法,所有的human实例都可以直接调用animal的方法。我们称human为子类,animal为父类。

有的人可能已经想到了,如果父类和子类拥有相同的方法,但是我又不想完全继承父类的方法怎么办?比如,动物能动,人也能动,但是我不希望人运动的方式和其它的动物完全一样。这种情况下,你只需要在子类里面重新定义一下mobile这个方法就好了,因为子类的方法会自动覆盖父类的方法。

class animal():
	def mobile(self):
		return ("Yes, this animal is mobile")

class human(animal):
	def __init__(self, name, age):
		self.name = name
		self.age = age
	
	def hello(self):
		print("Hello, I'm", self.name)
		
	def mobile(self):
		return ("Yes, this person is mobile")

面向对象高级

私有变量

私有变量命名的时候,在名字前有两个下划线,这样的话这个变量就是私有变量,只能在类的内部才能访问,在外部无法访问。

class person():
	def __init__(self, name):
		self.__name = name

如果变量不是私有变量的话,那么在类的外面是可以随时访问并修改变量的值的。因此当有些重要的变量不允许被随意修改时,最好设为私有变量。另外私有变量还有一个好处就是可以在类的内部设置函数对参数进行检查,避免无效参数输入

class person():
	def __init__(self, weight):
		self.__weight = weight
		
	def set_weight(self, weight):
		if weight < 100 :
			print ("Too light!")
		if weight >400 :
			print ("Too heavy!")

使用@property

按理论如果我们想对输入的weight参数进行检查时,我们需要在类里面定义set_weight()和get_weight()两个函数,

class person():

	def get_weight(self):
		return self._weight
		
	def set_weight(self, value):
		if weight < 100 :
			print ("Too light!")
		if weight >400 :
			print ("Too heavy!")
		self._weight = value
	
p1 = person()
p1.set_weight() = 160
p1.get_weight()

这样每次调用函数显得还是有点麻烦,Python中可以用@property装饰器将getter方法变成属性,与此同时,另外一个装饰器@weight.setter可以将setter方法编程属性赋值,

class person():

	@property
	def weight(self):
		return self._weight
	
	@weight.setter	
	def weight(self, value):
		if weight < 100 :
			print ("Too light!")
		if weight >400 :
			print ("Too heavy!")
		self._weight = value
	
p1 = person()
p1.weight = 160
p1.weight

使用__slots__

理论上我们可以给实例添加无数种属性,但当我们需要限制实例的属性时,可以使用__slots__

class person():
	__slots__ = ("name", "country")
	
p1 = person()
p1.name = "Yingshan"
p1.country = "China"
pi.weight #报错

类的特殊成员

  1. __doc__跟模块里面一样,表示类的注释
class person():

	"""
	This is a person class
	This is..
	"""
	def __init__(self, name):
		self.__name = name
		
print(person.__doc__)
  1. __call__对象末尾加括号后执行
class person():
	def __init__(self, name):
		self.__name = name
	def __call__(self):
		print("call myself")
		
Yingshan = person("Yingshan")
Yingshan()
  1. __dic__返回类或对象中的所有成员,以键值对形式输出
class person():
	def __init__(self, name):
		self.__name = name
		
Yingshan = person("Yingshan")
print(Yingshan.__dict__)
  1. __str__是在类中定义的特殊方法函数,当用print()打印对象的时候,就输出该方法的返回值
class person():
	def __init__(self, name):
		self.__name = name
	def __str__(self):
		print("This is person class")
		
Yingshan = person("Yingshan")	
print(Yingshan)

如果没有__str__函数,直接打印一个对象,输出的是这个对象的内存地址

  1. __iter__可以将对象变成可迭代,事实上像list,tuple等对象能够迭代其实就是对象内部定义了__iter__函数。
class person():
	def __init__(self, names):
		self.__names = names
	def __iter__(self):
		return iter(self.names)
	
friends = person(["Yingshan", "Mike", "Tim"])	
for guy in friends:
	print(guy)

__iter__内部,iter()函数将实例对象转化成一个迭代器(Iterator),因此当用for来调用它的时候,就可以不断调用next函数。

  1. __getitem____setitem____delitem__ 用于索引操作,对对象进行添删改
class person():

	def __init__(self):
		self.dic = {}

	def __setitem__(self, key, value):
		self.dic[key] = value
		
	def __getitem__(self, key):
		return self.dic[key]
		
	def __delitem__(self, key):
		del self.dic[key]
	
obj = person()
	
obj["p1"] = "Yingshan" #自动执行__setitem__
obj["p1"]	#自动执行__getitem__
del obj["P1]	#自动执行__delitem__