目录
--- python中的类是用来描述具有相同属性和方法的对象的集合,对象是类的实例。
--- 定义类使用class关键字, 后面紧跟着类名,类可以包含属性和方法。
方法__init__():
--- __init__方法是一个特殊的方法,每当你根据类创建新的实例时,Python都会自动运行它。
--- 在方法的定义中,形参self必不可少,且必须位于其他形参的前面,每个与实例相关联的方法调用都会自动传递实参self,它是一个指向实例本身的引用。
类的属性
--- 类的属性:类内的称呼,其实就是类内的变量,同一个类内的不同方法内的变量都是这个类的属性,也就是这个类的变量。
--- 注意:定义类的属性并不一定要在__init__()方法中定义,当类被实例化后,即使在类外也可为次对象添加属性,但此属性只对此类的这个实例对象有效,对其他实例对象不起作用。(可通过“实例对象名.属性名 = ……”方式添加)如下最后一行:
class Person:
identity = "student" #定义类属性identity
_id1 = 1 #定义受保护类属性
__id2 = 2 #定义私有类属性
same = '0' #同名类属性
def __init__(self, first, last):
self.first_name = first #实例属性first_name
self.last_name = last #实例属性last_name
self._num1 = 1 #受保护实例属性
self.__num2 = 2 #私有实例属性
self.same = '00' #同名实例属性
p1 = Person("xiao", "ming") #类的实例化
p1.sex = "男" #为实例对象p1添加实例属性sex
函数与方法、变量与属性的区别
--- 函数和方法都是函数,定义在类内叫做方法,定义在类外或者单独使用叫做函数。
--- 变量和属性其实都是变量,定义在类内叫做属性,定义在类外或者单独使用叫做变量。
实例属性和类属性
--- 直接在类中创建的属性叫类属性。(如上述代码第一行)
--- 实例属性每个实例各自拥有,相互独立;而类属性有且只有一份,创建的实例都会继承字唯一的类属性。意思就是绑定在一个实例上的属性不会影响到其他的实例,如果在类上绑定一个属性,那么所有的实例都可以访问类属性,且访问的类属性是同一个,一旦类属性改变就会影响到所有实例(即类属性为所有实例所供有)。
--- 实例属性只能在创建实例对象后,通过实例对象直接访问或者通过方法修改属性的值。实例属性不可通过类名访问
--- 类属性则既可以不实例化直接通过类名调用类属性,又可以实例化后通过实例对象访问类属性:
p1 = Person("xiao", "ming") #类的实例化
p1.sex = "男" #为实例对象p1添加实例属性
print(Person.identity) #通过类名访问类属性
print(p1.identity) #通过实例名访问实例属性
print(Person.first_name) #试图通过类名访问实例属性 将报错
#报错信息 AttributeError: type object 'Person' has no attribute 'first_name'
#提示Person没有属性‘first_name’
私有属性与受保护属性
--- python不能像java那样使用private和protected创建私有属性和受保护属性,但Python有个简单的机制能够避免子类意外覆盖私有属性。
--- python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线(__)开头,尾部没有或最多有一个下划线,被称为私有属性,私有属性在类的定义中可以调用和访问,类的实例不可以直接访问,子类也不可以直接访问。(私有实例属性可以通过在类内定义实例方法访问,私有类属性既可通过在类内定义实例方法访问,也可通过在类内定义类方法访问)方法名自定:
class Person:
pass
def __init__(self, first, last):
pass
def get_num2(self): #定义实例方法获取私有实例属性__num2
return self.__num2
def get_id2(self): #定义实例方法获取私有类属性__id2
return Person.__id2
@classmethod #定义类方法获取私有类属性__id2
def fun_1(cla):
return Person.__id2
def set_num2(self, num2): #定义实例方法访问、修改私有实例属性__num2
self.__num2 = num2
def set_id2(self, id2): #定义实例方法访问、修改私有类属性__id2
Person.__id2 = id2
@classmethod #定义类方法访问、修改私有类属性__id2
def fun_2(cla, id2):
cla.__id2 = id2
p1 = Person("xiao", "ming") #类的实例化
--- 在属性名前加单个下划线(_)来表示这是个受保护的属性,虽然Python解释器不会对使用单个下划线前缀的属性名做特殊处理,但程序员们会严格遵守这个约定,不会在类外部访问这种属性。
p1 = Person("xiao", "ming") #类的实例化
p1.sex = '男'
print(p1._num1) #输出:1
p1._num1 = 2
print(p1._num1) #输出: 2
可以看到,对于有单个下划线前缀的属性,是可以进行访问和直接修改的,因为Python解释器不会对其进行任何特殊处理。
--- python会对私有属性做特殊处理:在属性名前加一个下划线和类名,然后将属性名存入实例的__dict__属性中:
p1 = Person("xiao", "ming") #类的实例化
p1.sex = '男'
print('\n'.join(['{0}:{1}'.format(item[0],item[1]) for item in p1.__dict__.items()]))
'''换行输出p1的所有实例属性
输出结果:
first_name:xiao
last_name:ming
_num1:1
_Person__num2:2 #python对私有实例变量__num2做特殊处理,变为_Person__num2
same:00
sex:男
'''
#私有类属性也做与私有实例属性相同的处理
所以在类外还可以有另一种方法直接访问私有属性:
p1 = Person("xiao", "ming") #类的实例化
p1.sex = '男'
print(p1._Person__num2) #输出:2
实例属性和类属性重名问题
--- 当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问(即此时通过实例对象访问此属性时,只能访问到同名的实例属性,而同名的类属性只能通过类名访问):
p1 = Person("xiao", "ming")
print(p1.same) #输出同名的实例属性'00' ,屏蔽对同名类属性的访问
print(Person.same) #此时同名类属性只能通过类名访问,输出'0'
--- 当类属性存在,不能通过实例直接修改类属性的值:
· 在类定义的外面,可以通过实例对象名直接获取类属性,但不能试图通过实例对象名直接修改类属性,这种做法实际上是为此实例对象绑定了一个与类属性同名的实例属性,对类属性无任何影响,
p1 = Person("xiao", "ming")
print(p1.identity) #可以通过实例对象名直接访问类属性,输出'student'
p1.identity = 'teacher' #试图通过实例直接修改类属性的值
print(p1.identity) #输出'student' 类属性并没有受到影响
print('\n'.join(['{0}:{1}'.format(item[0],item[1]) for item in p1.__dict__.items()]))
'''换行输出p1的所有实例属性
输出结果:
first_name:xiao
last_name:ming
_num1:1
_Person__num2:2
same:00
sex:男
identity:teacher
'''
·在类定义的内部,在实例方法内可以通过实例本身的引用self直接获取类属性,但同样不能试图通过self直接修改类属性,这种做法实际上是为类添加一个与类属性同名的实例属性,对类属性无任何影响:
class Person:
pass
def __init__(self, first, last):
pass
def get_ident(self):
return self.identity
def set_ident(self):
self.identity = 'teacher'
p1 = Person("xiao", "ming")
p1.sex = '男'
print(p1.get_ident) #输出:'student'
print('\n'.join(['{0} : {1}'.format(item[0],item[1]) for item in p1.__dict__.items()]))
'''输出:
first_name:xiao
last_name:ming
_num1:1
_Person__num2:2
same:00
sex:男 #此时发现实例属性并没有被添加,get_ident方法只是获取类属性identity
'''
#当调用set_ident()方法试图通过self修改类属性identity的值时:
p1.set_ident()
print(p1.get_ident) #输出:'teacher'
print('\n'.join(['{0} : {1}'.format(item[0],item[1]) for item in p1.__dict__.items()]))
'''输出
first_name:xiao
last_name:ming
_num1:1
_Person__num2:2
same:00
sex:男
identity:teacher #此时实例属性多了一个与类属性identity同名的属性
'''
--- 私有实例属性和私有类属性、受保护实例属性和受保护类属性重名问题与上述一样。
实例方法与类方法
--- 方法名可与类名相同。
--- 实例方法是类中定义的函数,实例方法的第一个参数是实例本身的引用self(参数名可自定),其他参数的传递与在类外函数定义相同。
--- 类方法是通过标记一个@classmethod,将该方法绑定到类上,而非类的实例,可通过类名访问,也可通过实例访问,将@classmethod写在方法的上一行,即将此方法定义为类方法,如:
class Person:
pass
@classmethod
def fun_a(cla):
print(type(cla), cla)
上述代码中方法fun_a()即为类方法,类方法的第一个参数为类本身的引用cla(参数名可自定),因为类方法在类上调用,而非在实例上调用,因此,类方法无法获得任何实例变量,只能获得类的引用与其他参数,同时,类方法内不可调用其他实例方法。
--- 实例方法与类方法的区别:
① 实例方法的第一个参数是实例本身的引用,而类方法的第一个参数为类本身的引用,类方法无法获取实例的引用。
② 类方法的定义是在实例方法定义的基础上加一个@classmethod标记。
注意:
--- 当类方法与实例方法同名时,由于实例方法优先级高于类方法,将屏蔽掉对类方法的访问,导致无法访问已定义的同方法:
class Person:
pass
@classmethod
def fun_x(cla):
print('类方法')
def fun_x(self):
print('实例方法')
p1 = Person("xiao", "ming")
p1.fun_x() #输出:实例方法
Person.fun_x()
#TypeError: fun_a() missing 1 required positional argument: 'self'
#Fun_a()缺少1个必需的位置参数:'self'
--- 当类属性(公有、私有或受保护)名与方法名(实例方法或类方法)同名时,将屏蔽掉对同名类属性的访问,导致无法访问已定义的类属性:
class Person:
pass
a = 'a'
def a(self):
print('def a')
p1 = Person("xiao", "ming")
print(p1.a)
#<bound method Person.identity of <__main__.Person object at 0x000001E6D82F54E0>>
#输出a()方法的位置
--- 实例属性名不可与方法名同名
私有方法
--- 方法名前加两个下划线(__),方法名后最多一个下划线,被python定义为私有方法。
静态方法
--- 静态方法是通过标记一个@staticmethod,将@staticmethod写在方法的上一行,即将此方法定义为静态方法,静态方法既可以通过实例对象调用也可以通过类调用。
class Person:
pass
def __init__(self, first, last):
pass
@staticmethod
def staticm(a):
print(a)
静态方法与类方法的区别:
--- 静态方法无需传入代表实例对象本身的引用参数self, ll也无需传入类本身的引用参数cla。
--- 静态方法是类中的"函数”。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
--- 静态方法中可以通过类名调用类属性和类方法。
方法作属性访问
--- 通过在类中方法的上一行标记一个@property
class Person:
pass
@property
def aaa(self): #@property将类中的方法aaa装饰为一个成员,装饰后可以在类外以访问属性的形式访问此方法
return self.first_name
@aaa.setter # @property本身又创建了另一个装饰器@aaa.setter
def aaa(self, value): #负责将方法参数变成属性赋值的形式传递参数
self.first_name = "da"
p1 = Person("xiao", "ming")
print(p1.aaa) #输出: xiao
@的另一种用法
--- 函数修饰器:使用方法:@函数名
出现在函数定义的前一行,不允许和函数定义在同一行,
一个修饰符就是一个函数,它将被修饰的函数作为参数,返回修饰后的同名函数或其他可调用的东西(如果返回不是一个可调用的对象,那么会报错):
def fun_a(A):
print("It's fun_a")
@fun_a
def fun_b(b):
print("It's fun_b")
#运行结果: It's fun_a
#若改一下:
def fun_a(A):
print("It's fun_a")
A()
@fun_a
def fun_b(B):
print("It's fun_b")
#运行结果: It's fun_a
It's fun_b
此处相当于直接调用以fun_b()为参数的fun_a()函数。
类的__dict__的使用
--- __dict__是属性,不是方法。
--- 若使用实例对象调用__dict__,则输出有所有实例属性组成的字典。
--- 用类对象调用__dict__将输出由所有实例方法、类属性和类方法组成的字典。
类的__str__()方法
--- __str__()方法用于返回对象的描述信息,如果不在类内定义__str__()方法,直接在对类的对象使用print()函数或return,返回的是对象的内存地址,
--- 如果在__str__()中定义了描述信息,当执行打印实例名的时候,返回的就不是内存地址,打印的实际是str的返回值,显示更友好,实现了类到字符串的转化。
类的继承
--- 编写类时,并非总是要从空白开始,如果要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法,原有的类称为父类或超类,而新类称为子类,子类继承了所有父类的所有属性和方法,同时还可以定义自己的属性和方法。
--- 在既有类的基础上编写新类时,通常要调用父类的方法__init__()。这将初始化在父类__init__()方法中定义的所有属性,从而让子类包含这些属性。
下面创建一个Person1类,它具备Person类的所有功能:
class Person:
pass
class Person1(person):
def __init__(self, first, last):
super().__init__(first, last)
首先是Person类代码,创建子类时,父类必须包含在当前文件中,且位于子类前面,然后是Person1子类,定义子类时,必须在圆括号内指定父类的名称。
super () 是一个特殊的函数,让你能够调用父类的方法,super () 有两种写法(以Person1类为例):
· super (Person1, self).__init__(first, last)。
· super () .__init__(first, last) #此方法在Python3中允许。
--- 子类在继承父类之后还可以定义自己的属性和方法:
class Person:
pass
class Person1(person):
def __init__(self, first, last):
self.first_name = 'first_name' #初始化与父类同名的属性
super().__init__(first, last) #在子类进行初始化时,也想继承父类的__init__()就通过super()实现
self.a = a #定义子类Person1特有的属性a
def a1(self, objiect1): #定义子类Person1特有的方法a1
print(object1)
注意: 若在子类继承父类初始化之前定义了一个与父类属性名相同的属性后,再进行继承父类初始化,则由于super () 在子类初始化后再执行,因此子类中与父类同名的属性会被父类的属性覆盖掉(即上述子类Person1中first_name属性会在执行super () 后被父类的first_name属性覆盖)。
重写父类方法
--- 对于父类的方法,只要它不能满足子类的需求时,都可以进行重写。
重写分两种:
· 覆盖父类方法: 可在子类中定义一个与要重写的父类方法同名的方法,这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
class Person:
pass
def fun_x(self):
print('父类实例方法')
class Person1(Person):
pass
def fun_x(self):
print('子类覆盖父类实例方法')
p1 = Person1('xiao','ming')
p1.fun_x() #输出:子类覆盖父类实例方法
· 对父类方法进行扩展:如果在开发中子类方法实现包含父类方法实现,或父类原本封装的方法实现是子类方法实现的一部分,就可以使用扩展的方法。需要使用super () 实现。
class Person:
pass
def fun_x(self):
print('父类实例方法')
class Person1(Person):
pass
def fun_x(self):
super().fun_x()
print('子类扩展父类实例方法')
p1 = Person1('xiao','ming')
p1.fun_x()
#输出:
# 父类实例方法
# 子类扩展父类实例方法
多继承
--- 多继承可以继承多个父类,也继承了所有父类的属性和方法。
--- 如果多个父类中有同名的属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性__mro__的顺序查找类)。
将实例用作属性
--- 使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。这种情况下,可能需要将类的一部分提取出来,作为一个独立的类,可将大型的类拆分成多个协同工作的小类。
例如,不断给上述的Person类添加细节时,我们可能发现其中包含很多专门针对 人的姓名方面的属性和方法。在这种情况下,可将这些属性和方法提取出来,放到一个名为Name的类中,并将一个Name实例作为Person类的属性:
class Name:
def __init__(self, first, last):
self.first_name = first
self.last_name = last
def print_name(self):
print(f"person's name is {self.first_name}{self.last_name}")
class Person:
pass
def __init__(self, first, last):
pass
self.name = Name(first, last) #添加一个名为name的属性,此行代码让python创建一个新的Name实例,并将该实例赋给属性self.name
p1 = Person("xiao", "ming")
p1.name.print_name() #输出:person's name is xiaoming
每当Person类中__init__()方法被调用时,都将让python创建一个新的Name实例并赋给 self.name,因此现在每个Person实例都包含一个自动创建的Name实例。
导入类
--- 随着不断给类添加功能,文件可能变得很长,即便妥善的使用了继承亦如此,为遵循python的总体理念,应让文件尽可能整洁,在此方面,python允许将类存储在模块中,然后在主程序中导入所需的模块。 模块是扩展名为.py的文件,假设已经创建了一个模块person,里面包含类Person,则需要使用关键字import:
#此为模块名为person的模块
class Person:
pass
1> . 如上,若包含类的模块与主程序在同一文件夹中,则可直接导入:
import person
p1 = person.Person("xiao", "ming")
#另一种方式:
from person import Person
p1 = Person("xiao", "ming")
2>. 若包含类的模块与主程序不在同一文件夹下,如下:
则需要我们将包含类的模块所在的文件夹添加至python的搜索路径,既可导入成功:
--- 添加路径有三种方式
· ① 动态添加路径:
在程序运行过程中修改sys.path的值(不过此方法属于临时修改,只会在运行时生效,一旦退出,便失效):
import sys
sys.path.append(r"D:\自定义类模块")
将此代码添加至主程序中,既可按照1>所示的两种方法导入类。
· ② 通过修改pythonpath环境变量(待定)。
· ③ 通过添加**.pth文件: 在python安装目录下的 \lib\site-packages 文件夹中建立一个.pth 文件,内容为自己需要添加的路径。
装饰器
类的__call__() 方法
--- __call__() 方法的作用是把一个类的实例化对象变成像函数一样的可调用对象,但在默认情况下该方法在类中是没有被实现的,需要自己定义。
看一个简单案例:
class Person:
def __init__(self, first, last):
self.first_name = first
self.last_name = last
def __call__(self):
print(f"hello {self.first_name}{self.last_name}")
p1 = Person("xiao", "ming")
p1.__call__() #输出:hello xiaoming
p1() #输出:hello xiaoming #即此时类的实例对象可以像函数一样调用
可以看到,上述的两种形式p1.__call__ 与 p1() 的结果是一样的,而若在类中为定义__call__方法,在外部使用p1() 便会报错:
TypeError Traceback (most recent call last)
<ipython-input-1-aaf0728be047> in <module>()
p1.Person()
---> p1()
TypeError: 'Person' object is not callable
类作为装饰器
class Person:
def __init__(self, func):
self.func = func
self.count1 = 0
def __call__(self):
self.count1 += 1
return self.func
@Person
def func():
pass
for i in range(10):
func()
print(func.count1) #输出:10
被修饰的函数以参数形式传递给类,并返回 func = Person(func), 此时func已经是Person实例,而不是函数本身func。在__init__()中对实例对象func初始化后,调用func() 就相当于调用func.__call__()。
为类加装饰器
修改类属性的装饰器
def func1(cls): #以类为参数,传入类cls
cls.first_name = "li" #修改类属性
return cls #返回已被修饰的类
@func1
class Person:
first_name = "xiao"
print(Person.first_name) #输出: li
设置类方法的装饰器
def func2(func):
def func3(cls):
cls.func1 = func
return cls
return func3
def func():
print("this is func") #为类装饰的函数需要定义在@func2的前面
@func2
class Person:
pass
Person.func1() #输出: this is func