·类(Class)
笔记:
Python是一种面向对象(Object Oriented)的编程语言,类(Class)是Python的核心概念。在Python中,不管是列表、字符串、函数和类都是对象。
在面向对象编程(OOP)中,任何一个对象都要包含两个部分:属性(是什么)和方法(能做什么)
举个例子:小红有某些特征:眼睛大、皮肤白、身材好,这些特征可以比作类中属性;除了特征之外,小红会唱歌、跳舞、弹琴,这些能力可以比作类中的方法:
def 美女: 眼睛 = 大 皮肤 = 白 身材 = 好 唱歌() 跳舞() 弹琴()
将美女类实例化:
小红 = 美女() #小红是一个实例
针对”小红“这个实例,访问属性和使用方法:
小红.眼睛 #访问类中的属性 小红.眼睛 = 小 #修改类中的属性(眼睛由大变小) 小红.唱歌() #使用类中的方法
@类中的基本概念
创建一个Hello类:
class Hello(object): #在python3中,所有类都是类object的子类;定义类也可以写成 class Hello:或者 class Hello(): def __init__(self,name='world'): #初始化函数__init__ self.name = name #类中的属性 def get_name(self): #类中的函数称作方法。 return 'hello,{}'.format(self.name) h = Hello('jimmy') #实例化类(h是实例对象) print(h.name) #访问类的属性 print(h.get_name()) #访问类的方法 print('显示h是Hello的实例:',type(h)) print('显示h是Hello的实例:',h.__class__) print('显示h是Hello的实例,类Hello在内存中的地址为(at···):',h)
打印结果:
jimmy
hello,jimmy
显示h是Hello的实例: <class '__main__.Hello'>
显示h是Hello的实例: <class '__main__.Hello'>
显示h是Hello的实例,类Hello在内存中的地址为(at···): <__main__.Hello object at 0x000001A057CE6CC0>
初始化函数: __init__
初始化函数意味着实例化时要给name提供一个值,通过__init__执行初始化,让实例具有name的属性。由于类Hello中name参数设有默认值,当没有传入参数时,默认为world。
在类外,字符串 'Jimmy' 通过初始化函数__init__()中的 name 参数存入内存中,以Hello类型存在,组成一个对象,这个对象和变量h建立引用关系。这个过程也可以理解成这些数据附加到一个实例上,这样就能通过实例以访问属性的方式访问它。
类中的: Self
在类中, 如果某个地方(比如类中的方法)想要使用实例化传入的数据,那该怎么办?
在类中,将所有传入数据都赋值给一个变量,这个变量是在初始化函数__init__中的第一个参数self。它能接收实例化过程中传入的所有数据,这些数据是根据初始化函数后面的参数导入的。很显然,self就是一个实例,准确说法是应用实例,因为它所对应的就是具体的数据。接下来来验证:
class Hello(object): def __init__(self,name='world'): self.name = name
self.address = 'beijing' print(self) print(type(self)) def get_name(self): return 'hello,{}'.format(self.name) h = Hello()
打印结果:
<__main__.Hello object at 0x0000019FBBBB4E10>
<class '__main__.Hello'>
self实例和h实例一样都有属性。self.name是self的属性,值为name,当然也可以写成self.xxx = name也是可以的。self属性的值不一定要从外部传入,也可以在初始化函数中定义,比如self.address = 'beijing'
在get_name(self)方法中,self是默认的,当类实例化后调用此方法,不需要赋值(self能够让return中的语句访问初始函数中传入的值和属性)
类提供默认的行为,而实例可以看成一个工厂。所谓工厂就是可以用一个模板作出很多产品,而类就是模板,实例就是具体的产品。
@类属性和实例属性
类是一个对象,有属性;同样,类实例化后也是一个对象,也有它的属性。通过实例化得到的属性称为实例属性,一般情况下可以通过类来访问属性,也可以通过实例来访问属性。
class A: x = 1 a = A() a.x = 5 #更改实例属性 print('实例属性为:',a.x) print('类属性为:',A.x) 打印结果: 实例属性为: 5 类属性为: 1
实例属性更改了,但类属性没有跟着改变,这是因为类属性不会受实例属性所左右。
class A: x = 1 a = A() A.x = 10 #更改类属性 print('实例属性为:',a.x) print('类属性为:',A.x) 打印结果: 实例属性为: 10 类属性为: 10
类属性更改了,实例属性也跟着改变了,说明实例属性受类属性所左右。
以上所言是指类中的变量引用的是不可变的数据类型(整型、浮点型、字符串、元组),如果引用可变数据类型(字典、列表),数据将发生变化:
class B: x = [1,2,3,4] b = B() b.x.append(5) #实例属性增加一个值5 print('实例属性:',b.x) print('类属性:',B.x) 打印结果: 实例属性: [1, 2, 3, 4, 5] 类属性: [1, 2, 3, 4, 5]
当变量中引用的是可变数据类型时,类属性和实例属性都能够直接修改这个对象,从而影响另一方的值。
通过实例和类增加属性:
class B: x = [1,2,3,4] b = B() B.y = 'hello,world!' #增加一个类属性
print('实例属性:',b.y) 打印结果: 实例属性: hello,world!
结果表明:通过类增加一个类属性,实例可以访问到它。
如果是用实例增加一个实例属性呢?
class B: x = [1,2,3,4] b = B() b.y = 'hello,world!' #增加一个实例属性 print('实例属性:',b.y) print('类属性:',B.y) 打印结果: 实例属性: hello,world! Traceback (most recent call last): File "test1.py", line 8, in <module> print('类属性:',B.y) AttributeError: type object 'B' has no attribute 'y'
结果表明:增加的实例属性可以通过实例访问,如果通过类访问,结果显示类B中没有属性y。
@ 数据流转
在类的应用中,最广泛的是将类实例化,通过实例来执行各种操作。所以对此过程中的数据流转必须弄明白。
class Hello(object): def __init__(self,name='world'): self.name = name def get_name(self): return 'hello,{}'.format(self.name) def get_color(self,color): print('My love color is:',color) h = Hello('jimmy') print(h.get_name()) print(h.get_color('red')) 打印结果: hello,jimmy My love color is: red None #由于get_color()方法没有设置返回值,函数默认返回None
数据流转过程:
创建实例 h = Hello('jimmy'),h 实例和初始化函数中self对应(self接收实例传入的所有数据);'jimmy'是具体的数据,通过初始化函数中的name参数传给self.name(self是个实例,可以设置它的属性),此时name的值为jimmy。
类中除初始化函数以外的方法无论有没有定义参数,都要以self开头,这在类中是不能省略的,这表示所有方法都承接self实例对象。也就是实例的属性被带入到每个方法中,如get_name()中的return 'hello,{}'.format(self.name)语句,self.name调用前面已经确定的实例属性数据为jimmy,所以函数返回hello,jimmy。因此,通过self,可以实现数据在类的内部流转。
如果要把数据从类的内部传到外部,可以通过return语句,由于def get_color():方法没有设置函数返回值,函数默认返回None。
@ 类的命名空间和作用域
类命名空间:
定义类时,在class语句中的代码都在一个命名空间中执行,即类的命名空间。命名空间是从所定义的命名到对象的映射集合。不同的命名空间可以同时存在但彼此相互独立。
命名空间因对象不同,可以分为以下几种:
内置命名空间:
内置函数的命名空间都属于内置命名空间,因此,可以在程序中直接运行它们。比如print()函数id()函数等拿来就可以直接使用。
全局命名空间:
每个模块都有自己的全局命名空间,不同模块的全局命名空间彼此独立;不同模块相同名称的命名空间,因为模块不同而不会相互干扰。
本地命名空间:
模块中有类和函数 ,每个类或者函数所定义的命名空间就是本地命名空间。如果函数返回结果或者异常,本地命名空间也将结束。
各个命名空间的关系(图片来源于网络):
访问命名空间的时候,就按照从里到外的顺序:
def Duo(x,y): name = 'jimmy' return locals() print(Duo('he','llo')) 打印结果: {'x': 'he', 'y': 'llo', 'name': 'jimmy'}
通过locals()访问本地命名空间的方法,命名空间中的数据存储结构和字典一样;也可以通过globals()访问全局命名空间。
作用域:是指在python程序中可以直接访问到的命名空间。直接访问意味着访问命名空间中的命名时无需附加任何修饰语。程序按照命名空间从里到外的顺序,搜索相应命名空间能够访问到的作用域。
def out_t(): b = 10 def in_t(): c= 20 a = 30
假设我在def in_t():函数中,c对于我来说就是本地作用域,而a和b不是;如果我在def in_t():函数中新增加一个对象(b = 20),这和def out_t():中的b不会冲突,因为他们的作用域不同。
接下来我们证实:
def out_t(): a = 10 def in_t(): a = 20 print('in_t,a=',a) in_t() print('out_t,a=',a) a = 30 print(out_t()) print('a=',a) 打印结果: in_t,a= 20 out_t,a= 10 None a= 30
end~