class是什么
class是用来描述具有相同属性和方法的对象的结合。他定义该集合中每个对象所共有的属性和方法。对象是类的实例。
其中,类变量是在整个实例化的对象中是公用的。类变量定义子啊类中且在函数体之外。类变量通常不作为实例变量使用。
class的定义与实例的创建
假如我们想定义一个圆,即
class circle(object):###创建这个类别叫circle, circle为类名
pass####这里可以添加属性和方法
如果想调用circle这个类,可以通过类似于函数的方法进行调用
circle1=circle()
1.实例属性
这个circle不能光有一个名字啊,我们知道圆至少需要半径有关的属性,于是我们给圆添加半径
circle.r=1
print(circle1.r)
在定义这个类的时候,我们还可以添加一个特殊的__init__()方法,当创建实例时,__init__()方法被自动调用为创建的实例增加实例属性
我们在此为每个实例都统一加上了我们需要的属性
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 8 21:24:07 2024
@author: DELL
"""
class circle:
pi=3.1415###类属性(类变量,自己定义的,像__name__这种是默认定义的类变量),类属性是放在外面的哦,放在__init__里面的是实例属性,不要搞混了
def __init__(self,r): ####这个就是实例函数,实例函数与普通函数唯一的区别就是多了一个叫self的变量,这个self=circle这个类,相当于在调用里面的函数的时候需要知道是这个类,但是这个从操作在python里面已经被简化了
####__init__函数是用来做初始化的
######实例函数里面定义的属性就是实例属性
self.r=r#####圆的半径是什么,加上了selg之后,这个变量就会在这后面的函数中都能被调用了,所以__init__相当于一个初始化环境
self.d=2*r#####圆的直径是什么
self.S=self.pi*r**2#####圆的面积是什么
self.C=self.d*self.pi
self.__notallow=20 ####定义了一个私密属性,加一个下划线是代表私有,但是不影响外部调用,加上两个下划线代表完全私有,这个函数在外部就无法调用了
def get_S(self):#######通过定义函数也可以得到圆的面积(自己定义的实例函数)
return 2*self.pi*self.r**2
def __get_notallow(self): #####定义了一个私密函数
return 20
circle1=circle(2)#########这个就是创建的对象
print('这是圆的半径',circle1.r)
print('这是圆的直径',circle1.d)
print('这是pi',circle1.pi)
print('这是圆的面积',circle1.S)
print('这是也是圆的面积',circle1.get_S())
def main():
print(circle)
circle1=circle(1)###其实这个变量存放的就是他的一个储存器
print(hex(id(circle1)))#####输出他的内存地址
circle1=circle(1)###其实这个变量存放的就是他的一个储存器的故乡你
print(hex(id(circle1)))#####可以看到,重新赋值之后变成了一个新的内存地址
print(isinstance(circle1,circle))####判断这个对象是不是一个实例,对比这个对象是不是这个类的
print(isinstance(circle1,circle))####类也是一个对象,他是type的对象
###类本身就是一个对象,类本身这个变量的属性就是类变量,用这个类造出来的新变量,这个变量的属性叫做实例变量
####所有类的对象都共享同一个类变量
print(circle.__name__)####__name__是一个默认定义的类变量
print(circle.pi)####提取出类变量
print(getattr(circle,"pi"))#####这个方法也可以提取处类变量
####但是getattr有一个很强的用处,比如你找的这个属性在这个类里面不存在,这个函数就会返回第三个参数,这是python作为一个动态语言的体现
print(getattr(circle,'unknown','a new attribute'))##
####修改类属性
circle.pi=3.14159
####或者可以
setattr(circle,"pi",3.1415)####和前面的getattr一样,这也是一个可以动态修改的方法,比如
setattr(circle,"unknown",'10')#####这就会给之前的类多附加他之前没有的属性
####添加类属性
circle.newattribute=100000#####这和上面setattr的方法几乎如出一辙,但是一般不建议使用
print(circle.newattribute)
# ####删除类属性,因为把变量删除了我就没法作示范了,所以把这行给注释掉了
# del circle.pi ###老方法了,或者一下方法
# delattr(circle,'pi')
#########类变量的存储
print(circle.__dict__)###类变量都存储在这个叫做__dict__的字典里面了
######对实例函数的调用
# 1.先创建一个物理对象
# 2.在通过调用__init__来初始化这个object,给object一个初始环境
circle1=circle(2)#####这个其实就是对__init__方法的调用
circle1.get_S()####实例函数是对该类的对象掉调用的,不是对该类自己调用的
####比如我给circle1人为定义了一个属性,这个属性能在别的调用的对象出现吗?
circle1.newattr=1
circle2=circle(2)
# print(circle2.newattr)####答案是不行,会报错
###########私有属性与函数
###为什么要定义私的函数
#为了面向对象的封装,不希望把我们定义的封装的属性暴力给外部使用者(把用户可能会犯错的东西封装)
#原则就是给这个对象加上一个或者两个下划线,就比如 _name
#
if __name__=='__main__':
main()###
我们可能会疑惑,这个self到底是个什么变量么,其实self就是circle这个类它自己,调用self就是调用circle自己了啦
类方法
使用装饰器@classmethod调用当前类对象来传递类的属性和方法
class circle:
@classmethod
def get_instance(cls):
return cls
print(circle.get_instance().__name__)
###输出的结果就是circle类的名字,circle啦
###可以举一反三,这个方法能把这个类里面的别的变量和方法都调用出来
静态方法
class circle:
r=2
@staticmethod
def f():######可以看出来,定义这个函数时不需要像其他类里面的函数要加self
print(circle.r)#####因此这个函数在调用这个类里面的其他变量时间要加上这个类作为前缀
常用的特殊方法
一共有以下几种方法
__str__
__repr__
__eq__
__hash__
__bool__
__del__
1.__str__
用于返回一个描述对象本身的字符串
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __str__(self):
return f'{self.year}-{self.mon}-{self.day}'
my_date=MyDate(2024,1,1)
print(str(my_date))###一般str的作用是把其他类型的数据转化为字符串,而当前面有类对象被调用的时候,它的作用就改变为输出这个类里面__str__函数的东西了
####因此输出的结果为 2024-1-1
2.__repr__
为了给开发者输出想要看到的调试信息
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __repr__(self):
return f'MyDate: {self.year}-{self.mon}-{self.day}'
my_date=MyDate(2024,1,1)
print(repr(my_date))###为开发者使用的,可以输出一些调试信息(但感觉和__str__用法差不多)
3.__eq__
用于实现对比两个对象是否一样(这个“一样”的逻辑是你自己定义的)
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __eq__(self,other):
if not isinstance(other,MyDate)####如果被对比的对象不是MyDate这个类,直接就认为不等
return False
return self.year == other.year\
self.mon == other.mon ###我们这里定义,如果这两个类的实例拥有同样的年和月,就认为相同(这个逻辑可以自己随便魔改)
my_date1=MyDate(2024,1,1)
my_date2=MyDate(2024,1,1)
print(my_date1 == my_date2)###结果需要用 A == B 来调用
4.__hash__
用处是为你的对象生成一个特定的hash值,用来存放到set或者字典里面
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __hash__(self):
return hash(self.year + self.mon*101 + self.day*101)
my_date1=MyDate(2024,1,1)
a_set=set()
a_set.add(my_date1)#当对象被放进set里面,会直接通过__hash__方法给定的hash值来存放
5.__bool__
用于在对象被bool函数求解时返回一个布尔值。若这个类没有实现这个__bool__方法,那bool函数会去调用类里面的__len__方法求解布尔值
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __bool__(self):
if year>=2020:
return True
else:
return False
my_date1=MyDate(2024,1,1)
print(bool(my_date1))
6.__del__
在对象被删除时调用
class MyDate:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __del__(self):
print("do not delete me!")
my_date1=MyDate(2024,1,1)
del my_date1
property类
为了防止实例变量被外部错误修改,就需要编写setter和getter方法,然而其中会引起兼容性的问题(例如之前能直接引用的变量突然不能直接引用了),这个时候就需要引入property类
举例
class Student:
def __init__(self,name: str,age: int):
self.name = name
self.age = age
student=Student("Jack",19)
student.age=-2 ########发现这里的对年龄的赋值 肯定是有问题的,应该找个方法提醒别人不要这样
站在面向对象的设计角度来说,我们应该告诉使用这不应该这么赋值,那怎么办呢
1.先把变量变为私有
2.想改变量可以,但是你要调用函数去改,不要直接改变量(这个时候就可以在函数里面来判断赋的值是不是小于零了)
class Student:
def __init__(self,name: str,age: int):
self.name = name
self.__age = age
def set_age(self, age: int):
if age<0 or age>200:
raise Exception(f"age {age} is not valid")
else:
self.__age=age
def get_age(self):
return self.age
student=Student("Jack",3)
student.set_age(3) ######通过set方法对结果重新赋值
print(student.get_age()) #####通过get方法提取属性的值
但是又引出来了别的问题,别人student.age=14用的好好的,也没犯啥错,你突然把程序这么一改就没法直接赋值了,这让没犯错的人很为难。这个时候就要引入property类啦
class Student:
def __init__(self,name,age):
self.name = name
self.__age=age
def set_age(self, age):
if age<0 or age>200:
raise Exception(f"age {age} is not valid")
else:
self.__age=age
def get_age(self):
return self.__age
age = property(fget=get_age, fset=set_age)
student=Student("Jack",3)
student.age=5
print(student.age)
1.@property类装饰器
像上面操作一样手打property太麻烦,pythontigongle@property类装饰器让写代码变轻松了
class Student:
def __init__(self,name,age):
self.name = name
self.__age=age
@property #######加上property装饰器,对调用的人来说,age就像一个属性,而不是函数了
def age(self):
return self.__age
@age.setter #####这样给age又加上了赋值的属性
def age(self, age):
if age<0 or age>200:
raise Exception(f"age {age} is not valid")
else:
self.__age=age
student=Student("Jack",3)
student.set_age(3) ######通过set方法对结果重新赋值
print(student.get_age()) #####通过get方法提取属性的值
2.只读property
比如我们定义一个叫正方形的类
class Square:
def __init__(self,width):
self.__width=width
@property
def area(self):
return self.__width**2
square = Square(5)
print(square.area)
print(square.area)
这个类里面有宽度的属性和计算面积的函数(虽然被property装饰之后,外部的调用者认为这也是一个属性)
这个时候,如果我们要多次调用面积的话,它每次都得重新计算一次面积(因为这个面积是函数,不是已经赋值好了的属性),这样就会很占用计算时间。我们可以给它一个位置储存已经计算好的面积结果,即
class Square:
def __init__(self,width):
self.__width=width
self.__area=None####先定义一个空的变量,过会计算面积之后赋值给它
@property
def area(self):
if self.__area is None:###第一次计算结束把值赋给self.__area,第二次就能直接调用area了,无需计算
self.__area=self.__width**2
square = Square(5)
print(square.area)
print(square.area)
又来了个问题,万一有人第二次计算的时候把width的值给改了,那area的结果不就和width对不上了吗,所以widht也要一起改
class Square:
def __init__(self,width):
self.__width=width
self.__area=None####先定义一个空的变量,过会计算面积之后赋值给它
@property
def width(self):
return self.__width
@width.setter
def width(self,width):
self.__width=width
self.__area=None####改了width的值之后,area面积清空重新计算
@property
def area(self):
if self.__area is None:###第一次计算结束把值赋给self.__area,第二次就能直接调用area了,无需计算
self.__area=self.__width**2
square = Square(5)
print(square.area)
print(square.area)
3.删除property
定义一个删除时间会出现的property,但是对于大部分开发拉说并不需要(动态语言的时候可能需要)
class Square:
def __init__(self,width):
self.__width=width
self.__area=None####先定义一个空的变量,过会计算面积之后赋值给它
@property
def width(self):
return self.__width
@width.setter
def width(self,width):
self.__width=width
self.__area=None####改了width的值之后,area面积清空重新计算
@property
def area(self):
if self.__area is None:###第一次计算结束把值赋给self.__area,第二次就能直接调用area了,无需计算
self.__area=self.__width**2
@area.delete
def area(self):###这里就提供一个删除的属性
self.aera=None
square = Square(5)
print(square.area)
print(square.area)
def square.area ####这个时候删除area属性,会发生什么呢?
#####结果只是把area属性变成了一个空的值。并不是真的删掉了,这是delete方法所提供
类的继承
一个类拥有特定的特征之后,如果你还希望这个类还拥有你自己扩展的东西,那就需要继承父类所拥有的能力,同时也拥有自己的能力,这就叫类的继承
类继承的定义
class Person:
def _init__(self,name):
self.name=name
class Student(Person):###这个Student就是Person的子类
pass
student=Student('name')###很明显哈,我们似乎没给Student类添加name属性,那name真的能赋值在student里面吗?
print(student.name)####结果是打出来了,原因是Student继承了Person类的属性
同样,子类也可以拥有自己的属性。这个属性可以自己定义
class Person:
def __init__(self):
self.name = 'JACK'
class Student(Person):
def __init__(self):
self.school='ABC'
student_A=Student()
#
print(student_A.__dict__)###结果可以看到student_A没有name属性了,按照之前的结果来说应该继承了父类的属性了啊
因为__init__是构造方法,只有构造方法被执行的时候里面的属性才会被创建。然而当Studen这个子类自己有__init__方法的时候,父类的方法就没执行了,因此self.name也没被赋值
如果子类不写__Init__,python会自动调用父类的方法,而子类如果写了就不调用了。如果想执父类的方法,可以通过以下途径。
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
student_A=Student()
print(student_A.__dict__)####这可以看出来这个结果没有属性了。
isinstance()
用来判断是不是一个类的对象
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
student_A=Student()
print(isinstance(student_A,Student))####用来判断是不是一个类型的对象
print(isisntance(student_A,Person))####大家猜猜这两个是不是一个类型的对象
答案当然是啦,可以想一想,这个学生到底是不是个人呢(答案当然是啦)
issubclass()
用来判断一个类是不是另一个类的子类
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
student_A=Student()
print(issubclass(Person,Student))####用来判断Student是不是Person的子类
方法重写
父类定义了一个方法,但是子类在继承的时候觉得这个方法不好,想重新定义一下
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
student_A=Student()
student_A.say()
这个例子的结果输出的是
hello from person
但是你对这个结果不满意,想把这个输出的结果改成hello from student,我们就可以直接在子类下面重新定义一个一摸一样的函数,但是把内容给改了,把之前父类的函数给覆盖掉
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
def say(self):
print("hello from a student")
student_A=Student()
student_A.say()
但是原来父类里面的say函数是不会被修改的
如果我们有一个函数需要一个特定的类的对象,那么可以这么定义
class Person:
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
class Student(Person):
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
def say(self):
print("hello from a student")
def render(person: Student):
person.say()
我们使用上面这个代码里面的render函数时,特定了输入的person对象必须是Student类的结果。这个内容要么是Student类要么是他的子类造出的对象。
类属性的重写
和子类与父类的重写一样,如果子类下觉得父类的属性不好,也可以自己改
class Person:
color="yellow"
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
class Student(Person):
color="red"
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
def say(self):
print("hello from a student")
def render(person: Student):
person.say()
但是存在一个问题,就是如果父类里面有个方法是要调用这个color的,那么这个方法,存在这个方法里面,然而子类的这个变量不一样,我们调用子类时候,父类这个方法输出的结果是什么呢
class Person:
color="yellow"
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
def print_color(self):
print(self.color)
class Student(Person):
color="red"
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
def say(self):
print("hello from a student")
student_A=Student()
student.print_color()
而如果想让子类相同的函数继承父类相同函数的属性,就可以使用super的方法,例如
class Person:
color="yellow"
def __init__(self):
print("这个方法被执行了")
self.name = 'JACK'
def say(self):
print("hello from person")
def print_color(self):
print(self.color)
class Student(Person):
color="red"
def __init__(self):
super().__init__()#####代表执行父类的__init__方法
self.school='ABC'
def say(self):
super().say()
print("hello from a student")
student_A=Student()
student.print_color()
这个方法,一个类的对象,那么这个对象的范围,定义的类,就是这个函数的结果。
抽象类
更偏向于网络的设计,有的人经常写,有的人不怎么写。
抽象类不应该用来构造对象
这个类的名字就叫abc
from abc import ABC
class AbstractClassName(ABC):
pass
print(AbstractClassName.__dict__)
这个抽象类的第一个结果就是你不能通过它造对象,那么这个对象就是了