Python从设计之初就已经是一门面向对象的语言, 面向对象编程三大特性如下:
封装: 隐藏实现细节,使代码模块化
继承: 扩展已存在的类来实现代码重用,避免重复编写相同的代码
多态: 封装和继承的目的都是为了实现代码重用, 而多态是为了实现接口重用,使得类在继承和派生的时候能够保证任何一个类的实例都能正确调用约定好的属性和方法
在python中, 每个对象都有三个属性:
类是一种抽象数据类型,是对现实世界的一类数据及其操作的封装
类实例化后,可以使用其属性,创建一个类后,可以通过类名访问其类属性
如下所示, 定义一个含有三个属性的person类, 分别是ID(身份证号)、name(姓名)、nationality(国籍)
其中所有人的身份证号码都是不一样的,且不允许直接通过类或实例来访问或随意修改
import uuid
class Person:
nationality = "China"
#类的初始化
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" %(self.name, self.nationality, self.__id))
import uuid
class Person:
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name, self.nationality, self.__id))
user = Person('henry') #创建一个类实例化对象user
user.hello() #输出Hello, I am henry, I come from China, My ID is cea8b568-4f53-11ed-a7de-00e04c8c73bb
Python中默认所有的成员都是公有成员,但私有成员是以两个下划线开头的名字表示私有成员,私有成员不允许直接访问,只能通过内部方法进行访问,私有成员也不允许被继承
Python中通过在类变量、实例变量、类方法、实例方法前加__
前缀,可以将其对外进行隐藏,变为类的私有变量或函数
由于Python中内置变量或函数使用__
作为前后缀,因此不推荐私有的变量或函数在前后缀都用__
,而是只在前缀用__
Python类维护了一个用于保存类的数据的字典,字典内部Python将私有成员改名为_ClassName__variable
,因此在类外通过访问私有变量新的名称可以访问相应的私有变量, 如下代码所示:
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name, self.nationality, self.__id))
if __name__ == "__main__":
bauer = Person("Bauer")
print(bauer.__dict__) #输出存放私有成员的字典
print(bauer._Person__id) #输出该对象的person类的ID
# 输入如下:
# {'name': 'Bauer', '_Person__id': 'ed496846-94c7-11e9-80c4-5ce0c5e8bcf0'}
# ed496846-94c7-11e9-80c4-5ce0c5e8bcf0
直接定义在class下的属性是叫公有属性或类属性,类属性是类的所有实例对象共同所有的
类属性可以使用ClassName.VariableName
访问,在实例方法内部也可以使用self.VariableName
进行访问
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality,self.__id))
def sayHello(self):
print("Hello,I come from %s" % self.nationality)
if __name__ == "__main__":
bauer = Person("Bauer")
bauer.sayHello() #Hello,I come from China
jack = Person("Jack")
print(Person.nationality, bauer.nationality, jack.nationality) #输出China China China
bauer.nationality = "USA"
print(Person.nationality, bauer.nationality, jack.nationality) #输出China USA China
Person.nationality = "Germany"
print(Person.nationality, bauer.nationality, jack.nationality) #Germany USA Germany
在上述代码中, 将类属性 nationality
修改为 “Germany” 时,并没有修改实例 bauer
的 nationality
属性。因此,实例 bauer
的 nationality
属性的值仍然是 “USA”。
在 Python 中,如果实例有一个与类属性同名的属性,则实例的属性会覆盖类属性。例如,当你在类外面使用 bauer.nationality = "USA"
时,你为实例 bauer
创建了一个名为 nationality
的属性,值为 “USA”。这个属性会覆盖类属性 nationality
,因此在使用 bauer.nationality
访问实例属性时,会返回 “USA” 而不是 “Germany”
实例属性又称成员属性或成员变量,是类的每个实例对象单独持有的属性, 实例属性必须在类的__init__
方法中进行声明
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name #name属于实例属性
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))
if __name__ == "__main__":
bauer = Person("Bauer")
jack = Person("Jack")
print(bauer.name, jack.name) #输出"Bauer Jack"
若要访问实例属性, 只能通过实例对象访问, 若通过类访问则会报错, 如下所示
print(Person.name)
私有属性和实例属性必须在__init__
方法中进行声明,但私有属性的属性名需要以双下划线__
开头, 例如上述Person中的__id
属性
私有属性是一种特殊的实例属性,只允许在实例对象的内部访问, 且不能被子类继承
私有属性可以通过成员方法或是<实例对象._类名__私有变量名>
的方式来访问
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name #实例属性
self.__id = str(uuid.uuid1()) #私有属性
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" %(self.name,
self.nationality, self.__id))
def get_id(self):
return self.__id
if __name__ == "__main__":
bauer = Person("Bauer")
bauer.hello()
print(bauer._Person__id) #访问bauer成员的私有属性ID
print(bauer.get_id()) #通过成员方法get_id()访问私有属性ID
Python的类中有一些内置的、特殊的属性,其名称以双下划线__
开头且以双下划线__
结尾。特殊属性不是私有属性,可以在类的外部通过实例对象去直接访问,如下是常见的特殊属性
__doc__
:类的描述信息。
__module__
:对象定义所在的模块名。
__class__
:当前操作的对象对应的类名。
__dict__
:一个字典,保存类的所有的成员(包括属性和方法)或实例对象中的所有成员属性
实例对象.__dict__
和 类.__dict__
的值是不同的,实例对象.__dict__
的值中只包含成员属性和私有属性,类.__dict__
的值中包含类的类属性和所有方法
成员方法可以通过类的实例或类名调用,但使用类名时需要手动传入一个实例对象作为 self 参数。
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" %(self.name,
self.nationality, self.__id))
if __name__ == "__main__":
bauer = Person("Bauer")
bauer.hello() #通过类的实例对象调用
Person.hello(bauer) #通过类名调用
#两次输出的内容一致:Hello, I am Bauer, I come from China, My ID is 5beff5b1-500e-11ed-b861-00e04c8c73bb
私有方法是以双下划线__
开头的成员方法
私有方法只能在实例方法内部访问,且不能被子类继承;私有方法的第一个参数也必须是当前实例对象本身,通常写为self
前后加双下划线的命名方式用于Python内置的方法,不推荐自定义方法使用。
如果开发者以前后加双下划线的方式命名成员方法,则相应成员方法是公有的
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def __hello(self): # 私有方法
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))
def say_hello(self): # 成员方法/实例方法
self.__hello()
if __name__ == "__main__":
bauer = Person("Bauer")
bauer.say_hello() #输出Hello, I am Bauer, I come from China, My ID is 5beff5b1-500e-11ed-b861-00e04c8c73bb
类方法是以@classmethod
来装饰的成员方法,要求第一个参数必须是当前类, 通常写为cls
类方法可通过实例对象进行访问,还可以直接通过类名去访问
类方法只能访问类属性,不能访问实例属性,因此第一个参数传递的是代表当前类的cls
,而不是表示实例对象的self
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def __hello(self): # 私有方法
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,
self.nationality, self.__id))
def say_hello(self): # 成员方法/实例方法
self.__hello()
@classmethod
def get_nationality(cls): # 类方法
return cls.nationality
if __name__ == "__main__":
bauer = Person("Bauer")
print(bauer.get_nationality()) #通过实例对象调用类方法
print(Person.get_nationality()) #通过类名调用类方法
静态方法是以@staticmethod
来装饰的成员方法
静态方法通常通过类名进行访问,也可以通过类的实例对象进行访问
静态方法已经与类没有任何关联,因此定义静态方法不要求必须传递实例对象或类参数
静态方法对参数没有要求,因此可以任意给静态方法定义参数,如果给静态方法定义表示当前类的参数(cls),那么就可以访问类属性;如果给静态方法定义了表示当前类的实例对象的参数(self),那么就可以访问实例属性;如果没有给静态方法定义当前类参数或当前实例参数,那么就不能访问类或实例对象的任何属性
import uuid
class Person(object):
sum = 0
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
Person.sum += 1
@staticmethod
def add(a, b): # 静态方法
return a + b
@staticmethod # 静态方法,内部使用类变量
def counter():
return Person.sum
@staticmethod
def get_counter(cls): # 静态方法,传递当前类
return cls.sum
if __name__ == "__main__":
bauer = Person("Bauer")
print(bauer.add(1, 2)) #通过实例对象调用静态方法
print(Person.add(1, 2)) #通过类名调用静态方法
print(Person.counter())
print(Person.get_counter(Person)) #调用需传递类参数
属性方法是以 @property
装饰的成员方法,用来访问实例属性。属性方法的第一个参数必须是当前实例,且必须有返回值
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def __hello(self): # 私有方法
print("Hello, I am %s, I come from %s, My ID is %s" %(self.name,
self.nationality, self.__id))
def say_hello(self): # 成员方法/实例方法
self.__hello()
@classmethod
def get_nationality(cls): # 类方法
return cls.nationality
@staticmethod
def add(a, b): # 静态方法
return a + b
@property
def id(self):
return self.__id
if __name__ == "__main__":
bauer = Person("Bauer")
print(bauer.id)
Python的类中有一些内置的、特殊的方法,其名称是以双下划线__
开头且以双下划线__
结尾。特殊方法不是私有方法,可以在类的外部通过实例对象去直接访问,且都有着各自特殊的意义
__init__
构造方法__init__
方法是类构造函数,是类的特殊的方法,在创建类对象时自动调用,不能有返回值
__init__
方法的第一个参数必须是创建的实例本身,通常推荐使用self。类的实例属性、私有属性必须在__init__
方法进行声明
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
print(self.name, "__init__")
if __name__ == "__main__":
bauer = Person("Bauer") #输出"Bauer __init__"
__del__
析构方法__del__
是类的析构方法,当对象在内存中被释放,会自动触发执行__del__
方法,如实例对象的作用域退出时,或者执行 del
实例对象操作
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
print(self.name, "__init__")
#定义析构方法
def __del__(self):
print(self.name, "__del__")
if __name__ == "__main__":
bauer = Person("Bauer") #输出"Bauer __init__"
del bauer #删除实例对象,触发析构函数,输出"Bauer __del__"
__str__
若类中定义了__str__
方法,那么在打印对象时默认输出__str__
方法的返回值,否则会打印出实例对象的内存地址
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
print(self.name, "__init__")
def __del__(self):
print(self.name, "__del__")
def __str__(self):
return "name: %s, nationality: %s, id: %s" % (self.name,self.nationality, self.__id)
if __name__ == "__main__":
bauer = Person("Bauer")
print(bauer)
#输出内容如下:
# Bauer __init__
# 输出name: Bauer, nationality: China, id: 27c26af1-508a-11ed-899f-00e04c8c73bb
# Bauer __del__
__new__
其实在python中, __init__
并不是真正的构造函数,__new__
加__init__
才是真正的构造函数
当我们在对类进行实例化的时候, __new__
方法会在__init__
方法前被执行, 会创建并返回一个新的实例对象传给__init__
在下面的例子中, 我们把类demo进行实例化对象为a, 那么在这个过程中, 类demo是如何确定它的实例对象会是a的呢?
这是因为__new__
方法返回了这个值, 而这个值就是a, 最后python解析器将这个返回值告诉给__init__
方法, 在__init__
方法中的参数self
其实就是这个a, 因为self代表的是类的对象
class demo():
def __init__(self,arg,kwarg): #定义属性并初始化
self.arg = arg
self.kwarg = kwarg
def Output(self):
print(self.arg)
print(self.kwarg)
a = demo("NMSL","WSND") #实例化
a.Output() #调用类中的Output方法
__new__
方法常用于单例设计模式, 它是由object基类提供的内置静态方法
class demo(object):
ins = None
def __new__(cls):
if cls.ins == None:
cls.ins = super().__new__(cls)
return cls.ins
a = demo()
b = demo()
print(a) #0x000001CE34C96FA0>
print(b) #0x000001CE34C96FA0>
__call__
类中定义__call__
方法时,类对象实例可以作为一个函数去调用
import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def __call__(self, *args, **kwargs):
print("name: ", self.name, "args: ", *args)
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" %(self.name,
self.nationality, self.__id))
if __name__ == "__main__":
bauer = Person("Bauer")
bauer(26) #将实例对象看成函数来传参进行调用,输出:"name: Bauer args: 26"
__reduce___
在 Python 中,类的 __reduce__
方法是一个特殊方法,用于描述对象的可序列化表示。当你使用内置的序列化函数(如 pickle.dumps
)将对象序列化时,会调用对象的 __reduce__
方法
__reduce__
方法必须返回一个元组,元组中包含两个元素
import pickle
shellcode = "list1 = [1,2,3,4]"
class A(object):
def __reduce__(self):
return (exec,(shellcode,))
#当实例对象被序列化后,则会调用特殊方法__reduce__,所以下列代码相当于pickle.dumps((exec,(shellcode,)))
ret = pickle.dumps(A())
print(ret)
#输出:b'\x80\x04\x95-\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04exec\x94\x93\x94\x8c\x11list1 = [1,2,3,4]\x94\x85\x94R\x94.'
pickle.loads(ret)
print(list1)
#输出[1,2,3,4]
Python中类的继承按照父类中的方法是否已实现可分为两种:
若根据继承父类的个数来分类, 又可分为两种:
派生类的构造函数需要显式调用父类的构造函数,对父类的属性成员进行初始化,调用父类的构造函数时需要显式传递实例对象self
什么是显示调用和隐式调用? —— 在python使用
3+2
或3*2
时,您似乎没有显式地调用任何方法,但实际上您调用了它,因为它们的实现方式是调用(3).__add__(2)
或(3).__mul__(2)
。因此,您隐式地调用这些方法
子类需要在自己的__init__
方法中的第一行位置调用父类的构造方法, 以下有两种方法, 在上述代码的注释中也进行了具体描述
super.(子类名, self).__init__(父类构造参数)
父类名.__init__(self,父类构造参数)
如下代码所示, Teacher类和Student类都继承了Person类, 也就是说Teacher类和Student类是Person类的子类或派生类, 而Person类是Teacher类和Student类的父类、基类或超类
Teacher和Student对Person的继承属于实现继承,且是单继承
Teacher类和Student类都继承了Person类的name和age属性, 以及talk()
和walk()
方法, 并扩展了自身的属性和方法
Teacher类和Student类可以在自己的类定义中重新定义父类Person的方法, 这种我们称为方法重写
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def walk(self):
print('%s is walking...' % self.name)
def talk(self):
print('%s is talking...' % self.name)
class Teacher(Person):
def __init__(self, name, age, level, salary):
super(Teacher, self).__init__(name, age) #派生类调用父类构造函数的第一种方法:super.(子类名, self).__init__(父类构造参数)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching...' % self.name)
class Student(Person):
def __init__(self, name, age):
Person.__init__(self, name, age) #派生类调用父类构造函数的第二种方法:父类名.__init__(self,父类构造参数)
def study(self):
print('%s is studying...' % self.name)
isinstance
: 判断一个类对象是否是类的对象或者是类的子类对象issubclass
: 判断一个类是否是某个类的子类class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def walk(self):
print('%s is walking...' % self.name)
def talk(self):
print('%s is talking...' % self.name)
class Student(Person):
def __init__(self, name, age):
Person.__init__(self, name, age) #派生类调用父类构造函数的第二种方法:父类名.__init__(self,父类构造参数)
def study(self):
print('%s is studying...' % self.name)
if __name__ == '__main__':
Tom = Student('tom',10)
print(isinstance(Tom,Person)) #判断类对象Tom是否是Person类的子类对象或类对象,返回True
print(isinstance(Tom,Student)) #判断类对象Tom是否是Student类的子类对象或类对象,返回True
print(issubclass(Student,Person)) #判断Student类是否是Person类的子类,返回True
Python支持多层父类继承, 子类会继承父类以及父类的父类所有的属性和方法
在多继承时, 使用的super()
函数只会调用第一个父类的属性方法, 若想调用特定父类的构造函数, 只能使用父类名.__init__
这种方式调用
若在多个继承的父类中有相同的方法名, 而在子类未使用显示调用父类的方法, Python会根据继承顺序从左至右搜索查找父类中是否包含方法。
class B():
def __init__(self):
print("class B")
def hello(self):
print('hello, class B')
class C():
def __init__(self):
print("class C")
def hello(self):
print('hello, class C')
class D(B, C):
def __init__(self):
super(D,self).__init__() #调用第一个父类(B)的构造函数
print("class D")
if __name__ == "__main__":
d = D()
d.hello() #调用父类B的hello(),而没有调用父类C的hello()
#输出内容:
# class B
# class D
# hello, class B
类的属性__mro__
或者方法mro()
都能打印出类的继承顺序,super()
在执行时查找MRO列表,到列表当前位置的类中去查找其下一个类,也就是说为了实现继承, python会在MRO列表从左到右开始查找父类, 直到找到第一个匹配属性的类为止
super是MRO中的一个类。MRO全称Method Resolution Order,代表类的继承顺序。对于定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表,MRO列表是一个简单的所有基类的线性顺序列表
class B():
def __init__(self):
print("class B")
def hello(self):
print('hello, class B')
class C():
def __init__(self):
print("class C")
def hello(self):
print('hello, class C')
class D(B, C):
def __init__(self):
super(D,self).__init__() #调用第一个父类(B)的构造函数
print("class D")
if __name__ == "__main__":
print(D.mro()) #输出[, , , ]
接口的所有子类必须实现接口中定义的所有方法;接口的各个子类在实现接口中同一个方法时,具体的代码实现各不相同,即为多态
多态通常是通过继承接口的方式实现的,虽然Python中没有接口这个概念,但Python中可以通过在一个成员方法体中抛出一NotImplementedError
异常来强制继承接口的子类在调用接口方法前必须先实现接口方法
举个例子,我们定义了一个名为 Shape
的类,用于表示形状。这个类中有一个方法 area
用于计算形状的面积,但是并没有为这个方法实现具体的代码逻辑。这样,当我们尝试调用 Shape
类的 area
方法时,就会抛出 NotImplementedError
异常,告诉调用者这个方法尚未实现
class Shape:
def area(self):
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius **
Python中反射机制是通过hasattr
、getattr
、setattr
、delattr
四个内置函数实现的,四个内置函数不仅可以用在类和对象中,也可以用在模块等
hasattr(obj,key)
: 返回bool值, 判断某个成员或者属性在不在类或对象中getattr(obj,key,default=xxx)
: 获取类或者对象的成员或属性, 若不存在, 则会抛出AttirbuteError异常; 若定义了default, 那么当没有属性的时候会返回默认值setattr(obj,key,value)
: 用于修改对象的属性值, 若有key属性, 那么更新key属性, 若没有则添加key属性并赋值valuedelattr(obj,key)
: 删除key属性import uuid
class Person(object):
nationality = "China"
def __init__(self, name):
self.name = name
self.__id = str(uuid.uuid1())
def hello(self):
print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))
if __name__ == "__main__":
bauer = Person("Bauer")
setattr(bauer, "sex", "Man") #设置bauer的成员属性
print(getattr(bauer, "name")) #获取bauer的name属性值,输出:Bauer
print(getattr(bauer, "sex")) #获取bauer的sex属性值,输出:Man
print(hasattr(bauer,'sex')) #判断bauer是否有名为'sex'的属性或成员,输出:True
print(hasattr(bauer,'hello')) #判断bauer是否有名为'hello'的属性或成员,输出:True
delattr(bauer,'sex') #删除bauer的sex属性
print(getattr(bauer, "sex")) #抛出异常:AttributeError: 'Person' object has no attribute 'sex'