目录
一,前言:
1,面向过程与面向对象的关系
2,对象的进化
二,类(class)与对象:
1,类的简述
2,类的定义
类属性,实例属性,实例方法总结:
方法与函数的区别:
实例方法调用的本质:
操作对象的函数及对象的属性:
三,类对象
1,类对象
2,类属性
3,类方法
4,静态方法
四,__del__和__call__魔法方法
1,__del__方法
2,__call__方法
五,python的方法没有重载
六,方法和属性的动态性
1,给类添加方法和属性
1,动态添加新的类方法和静态方法
2,给类添加新的属性
2,给对象动态的添加方法和属性
1,给对象添加新的方法
2,给对象添加新的属性
七,私有属性和私有方法
八,@property 装饰器
九,属性和方法命名总结
1,各种特殊命名形式
2,类编码风格
十,None对象
面向过程编程,注重于解决问题的步骤。例如把大象装进冰箱,一共分三步,打开冰箱门,把大象装进去,关上冰箱门即可。他首先先拿到数据,再思考如何去储存和运算这些数据。
python是面向对象编程的一门语言,具有面向对象的继承,多态,封装等特性。python一切都是对象,小到以数字,大到一个类,一个模块都是对象,可以这么理解:凡是能存到内存里的数据或是代码集合都可以称为一个对象。
面向对象编程是一种编程思维,他并不是和面向过程编程是分道扬礁的,而是相辅相成的,任何事情的处理最终都是面向过程来完成的。
面向对象编程常用于开发复杂的大型软件,而不是简单的打印一个九九乘法表。比如造一辆车,我们很难按照面向过程那样按步骤来造成车辆。但是,假如把车的发动机,轮胎,车架都给我们,那么造车也是一件很容易的事情,我们只需把他们组装起来,如何来组装这个问题又回到了面向过程编程来了。
面向对象编程使程序的扩展性更强、可读性更好,使编程可以像搭积木一样简单。面向对象编程将数据和操作数据相关的方法封装到对象中,组织代码和数据的方式更加接近人的思维,从而大大提高了编程的效率。
面向对象编程,应该对名词产生敏感。
面向过程是一种执行者的思维,强调如何取解决,即使很多事情很难解决。
面向对象是一种设计者的思维,强调如何规划设计,相当于司令来统筹各方兵力。
另外,python还支持函数式编程等特性。
生活中的数据各式各样。有单一的数据,一堆同类型的数据,一堆不同类型的数据,一堆不同类型的数据和处理数据的方法。他们可以看成是简单数据,数组,结构体,对象储存的类型。数组是对简单数据的封装;结构体是对各种不同类型数据的封装;对象可以看成是对各种数据和操作数据方法(函数)的封装。
类是对某类对象共性(相同的特点属性,相同的动作行为)的封装。
类(class)是对某类对象的大概描述。比如人,人都有头,手,腿,脚,会说话,会喝水,会吃饭;每个具体的人几乎都具有这些器官和动作。
从类创造出来的各个对象的差异是各种各样的。人作为一个类,社会中那么多人都是人这个类的对象,他们具有人这个类的基本属性,还具有每个人的特性。
在程序中也类似,同一个类创建出来的对象并不是千篇一律的,我们可以给每个具体的对象另外绑定很多的属性和动作,从而使他们和其他对象不一样。python支持这样的操作,因为python是一门动态性语言。
对象(object)也叫实例(instance),我们常见的叫法为:类的对象,类的实例。
定义类的语法格式如下:
class 类名():
类体
类体与循环体类似,不过类体里面是这个类的属性和方法。类名遵循标识符命名规范,推荐使用大驼峰的形式。
在python中,一个类中能被所有对象拿走共用的只有方法。因为不同的对象,他们的属性的值不同。比如,鸡类里面定义了鸡的颜色,不同的鸡颜色有黄的,有黑的,无法统一。所以在类里面不对属性进行统一,而是由不同的对象自己决定自己属性的值。这时需要加上self来对属性进行限制,格式如下:self.attrbute;换种写法:self.属性名,加上self的属性会被不同的对象自己赋值。不加self的属性只是占个位置而已,不能被类的对象所用。
加了self的属性也叫实例属性,它隶属于对象而不是类(这些属性是放在实例对象的内存空间里的,而不是类对象的内存空间里),类里边只是声明了属性名称而已,此时这些属性并没有值。
类里面的方法会被这个类的所有实例对象所共享,但是实际上这些方法也被self所限制。因为是共享,所以要记录是谁进行了使用,self关键字就起到了这个作用,self指代了使用者,也就是self指的是调用实例方法的对象,因为对象不同,self指向的内存地址也不同。。
class Student():
count = 10 # 定义了一个属性,没有加self,也没有放在方法里边,他是类属性,属于Student这个类
def __init__(self, name, age): # __init__()方法用来初始化对象本身,即对象对自己进行初始化。创建一个新对象时,会自动调用这个方法。全名initialize。
self.name = name
# 定义了self.name属性,加了self,说明这个属性对象可以拿去使用,然后对象自己赋自己想要的值就好了,赋值之后放在实例对象的内存空间里,不会像name和age一样弄丢。
self.age = age
# 定义了self.age属性,加了self,说明这个属性对象可以拿去使用,然后对象自己赋自己想要的值就好了
'''
虽然__init__方法中有name,和age两个形参变量,但是他们只是和普通函数的形参一样而已,用来接收参数。
当__init__方法执行完毕,name和age会随着__init__方法的销毁而消失。
'''
def showinfo(self): # 这个方法带有self,是实例方法,方法可以被所有对象共享,用self来识别是哪一个对象调用的。
gender = 'male' # 这个变量没加self,就是一普通局部变量,showinfo方法执行完毕,他就没了
print(gender)
print(self.age)
# 这里使用了某个实例对象的age属性,因为这个类的实例对象不止一个,要用self来确定到底是哪一个对象的age属性,
# 从而打印出这个对象age的值,
# 而不是错误地打印出其他对象的age 的值。
print(self.name)
# 创建一个实例对象
stu1 = Student(name='xiaoming', age=12)
# 创建了Student类的一个实例对象stu1,相当于执行Student类里面的__init__()方法。
# 需要传入两个属于stu1这个对象的特性属性,'xiaoming'给name,12给age
stu2 = Student('lihua', age=18)
# 同样创建Student类的一个实例对象stu2,这个对象name和age属性分别是'lihua'和18
# 因为不同的对象都会享有他们类的的方法,属性各自赋值,现在测试一下
stu1.showinfo()
# male
# 12
# xiaoming
stu2.showinfo()
# male
# 18
# lihua
#果然都能调用showinfo方法,打印了male,但是name和age各是各的。
类属性:写在类里面,且与各种方法同级别(缩进相同)的变量。
实例方法:第一个参数为self的写在类里面的方法,由类创建的各个对象都可以使用这些方法。self必须作为参数放在方法名后面的小括号里的第一个位置。
实例属性:在实例方法里定义的带有self的属性,他属于各个对象本身,与其他对象无关。这个对象的所有方法都可以访问和使用本对象的实例属性。实例属性一般定义在__init__方法里,也可以定义在其他方法里。
class Stu(object):
def __init__(self):
print('初始化成功,但是我并没有初始化任何东西')
def info_age(self): # 定义一个实例方法,他会添加一个age属性
self.age = 18 # 定义一个实例属性
def show_age(self):
print(self.age) # 可以访问其他方法里定义的实例属性,因为他和我同属于一个对象
s1 = Stu()
s1.info_age()
s1.show_age()
# 初始化成功,但是我并没有初始化任何东西
# 18
利用类创建对象时调用了__init__方法,进行对象自身的初始化。不需要初始化的话,可以不写__init__方法,因为解释器会自动生成一个__init__方法来执行。。
实际上调用__init__方法之前,也就是新创建的对象对自己进行初始化之前,得先创建出来对象。所以在__init__方法执行之前,先调用了__new__(cls)方法,这个方法的作用就是根据类(cls)创建一个新的对象。创建新对象之后,再执行这个新对象的__init__方法,对新对象进行初始化。__new__(cls)的实现过程被隐藏了,但是我们可以重写这个方法,在重写之前需要先调用实现过程。
class Stu(object):
def __new__(cls, *args, **kwargs):
print('no.1')
obj = super().__new__(cls)
return obj
def __init__(self): # 实例方法,要把slef作为参数放在小括号里的第一个位置
print('no.2')
s1 = Stu()
# no.1
# no.2
从结果可以看出,先执行了__new__方法,再执行了__inti__方法。一般没有必要重写__new__方法,但是当一个类继承了不可变类型,这个特性就很有用了。
1,都是用来完成一个功能的语句块,本质一样。
2,方法调用时,通过对象来调用。方法从属于特定实例对象,普通函数没有这个特点
3,直观上看,方法定义时需要传递self,函数不需要
前面已经说过,类里面的实例方法可以被类创建的任何对象使用。是因为这些实例方法是属于类对象的,也就是说实例方法包含在类对象的内存空间里。换句话说就是,这些实例方法是类对象的值(或者叫类的属性)。
class Stu(object):
def __init__(self):
print('初始化成功,但是我并没有初始化任何东西')
def info_age(self, age):
self.age = age # 定义一个实例属性
def show_age(self):
print(self.age) # 可以访问其他方法里定义的实例属性,因为他和我同属于一个对象
s1 = Stu()
s1.info_age(18)
s1.show_age()
# 下面的两行代码是上面两行代码的本质,python解释器其实执行的是下面两句代码
Stu.info_age(s1, 20) # 将年龄修改成了20
Stu.show_age(s1)
# 初始化成功,但是我并没有初始化任何东西
# 18
# 20
dir(obj)函数可以获得obj对象的属性;
obj.__dict__:对象的属性字典;
pass:空语句,用来占位
isinstance(对象,类型):用来判断对象是不是某种类型的实例对象;
print(dir(1))
print(isinstance(1, int))
什么是类对象,他和对象有什么区别。对象是类创建的实例,他与类对象没有关系。因为在python中,一切都是对象,当我们定义了一个类,这个类也是对象,叫做类对象。类对象也具有id,type,value三大特点。类的类型是“class type”。
class Stu():
def __init__(self):
print('init')
def speak(self):
print('running')
print(Stu, 'and id is:', id(Stu), 'type is:', type(Stu))
S_copy = Stu
print(S_copy, 'and id is:', id(S_copy), 'type is:', type(S_copy))
# and id is: 2150663355640 type is:
# and id is: 2150663355640 type is:
类属性也叫类变量,他属于类对象所有。
语法格式:
class 类名:
类变量名= 初始值
class Stu():
num = 100 # 这就是类变量,他与实例方法同级别,因为与实例方法缩进相同
def __init__(self):
print('init')
def speak(self):
print(Stu.num) # 使用类名.变量的形式来访问。
print(self.num) # 当对类变量只做访问不做改变的话,也可以使用self.变量名来访问。类创建的对象是否能够使用类变量呢?
s1 = Stu()
print(s1.num) # 可以看出类创建的对象也拥有类变量的使用权,但是没有修改权,一旦产生同名变量,则会隐藏类变量
Stu.speak(s1)
# init
# 100
# 100
# 100
用语法糖@classmethod来声明。
语法格式如下:第一个cls不能少,也不要修改
@classmethod
def 类方法名(cls [,形参列表]) :
方法体
def test(): # 普通函数
print(666)
class Test():
def __init__(self):
self.test()
@classmethod # 定义类方法
def test(cls):
print(666)
def ceshi(self):
self.test() # 这里用self.类方法名也可以调用类方法
test()
Test.test()
t1 = Test()
# 666
# 666
# 666
要点如下:
1,@classmethod下方紧跟类方法,
2,第一个cls 必须有;cls 指的就是"类对象"本身,
3,调用类方法格式:类名.类方法名(参数列表)。参数列表中,不需要也不能给cls传值,
4,子类继承父类方法时,传入cls是子类对象,而非父类对象。也就是类方法被继承后属于子类对象。
5,类方法不能访问实例属性和实例方法,因为实例属性属于对象不属于类。而且类方法在类定义的时候就已经创建好了,假如用类方法去调用实例方法,这个时候连实例对象都没有的话肯定是不行的。
静态方法与普通函数没有本质区别,只是放的位置有差异。普通函数定义在模块级别。静态方法本质上就是:普通函数放在类里边了,也没有类方法的cls参数。
语法格式如下:
@staticmethod
def 静态方法名([形参列表]) :
方法体
def test(): # 普通函数
print(666)
class Test():
@staticmethod # 类似于普通函数放在类里边
def show():
print(666)
def test(self):
self.show() # 利用self.静态方法名也能调用
Test.show() # 通过类名.静态方法名来访问
t1 = Test()
t1.show()
# 666
# 666
又叫析构方法。顾名思义,是用来删除东西用的,那么删什么东西了?答案是销毁对象。
我们常用的del语句,本质上调用的是__del__方法。
我们知道一个对象可以被多个变量进行绑定,也就是一个对象可以被很多个对象引用。一个对象被引用阅读说明比较重要,假如一个对象没有任何变量去引用他,他就是占着茅坑不拉屎,会被python垃圾回收机制进行内存释放。
__del__属于实例方法。记住python一切都是对象。
import time
class Test():
def __del__(self):
print('销毁了')
t1 = Test()
t2 = t3 = t1
del t1
time.sleep(3)
print('删除了t1')
del t2
time.sleep(3)
print('删除了t2')
time.sleep(3)
print('删除了t3')
# 删除了t1
# 删除了t2
# 删除了t3
# 销毁了
t1,t2,t3都绑定了同一个对象。只有t1,t2,t3都被删除时,才会调用__del__方法进行垃圾回收。
具有__call__方法的对象叫做可调用对象。所谓可调用对象,是指像函数那样,加上个小括号()就可以执行,我们调用函数本质上就是调用函数对象的__call__方法。
def test():
print(666)
print(dir(test))
"""
[
'__annotations_', 看这里'__call__'看这里, '__class__', '__closure__', '__code__',
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__',
'__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'
]
test函数对象隐藏有个__call__方法。
"""
test.__call__() # 这样也能调用函数
# 666
函数对象有__call__方法,那么类创建的对象有无__call__方法呢?有。
class Test():
def __call__(self, *args, **kwargs):
print(666)
t1 = Test()
t1() # 666
# 由于t1的类里面定义了__call__方法,那么这个类创建的对象就是可调用对象。用对象名()即可调用。
重载是指在类里面写多个同名的方法,但是每个方法的参数不一样。python不支持重载。原因有:1,多个同名方法名只能去绑定一个方法对象,如果在类里面写多个重名的方法,那么这个方法名只会去绑定最后写的那个方法,因为代码从上往下执行。2,python用可变参数可以收集多个参数,且每个参数的类型多变,导致无法区分各个同名函数。
与重写的区别:
重写需要继承,在子类中重写父类的方法,增加一些额外功能。
class Test():
def __call__(self, *args, **kwargs):
print(666)
def __call__(self, *args, **kwargs):
print(888)
t1 = Test()
t1() # 888
# 定义了两个__call__方法,最后一个生效。
仅仅是类里面定义的方法是远远不够,类里面定义的方法只是某类对象的共性方法。每个对象都会有自己独一无二的属性或者方法。如何给他对象添加属于自己的方法和属性呢?python是一门动态性的语言,我们可以为对象动态地添加方法和属性。
class Test():
@classmethod
def func(cls):
print(666)
@staticmethod
def func1():
print(888)
@classmethod
def func_class(cls):
print('我是新增的类方法')
@staticmethod
def func_static():
print('我是新增的静态方法')
# 动态的给已有的类新增一个类方法和静态方法
Test.func_cls = func_class
Test.func_sta = func_static
t1 = Test()
t1.func_cls()
t1.func_sta()
# 我是新增的类方法
# 我是新增的静态方法
class Test():
@classmethod
def func(cls):
print(666)
@staticmethod
def func1():
print(888)
def show_info(self):
print(self.num) # 在实例方法中用self.类变量名来访问使用变量,但是不修改
Test.num = 100 # 动态的给已有的类添加属性num
t1 = Test()
print(t1.num)
print(Test.num)
# 100
# 100
这需要使用types.MethodType(func_name,obj_name)方法。第一个参数是需要绑定的方法的名称,第二个参数是将要绑定这个方法的对象名称。
import types # 导入types模块,用它的MethodType方法来给对象绑定新的方法
class Test():
def __init__(self):
self.age = 18
self.name = 'xiaoming'
t1 = Test()
def show_info(self): # 既然要与对象绑定,第一个self不能少,少了self就不叫实例方法了
print(self.age)
print(self.name)
t1.show_info = types.MethodType(show_info, t1) # 给对象t1绑定一个新方法show_info,用来打印信息
t1.show_info() # 调用新绑定的放阿飞
# 18
# xiaoming
这里很简单。语法如下:
对象名.属性名 = value
class Test():
def __init__(self):
self.info = 666
def show_info(self):
print(self.info)
print(self.num) # 进行打印num看看是否给对象添加上了,如果没有的话,就会报错。
t1 = Test() # 创建对象
t1.num = 100 # 给t1对象动态添加一个num属性
t1.show_info()
# 666
# 100
私有是实现封装的前提。
Python对于类的成员没有严格的访问控制限制,这与其他面向对象语言有区别。关于私有属性和私有方法,有如下要点:
1 ,通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。
2 ,类内部可以访问私有属性(方法)
3 ,类外部不能直接访问私有属性(方法)
4 ,类外部可以通过 _类名__私有属性(方法)名 ”访问私有属性(方法)
方法本质上也是属性!只不过是可以通过()执行而已。
由此可见所谓封装,就是把私有变量和私有方法封在类里边,变为类的私有,类外面无法使用。作为类的私有,只能在类里面进行数据访问和方法调用。那么想要在类外面对类里面的数据进行修改该怎么办?可以通过在来类外边调用一些不是类私有的方法(公开的方法)来进行修改。
class Test():
__num = 100 # 定义一个私有类属性
def __init__(self):
self.__age = 18 # 定义一个私有实例属性
self.name = 'xiaoming'
Test.__show_info(self) # 在类里边调用私有实例方法,因为私有实例方法属于类,要用类名.__方法名__()来调用
def __show_info(self): # 定义一个私有实例方法
print(self.__age) # 类里面的方法可以访问到私有的变量
print(Test.__num) # 类里面的任何地方都可以访问私有类变量
# 18
# 100
t1 = Test()
# t1.__show_info() # 由于私有方法,外界无法调用
# print(Test.__num) # 由于私有属性,外界无法访问
实际上,在外部也能调用私有属性和私有方法。使用对象名._类名.__属性名的形式来访问。
class Test():
__num = 100 # 定义一个私有类变量
def __init__(self):
self.__age = 18 # 定义一个私有实例属性
@classmethod
def __cls_func(cls): # 定义一个私有类方法
print(666)
print(dir(Test))
"""
[
'看这里_Test__cls_func', '_Test__num', 看这里'__class__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
_Test__cls_func, _Test__num都是Test类的属性(或者叫类的数据)。其中_Test__cls_func是一个可调用对象。
"""
print(Test._Test__num)
Test._Test__cls_func()
# 100
# 666
t1 = Test()
print(t1._Test__age)
# 18
print(dir(t1))
"""
[看这里'_Test__age'看这里, '_Test__cls_func', '_Test__num', '__class__',
'__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
"""
这些私有类属性和私有方法都是类的属性(或者叫类的数据)。
这些数据中包含我们定义在类里面的各种方法(类方法,静态方法,实例方法,而且不管私有不私有都属于类)。
另外这些数据中也包含各种定义在类里面的类变量(不管私有还是不私有)。
而且私有的类变量,类方法,静态方法,实例都是以“_类名__属性名”的形式储存为类的属性。
class Test():
@classmethod
def __func1(cls): # 私有类方法
print(1)
@staticmethod
def __func2(): # 私有静态方法
print(2)
__num = 100 # 私有类属性
def __init__(self):
self.__age = 18 # 私有实例属性
def __show_info(self): # 私有实例方法
pass
t1 = Test()
print(dir(t1))
print(dir(Test))
# ['_Test__age', '_Test__func1', '_Test__func2', '_Test__num', '_Test__show_info',……
# ['_Test__func1', '_Test__func2', '_Test__num', '_Test__show_info',……
从上面的代码可以发现,在所有可能的私有形式下:
1,私有实例属性属于对象所有
2,除了私有实例属性,其他都属于类所有。这再一次证明,对象从创建他的类里拿走的只能是属于类的东西,而实例属性不属于类,所以各个对象的属性值都不相同。
@property 可以将一个方法的调用方式变成“属性调用”。 @property 主要用于帮助我们处理属性的读操作、写操作,主要就是解决如何在类外部来修改类内部的私有属性这个问题。将一个方法的调用方式变成“属性调用”,是指一般方法都是用方法名()的形式来调用,现在用‘.’这个操作符来调用方法。‘.’操作符为取属性操作符,通过他可以获得对象的属性,实质上是__getattr__方法。
class Test():
__num = 100
def __init__(self):
self.info() # 初始化的时候先生成info信息
def info(self):
self.__age = 18
self.__name = 'xiaoming'
@property # @property修饰一个方法,把一开始需要调用这个方法才能得到return的值,变成了用“对象名.方法名”就能得到return的值
def change_age(self):
return self.__age
@change_age.setter # 用@方法名.setter()方法,把一开始需要调用这个方法才能给变量赋值,变成了用“对象名.方法名 = value”就能给变量赋值
def change_age(self, new_age):
self.__age = new_age
# 注意:使用@property来修饰进行更方便的获取私有变量的值或者给私有变量赋值时,他们需要修饰两个名字相同的方法。上面的例子中,两个同名方法为change_age
t1 = Test()
age = t1.change_age # 用“对象名.方法名”就能得到return的值
print(age)
# 18
t1.change_age = 20 # 用“对象名.方法名 = value”就能给变量赋值
age = t1.change_age
print(age)
# 20
1,_xxx :保护成员,不能用 from module import * 导入(也就是对其他模块是不可见的),只有类对象和子类对象能访问这些成员(因为继承)。
2,__xxx__ :系统定义的特殊成员,也被称为魔法方法,例如__init__,__del__
3,__xxx : 类中的私有成员,也就是私有属性,只有类对象自己能访问,子类对象也不能访问。(但是,在类外部可以通过 对象名. _类名__xxx 这种特殊方式访问。Python不存在严格意义的私有成员)。
方法和属性都遵循上面的规则。通过对方法名和属性名的形式就能知道这个方法或者属性是哪一种成员。
1,类名首字母大写,多个单词之间采用驼峰原则。
2 ,实例名、模块名采用小写,多个单词之间采用下划线隔开。
3 ,每个类,应紧跟“文档字符串”,说明这个类的作用。
可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类。
None,null,空,0他们之间有什么区别呢?
None和null差不多,但是python中没有null,取而代之的是None。None实质是None对象,他的类型是NoneType。在python中,一旦解释器开始运行,就会创建唯一一个None常量对象(唯一就是说,所有为None的变量,都是跟这个唯一的None对象绑定的)。None大概表示的就是,一个变量被定义出来肯定有用处,假如实在没有对象与这个变量绑定的话,那就只能与None对象绑定了。
空指的是:序列类型或者容器类型里面没有数据,例如空字符串,空数组,空列表
0指的是:数字0,偶尔还代表False值。
a = None
b = None
print(id(a))
print(id(b))
print(None, type(None), id(None))
# 140737282541584
# 140737282541584
# None 140737282541584