参考了《Python3从入门到实战》
参考了大拿老师讲的Python课
Python关于类的概念
一、定义一个类
class Employee:
def printInfo(self):
print(self.name,"--->",self.salary)
e1 = Employee()
e2 = Employee()
e1.name = "许嵩"
e1.salary = 5000
e2.name = "薛之谦"
e2.salary = 10000
e1.printInfo()
e2.printInfo()
运行结果如下:
许嵩 ---> 5000
薛之谦 ---> 10000
二、构造函数__Init__()
1.python创建一个类的实例对象是通过一个叫做构造函数__init__()的方法完成的,类Employee(上述)虽然没有定义这个构造函数,但python会自动生成一个默认的__init__()方法
2.第一个参数必须是一个叫做self的参数,这个参数指向要创建的对象.默认的构造函数会调用其父类的super().__init__方法。并且这个对象从父类继承下来的属性进行初始化,super用于得到这个类的父类
class Employee:
def __init__(self):
print("正在创建一个对象")
super().__init__()
e = Employee()#这里初始化对象,自动调用__init__()方法
运行结果如下:
正在创建一个对象
三、类属性:指类所有对象都共有的属性,是定义在类方法外面的属性
class A:
count = 0#这个就是类属性
def __init__(self):
A.count += 1#每次新建对象,该类属性都会自加1
print(A.count)
a1 = A()
a2 = A()
a3 = A()
print(A.count)
class A:
count = 0#类属性
def __init__(self,name):
self.name = name
A.count += 1
a = A("xusong")
print(A.count)
print(a.count)
四、可以用__dict__查看类与实例的属性
class A:
name = "xusong"#类属性
def __init__(self,name):
self.name = name
def Print(self):
print("self",self.name)
print("class",__class__.name)#类的属性(用__class__指代类A)
a = A("杰伦")
a.Print()
print(a.__dict__)#对象中所有成员检查
print(A.__dict__)#类中内置属性
运行结果如下:
self 杰伦
class xusong
{
'name': '杰伦'}
{
'__module__': '__main__', 'name': 'xusong', '__init__': <function A.__init__ at 0x0000023B73F93820>, 'Print': <function A.Print at 0x0000023B73F93A60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
五、类属性
注: 可以通过类名.类属性查询或者修改类属性 ,也可以通过实例名.实例属性(包括self.类属性)来查询实例属性 但不能通过实例名.类属性(包括self.类属性)的方式来修改类属性,否则就是创建了实例属性.而不是访问了类属性.
class A:
count = 0
def __init__(self,name):
self.name = name
A.count += 1
print(A.count)
a = A("xusong")
a.count = 2
print(a.count)
print(A.count)
运行结果如下:
0
2
1
class A:
count = 0
def __init__(self,name):
self.name = name
A.count += 1
print(A.count)
a = A("xusong")
A.count = 2
print(a.count)
print(A.count)
类的内置属性
class A:
'''
函数文档写在这
'''
def __init__(self,name):
self.name = name
a = A("name")
'''
类的内置属性
__dict__:以字典的方式显示类的成员组成
__doc__:查看类的文档信息
__name__:获取类的名称,如果在模块中使用,则是获取模块的名称
__bases__:获取某个类的所有父类,以元组的方式
'''
print(A.__dict__)
print(A.__doc__)
print(A.__name__)
print(A.__bases__)
运行结果如下:
{
'__module__': '__main__', '__doc__': '\n 函数文档写在这\n ', '__init__': <function A.__init__ at 0x0000023B73F93280>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>}
函数文档写在这
A
(<class 'object'>,)
del 运算法可以删除一个对象的实例属性或者类属性
class A:
def __init__(self,name):
self.name = name
a1 = A("a1")
a2 = A("a2")
print(a1.name)
print(a2.name)
del a1.name
#不影响另一个
print(a2.name)
运行结果如下:
a1
a2
a2
class A:
count = 0
def __init__(self,name):
self.name = name
a1 = A("a1")
a2 = A("a2")
del A.count
print(A.count) #这样就会报错
六、私有属性
在python中可以通过类的属性名前面加两个下划线来禁止外界对属性进行访问,外界可以通过修改名字做到访问并修改私有属性,所以python不能做到真正访问限制,私有属性是最高级别的封装,只能在当前类或对象中访问。
class A:
def __init__(self,name,age):
self.__name = name
self.__age = age
def printf(self):
print(self.__name,'--->',self.__age)
a1 = A("许嵩",18)
a2 = A("薛之谦",32)
a1.printf()
a2.printf()
a1.__age = 30#进行修改 但是这样进行修改 并没有直接改到内部的属性(看下一段代码)
#print(a1.age) 这样就会报错
print(a1.__age)
运行结果如下:
许嵩 ---> 18
薛之谦 ---> 32
30#并未修改到内部的属性,只是一个变量而已
class A:
def __init__(self,name):
self.__name = name
a = A("许嵩")
#Python中用加两个下划线表示私有,但不是真正的私有,可以通过下面形式访问.
print(a._A__name)
class Employee:
def __init__(self,name,salary=0):
self.set_name(name)
self.set_salary(salary)
def printInfo(self):
print(self.__name,",",self.__salary)
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_salary(self):
return self.__salary
def set_salary(self,salary):
if(salary<0):
self.__salary = 0
else:
self.__salary = salary
e = Employee('wang',1000)
e.__salary = 88000#这样是无法修改的
e.__name = "li"
e.printInfo()
e.set_salary(8000)
e.printInfo()
e.set_salary(-200)
e.printInfo()
运行结果如下:
wang , 1000
wang , 8000
wang , 0
@构造函数里的self.salary = salary实际上触发了函数调用set_salary()
@函数property构造并返回一个property对象
@property(fget=None,fset=None,fdel=None,doc=None)其中fget,fset,fdel,分别用来获得修改和删除一个数据属性,doc是一个文档,这些函数参数都是可选的,可以不带任何参数调用这个函数。
class Employee:
def __init__(self,name,salary=0):
self.set_name(name)
self.salary = salary
def printInfo(self):
print(self.__name,",",self.__salary)
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_salary(self):
return self.__salary
def set_salary(self,salary):
if(salary<0):
self.__salary = 0
else:
self.__salary = salary
salary = property(get_salary,set_salary)
e = Employee('wang',1000)
e.salary = 8000#调用set_salary方法
print(e.salary)#调用get_salary方法
e.printInfo()
e.salary = -200#同理
e.printInfo()
运行结果如下:
8000
wang , 8000
wang , 0
getters()和setters()是面向对象为了达到数据封装效果的常用方法,可以用这些方法作为接口去访问私有数据
@property比直接调用函数更加方便,对于某个数据属性的getter()方法,在前面添加@property,就可以直接查询类对象的这个方法对应的数据属性,而对于某个数据属性的setter()方法,在前面添加@xxx.setter()方法就可以直接修改类对象的这个方法对应的数据属性
class Employee:
def __init__(self,name,salary=0):
self.__name = name
self.__salary = salary
def printInfo(self):
print(self.__name,"->>>",self.__salary)
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
@property
def salary(self):
return self.__salary
@salary.setter
def salary(self,salary):
self.__salary = salary
e = Employee('wang',1000)
e.printInfo()
e.salary = 8000#可以直接修改了
e.name = "许嵩"
e.printInfo()
运行结果如下:
wang ->>> 1000
许嵩 ->>> 8000
受保护的封装(protected)是将对象成员进行一定级别的封装,然后在类中或者子类中都可以进行访问,但是在外部不可以,在成员名称前面加一个下划线即可封装。
七、运算符重载
'__ add__'是加法运算符的重载
class Vector2():
def __init__(self,x,y):#两个参数
self.__x = x
self.__y = y
def __add__(self,other_v):#加法运算符重载
#一个的x值加另一个的x值,一个的y值加另一个的y值
return Vector2(self.__x+other_v.__x,self.__y+other_v.__y)
def print(self):#就是打印出来值
print(self.__x,self.__y)
v = Vector2(2.5,3.5)
v2 = Vector2(10.5,30.5)
v3 = v + v2
v3.print()
常见运算符对应的运算符方法
+:__add__(self,other)
-:__sub__(self,other)
*:__mul__(self,other)
/:__truediv__(self,other)
//:__floordiv__(self,other)
%:__mod__(self,other)
**:__pow__(self,other)
<:__lt__(self,other)
<=:__le__(self,other)
==:__eq__(self,other)
!=:__ne__(self,other)
>:__gt__(self,other)
>=:_ge__(self,other)
&:__and__(self,other)#与
|:__or__(self,other)#或
^:__xor__(self,other)#异或
~:__invert__(self)#位取反
<<:__lshift__(self,other)#左移
>>:__rshift__(self,other)#右移
[]:__getitem__(self,index)#下标运算符
in:__contains__(self,val)#包含于
八:派生类
Python允许在一个已经存在的类的基础上再定义一个新的类,新的类会继承已有类的属性,但也会给自己添加一些新的属性,这个新的类叫派生类、子类 ,而原有的类叫父类 、基类 、超类。从一个基类(Base)定义一个新的派生类(Derived):即要在圆括号内写入基类名就可以了。
子类一旦继承父类,则可以使用父类中除私有成员外的所有内容,子类继承父类后并没有将父类的成员完全赋值到子类中,而是通过引用关系进行访问调用
子类如果想扩展父类的方法,可以在使用新方法的同时访问父类成员进行代码重用。可以使用父类名.父类成员的格式来调用父类成员,也可以使用super().父类成员的格式来调用
class Employee:
def __init(self,name,salary):
self.name = name
self.salary = salary
class Manger(Employee):#派生类
def __init__(self,name,salary,level,employees):
Employee.__init__(name,salary)#基类构造函数对基类初始化
self.level = level
self.name = name
self.employees = employees
self.salary = salary
def printf(self):
print(self.name,'--->',self.salary,'--->',self.level)
e = Manger("许嵩",40000,"高级","我")
e.printf()
运行结果如下:
许嵩 ---> 40000 ---> 高级
派生类中可以通过super()调用父类的方法
class Employee:
def __init(self,name,salary):
self.name = name
self.salary = salary
def printf1(self):
print(self.name)
class Manger(Employee):#派生类
def __init__(self,name,salary,level,employees):
Employee.__init__(name,salary)#基类构造函数对基类初始化 也可以用super()
self.level = level
self.name = name
self.employees = employees
self.salary = salary
def printf(self):
super().printf1()#这里调用了父类的方法
print(self.name,'--->',self.salary,'--->',self.level)
e = Manger("许嵩",40000,"高级","我")
e.printf()
#运行结果如下
构造函数是一类特殊的函数,在类进行实例化以前进行调用,如果定义了构造函数,则实例化时使用构造函数,不查找父类构造函数,如果没定义,则自动查找父类的构造函数,如果子类没定义,父类的构造函数带参数,则构造对象应该按父类参数构造
@覆盖:子类通过定义和父类同名的属性可以覆盖父类的数据属性和方法
@多继承:一个类可以继承多个类的特性(若有重复,一般是继承第一个类的重复属性)
@菱形继承/钻石继承:多个子类继承自同一个父类,这些子类被同一个类所继承,于是形成一个菱形图谱
此处和上文某些地方引用大拿老师补充的部分
【MRO】:(https://www.cnblogs.com/whatisfantasy/p/6046991.html)
构造函数一定要有,如果没有,则按照MRO顺序自动向上查找
九、多态
多态就是同一个对象在不同情况下有不同的状态出现
【多态和多态性】(https://www.cnblogs.com/luchuangao/p/6739557.html)
Minxin设计模式
使用多继承语法来实现Mixin,可以扩充功能
【Mixin概念】(https:///www.zhihu.com/question/20778853)
【Mixin模式】(https://www.cnblogs.com/xybaby/p/6484262.html)
还有【MRO和Mixin】【Mixin MRO】【MRO】等文章(截图没截到呜呜呜)
十、类方法与静态方法
类的静态方法和类方法,类中还可以定义相关的的类方法和静态方法,这两种方法与类的具体实例无关,因此既可以通过类名也可以通过实例名调用这两种方法。这两种方法分别用装饰器@staticmethod和@classmethod开头
将这种和某个类有关的函数放在类的内部称为类的静态方法有如下三个优点
1.告诉其他人这个方法是和这个类有关系的
2.不需要通过模块名.函数名 或 单独导入函数名的方式类使用这个函数
3.使代码组织清晰,便于维护,管理,跟踪
#类方法与实例方法
class A:
def __init__(self,name):
self.name = name
#类方法第一个参数命名为cls
@classmethod
def play(cls):
print(cls)
print("Playing...")
#静态方法,不需要第一个参数表示自身或类
@staticmethod
def say():
print("Saying...")
a = A("许嵩")
a.play()
a.say()
A.play()
A.say()
'''
<class '__main__.A'>
Playing...
Saying...
<class '__main__.A'>
Playing...
Saying...
'''
类的静态方法处理和类相关的数据,但是不能访问或修改类本身的状态(数据),而类的类方法通常用于访问或修改类本身的状态(数据)
不使用类方法
class Date:
default_date = [2018,1,1]
def __init__(self,year=default_date[0],month=default_date[1],day=default_date[2]):
self.year = year
self.month = month
self.day = day
def Print(self):
print(self.year,'.',self.month,'.',self.day)
d = Date()
d.Print()
s_date = '2020.10.21'
year,month,day = map(int,s_date.split('.'))
d1 = Date(year,month,day)
d1.Print()
'''
2018 . 1 . 1
2020 . 10 . 21
'''
使用类方法
class Date:
default_date = [2018,1,1]
def __init__(self,year=default_date[0],month=default_date[1],day=default_date[2]):
self.year = year
self.month = month
self.day = day
def Print(self):
print(self.year,'.',self.month,'.',self.day)
@classmethod
def create_date(cls,string):
#第一个参数是cls,表示这个类
year,month,day = map(int,string.split("."))
return cls(year,month,day)
#类的类方法第一个参数必须是cls,用于指向调用这个类方法的类,可以通过类名或类的实例调用这个类方法,
s = '2020.10.21'
d = Date.create_date(s)
d.Print()
'''
运行结果如下:
2020.10.21
'''
十一、类相关函数
'''
#类的相关函数
-issubclass:检查一个类是否是另一个类的子类
-isinstance:检测一个对象是否是另一个类的实例
-hasattr:检测一个对象是否由成员xxx组成
-getattr:得到属性
-setattr:设置属性
-delattr:删除属性
-dir:获取对象的成员列表
'''
class A:
def __init__(self,name):
self.name = name
a = A("name")
print(hasattr(a,"name"))#查看a的属性有没有name
print(getattr(a, "name"))#得到a中属性为name的值
setattr(a,"name","name1")#将a中name设置为name1
print(getattr(a,"name"))
delattr(a,"name")#删除这个属性
#print(getattr(a,"name"))会报错
print(dir(a))#获取对象的成员列表
类中自动触发的函数
#类中特定的时刻自动触发的方法
'''
__init__:构造函数
__new__:对象实例化方法,此函数较特殊,一般不使用
__call__:对象当函数使用的时候触发
__str__:当对象被当做字符串使用的时候调用
__repr__:返回字符串,百度呜呜呜
__getattr__:访问一个不存在的属性时触发
__setattr__:对成员属性进行设置的时候触发
'''
class A:
def __init__(self,name='许嵩'):
self.name = name
print('初始化')
def __call__(self):
print('我被调用了')
def __str__(self):
return '一个字符串'
def __getattr__(self,name1):
print(name1)
def __setattr__(self,name,value):
#self.name = value会导致死循环
#这里统一调用父类函数
super().__setattr__(name,value)
def Print(self):
print(self.name)
a = A()
a()
print(a)
print(a.but)
a.name = "周杰伦"
a.Print()
'''
运行结果如下:
初始化
我被调用了
一个字符串
but
None
周杰伦
'''
十二、抽象类
--抽象类:包含抽象方法的类叫抽象类,通常称为ABC类
--抽象方法:没有具体实现内容的方法
--抽象方法的主要意义的规范子类的行为和接口
--抽象类的实现需要借助abc模块
--先要导入模块import abc
--抽象类可以包含抽象方法,也可以包含具体方法
--抽象类中可以有方法也可以有属性
--抽象类不允许直接实例化
--必须继承才能使用,且继承的子类必须实现所有继承来的抽象方法
--假定子类没有实现所有的抽象方法,则子类也不能实例化
import abc
#定义一个类并指定当前类的元类
class Human(metaclass=abc.ABCMeta):
#定义一个抽象方法
@abc.abstractmethod
def smoking(self):
pass
#定义类抽象方法
@abc.abstractclassmethod
def drink():
pass
#定义静态抽象方法
@abc.abstractstaticmethod
def play():
pass
十三、自定义类
--类其实是一个类定义和各种方法的自由组合
--可以定义类和函数,然后自己通过类直接赋值
--借助于MethodType实现
--也可以借助于type实现
--也可以利用元类实现 MetaClass
--元类的类,被用来创造别的类
from types import MethodType
class A:
pass
def say(self):
print('Saying...')
#组合
a = A()
a.say = MethodType(say,A)
a.say()
#用type来创建一个类
B = type("名字啦",(object,),{
"class_say":say})
b = B()
b.class_say()
#元类:写法固定,必须继承type,命名一般以MetaClass结尾
class TulingMetaClass(type):
def __new__(cls,name,bases,attrs):
print("这是元类")
attrs['id'] = '00000'
attrs['addr'] = '北京'
return type.__new__(cls,name,bases,attrs)
class Teacher(object,metaclass=TulingMetaClass):
pass
t = Teacher()
print(t.id)
十、一些小补充
class A:
name = "许嵩"
def __init__(self,name):
self.name = name
def printinfo(self):
print(self.name)
class B:
name = "周杰伦"
def __init__(self,name):
self.name = name
a = A("汪苏泷")
a.printinfo()
A.printinfo(A)#需要一个参数
A.printinfo(B)#因为B和A具有相同的参数,所以这里B也可以传进去
运行结果如下:
汪苏泷
许嵩
周杰伦
class A:
def __init__(self):
pass
def Print():#如果这里没有self参数,就只能用类名调用方法
print("这是一个空")
a = A()
A.Print()