在设计之初,Python 就被设计成支持面向对象的编程语言,因此 Python 完全能以面向对象的方式编程。而且 Python 的面向对象比较简单,它不像其他面向对象语言提供了大量繁杂的面向对象特征,它致力于提供简单、够用的语法功能。
正因为如此,在 Python 中创建一个类和对象都很容易。Python 支持面向对象的三大特征:封装、继承和多态,子类继承父类同样可以继承到父类的变量和方法。
面向对象相关术语
在系统学习面向对象编程之前,初学者要了解有关面向对象的一些术语。当和其他人讨论代码的时候,或者尝试查找我们遇到的问题的解决方案时,知道正确的术语会很有帮助。
面向对象相关术语
在系统学习面向对象编程之前,初学者要了解有关面向对象的一些术语。当和其他人讨论代码的时候,或者尝试查找我们遇到的问题的解决方案时,知道正确的术语会很有帮助。
面向对象中,常用术语包括:
- 类:可以理解是一个模板,通过它可以创建出无数个具体实例。比如,前面编写的 tortoise 表示的只是乌龟这个物种,通过它可以创建出无数个实例来代表各种不同特征的乌龟(这一过程又称为类的实例化)。
- 对象:类并不能直接使用,通过类创建出的实例(又称对象)才能使用。这有点像汽车图纸和汽车的关系,图纸本身(类)并不能为人们使用,通过图纸创建出的一辆辆车(对象)才能使用。
- 属性:类中的所有变量称为属性。例如,tortoise 这个类中,bodyColor、footNum、weight、hasShell 都是这个类拥有的属性。
- 方法:类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个 self 参数(后续会做详细介绍)。例如,tortoise 类中,crawl()、eat()、sleep()、protect() 都是这个类所拥有的方法,类方法无法单独使用,只能和类的对象一起使用。
Python class:定义类
类仅仅充当图纸的作用,本身并不能直接拿来用,而只有根据图纸造出的实际物品(对象)才能直接使用。因此,Python 中使用类的顺序是:先创建(定义)类,然后再创建类的实例对象,通过实例对象实现特定的功能。本节来先学习如何创建一个类。
Python 中,创建一个类使用 class 关键字实现,其基本语法格式如下: class 类名: 零个到多个类属性... 零个到多个类方法... 注意,类中属性和方法所在的前后顺序没有任何影响,且各成员之间可以相互调用。
类名只要是一个合法的标识符即可,但这仅仅满足的是 Python 的语法要求:如果从程序的可读性方面来看,Python 的类名必须是由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符,例如类名为“TheFirstDemo”
Python 的类定义由类头(指 class 关键字和类名部分)和统一缩进的类体构成,在类体中最主要的两个成员就是属性和方法。如果不为类定义任何属性和方法,那么这个类就相当于一个空类,如果空类不需要其他可执行语句,则可使用 pass 语句作为占位符。例如,如下类定义是允许的:
[root@kube class]# cat demo.py #coding:utf-8 class Person: ''' 这是定义person 的一个类 ''' hair = 'black' def say(self,content): print(content) [root@kube class]#
Python __init__()类构造方法
在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。Python 类中,手动添加构造方法的语法格式如下:
def __init__(self,...): 代码块
注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义,教程后面还会详细介绍这些特殊的方法。
另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。
[root@kube class]# cat demo1.py #coding:utf-8 class Person: ''' 定义一个__init__方法 ''' def __init__(self): print('调用构造方法') test = Person() print(test) [root@kube class]# py demo1.py 调用构造方法 <__main__.Person object at 0x7fd1595ba290> [root@kube class]#
还可以自定义传递的参数
[root@kube class]# cat demo1.py #coding:utf-8 class Person: ''' 定义一个__init__方法 ''' def __init__(self,name,age): print('调用构造方法',name,age) test = Person('tom',18) print(test) [root@kube class]#
Python类对象的创建和使用
使用 class 语句只能创建一个类,而无法创建类的对象,因此要想使用已创建好的类,还需要手动创建类的对象,创建类对象的过程又称为类的实例化。
对已创建的类进行实例化,其语法格式如下:
类名(参数) 当创建类时,若没有显式创建 __init()__ 构造方法或者该构造方法中只有一个 self 参数,则创建类对象时的参数可以省略不写。
[root@kube class]# cat demo2.py #coding:utf-8 class Person: ''' 定义一个类 ''' #下面定义2 个类变量,类变量就是定义在方法之外的变量,这个类变量和__init__ 定义的形参没有关系 name = 'jojo' age = 20 #下面定义 __init__ 构造函数,并定义需要传递的形参 def __init__(self,name,age): # self.name和 self.age 是实例变量,就是定义在方法中的变量 self.name = name self.age = age #下面定义一个实例方法 def say(self,content): print(content) #将Person 对象赋值给P,那么p 就可以使用对象方法,这里就理解了 对象就相当于一个个工具,将对象赋值给某一变量,那么这个变量就能调用对象定义的功能,相当于一个装甲函数 #访问类变量使用对象名加变量名 print(Person.name,Person.age) #print(Person.say(('hello,word'))) # 直接调用类的实例方法报错, p = Person('wahaha',18) print(p.name) print(p.age) print(p.say('hi,baby')) [root@kube class]# py demo2.py jojo 20 waha 18 hi,baby None [root@kube class]#
Python类对象的使用
创建对象之后,接下来即可使用该对象了。Python 的对象大致有以下作用:
- 操作对象的实例变量,包括访问、修改实例变量的值、以及给对象添加或删除实例变量)。
- 调用对象的方法,包括调用对象的方法,已经给对象动态添加方法。
类对象访问变量或方法
使用已创建好的类对象访问类中实例变量的语法格式如下:
对象名.变量名
使用类对象调用类中方法的语法格式如下:
对象名.方法名(参数)
注意,对象名和变量名以及方法名之间用点 "." 连接。
给类对象动态添加变量
Python 支持为已创建好的对象动态增加实例变量,方法也很简单,只要为它的新变量赋值即可,比如说:
p.gender = ['男','女'] print(p.gender)
del p.gender #删除变量
给类对象动态添加方法
Python 也允许为对象动态增加方法。比如上面程序中在定义 Person 类时只定义了一个 say() 方法,但程序完全可以为 p 对象动态增加方法。
Python self用法详解
在学习如何定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。例如
_init__方法的第一参数永远是self,表示创建的类实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。(2)、有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器会自己把实例变量传进去:
[root@kube class]# cat demo3.py #coding:utf-8 class Dog: def __init__(self): print('__init__ 类 初始化') def jump(self): print('正在执行 jump 动作') a = Dog() print(a) print(a.jump()) [root@kube class]# py demo3.py __init__ 类 初始化 <__main__.Dog object at 0x7f3ac4aaf310> 正在执行 jump 动作 None [root@kube class]#
[root@kube class]# cat demo4.py class Dog: def __init__(self): print(self,"在调用构造方法") # 定义一个jump()方法 def jump(self): print(self,"正在执行jump方法") # 定义一个run()方法,run()方法需要借助jump()方法 def run(self): print(self,"正在执行run方法") # 使用self参数引用调用run()方法的对象 self.jump() dog1 = Dog() dog1.run() dog2 = Dog() dog2.run() [root@kube class]# py demo4.py <__main__.Dog object at 0x7f7ca38c8990> 在调用构造方法 <__main__.Dog object at 0x7f7ca38c8990> 正在执行run方法 <__main__.Dog object at 0x7f7ca38c8990> 正在执行jump方法 <__main__.Dog object at 0x7f7ca38c8a50> 在调用构造方法 <__main__.Dog object at 0x7f7ca38c8a50> 正在执行run方法 <__main__.Dog object at 0x7f7ca38c8a50> 正在执行jump方法 [root@kube class]#
#面代码中,jump() 和 run() 中的 self 代表该方法的调用者,即谁在调用该方法,那么 self 就代表谁
Python类变量和实例变量(类属性和实例属性)
我们知道,无论是在类中定义的属性还是方法,在类的外部,都无法直接调用它们,因此,我们完全可以把类看做是一个独立的作用域(称为类命名空间),则类属性其实就是定义在类命名空间内的变量(类方法其实就是定义的类命名空间中的函数)。
根据定义属性的位置不同,类属性又可细分为类属性(后续用类变量表示)和实例属性(后续用实例变量表示)。
类变量(类属性)
类变量指的是定义在类中,但在各个类方法外的变量。类变量的特点是:所有类的实例化对象都可以共享类变量的值,即类变量可以在所有实例化对象中作为公用资源。
注意,类变量推荐直接用类名访问,但也可以使用对象名访问。
[root@kube class]# cat demo5.py #coding:utf-8 class Address: detail = 'yinchuan' phone = '010-8848' def info(self): print(Address.detail) print(Address.phone) #创建类对象 a1 = Address() a1.info() Address.detail = 'shizuishan' #改变类变量的值会作用于该类所有的实例化对象。 a2 = Address() a2.info() [root@kube class]# py demo5.py yinchuan 010-8848 shizuishan 010-8848 [root@kube class]#
实例变量(实例属性)
实例变量指的是定义在类的方法中的属性,它的特点是:只作用于调用方法的对象。
注意,实例变量只能通过对象名访问,无法通过类名直接访问。
Python 允许通过对象访问类变量,但无法通过对象修改类变量的值。因为,通过对象修改类变量的值,不是在给“类变量赋值”,而是定义新的实例变量。
[root@kube class]# cat demo6.py #coding:utf-8 class Inv: #定义两个类变量 a = 100 b = 200 #定义实列方法 def jisuan(self,c,d): self.c = c #这两行是重新定义实列变量,即便c,d 变成 a,b 也和类变量没啥关系 self.d = d #创建Inv 对象,对象也称实列 iv = Inv() iv.jisuan(300,400) #输出实列变量 print(iv.c) print(iv.d) #输出类变量 print(Inv.a) print(Inv.b) Inv.a = '类变量a' #修改一个类变量,既不会影响实例变量的值,也不会影响其它对象的实例变量。 Inv.b = '类变量b' print('-------------------------------') print(iv.c) print(iv.d) #输出类变量 print(Inv.a) print(Inv.b) [root@kube class]#
Python实例方法、静态方法和类方法详解(包含区别和用法)
和类属性可细分为类属性和实例属性一样,类中的方法也可以有更细致的划分,具体可分为类方法、实例方法和静态方法,本节将详细介绍这 3 种类方法的特点和用法。
[root@kube class]# cat demo7.py #coding:utf-8 class test: def __init__(self,name = 'jojo',age = 20): self.name = name self.age = age def a(self,a): return a def b(self,b): return b c = test() print(c.a(100)) #对象调用实列方法 print(c.b(100)) print(c.__dict__) print(test.a(c,100000)) #类调用实列方法,需要用实例给self 传递参数才行 [root@kube class]# py demo7.py 100 100 {'name': 'jojo', 'age': 20} 100000 [root@kube class]#
[root@kube class]# cat demo7.py #coding:utf-8 class test: def __init__(self,name = 'jojo',age = 20): self.name = name self.age = age def a(self,a): return a def b(self,b): return b c = test() print(c.a(100)) #实例调用方法 print(c.b(100)) print(c.__dict__) print(test.a(c,100000)) #类调用方法需要实列传递参数 print(c.name) #实列调用方法 print(c.age) print(test.name) #类调用 init 方法变量报错,这个方法不属于类 print(test.age) [root@kube class]# py demo7.py 100 100 {'name': 'jojo', 'age': 20} 100000 jojo 20 Traceback (most recent call last): File "demo7.py", line 20, in <module> print(test.name) AttributeError: type object 'test' has no attribute 'name' [root@kube class]#
Python类方法
Python 类方法和实例方法相似,它最少也要包含一个参数,只不过,类方法中通常将其命名为 cls,且 Python 会自动将类本身绑定给 cls 参数(而不是类对象)。因此,在调用类方法时,无需显式为 cls 参数传参。
和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。
除此之外,和实例方法最大的不同在于,类方法需要使用@classmethod
进行修饰,例如:
[root@kube class]# cat demo8.py #coding:utf-8 class Bird: #@classmethod 的修饰方法是类方法 @classmethod def fly(cls): print('类方法fly:',cls) #将类本身传递给 cls 参数 Bird.fly() b = Bird() b.fly() [root@kube class]# py demo8.py 类方法fly: <class '__main__.Bird'> 类方法fly: <class '__main__.Bird'> [root@kube class]#
Python类静态方法
静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。
静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定,也正是因为如此,此方法中无法调用任何类和对象的属性和方法,静态方法其实和类的关系不大。
静态方法需要使用@staticmethod
修饰,例如:
[root@kube class]# cat demo9.py #coding:utf-8 class Bird: #staticmethod 修饰的方法是静态方法 @staticmethod def info(ppap): print('静态方法info:',ppap) Bird.info('类名') b = Bird() b.info('类对象') [root@kube class]# py demo9.py 静态方法info: 类名 静态方法info: 类对象 [root@kube class]#
Python类调用实例方法
但要提醒大家的是,Python 的类在很大程度上可看做是一个独立的空间(称为类命名空间),当程序在类体中定义变量、方法时,与前面介绍的定义变量、定义函数其实并没有太大的不同
[root@kube class]# py demo10.py 全局空间的foo方法 20 Bird空间的foo方法 200 [root@kube class]# cat demo10.py # 定义全局空间的foo函数 def foo (): print("全局空间的foo方法") # 全局空间的bar变量 bar = 20 class Bird: # 定义Bird空间的foo函数 def foo(): print("Bird空间的foo方法") # 定义Bird空间的bar变量 bar = 200 # 调用全局空间的函数和变量 foo() print(bar) # 调用Bird空间的函数和变量 Bird.foo() print(Bird.bar) [root@kube class]# py demo10.py 全局空间的foo方法 20 Bird空间的foo方法 200 [root@kube class]#
[root@kube class]# cat demo11.py class User: def walk (self): print(self, '正在慢慢地走') # 通过类调用实例方法 User.walk() #这一个事类直接调用walk 方法,没有传递参数,在执行时报错 [root@kube class]# py demo11.py Traceback (most recent call last): File "demo11.py", line 5, in <module> User.walk() TypeError: walk() missing 1 required positional argument: 'self' [root@kube class]#
#请看程序最后一行代码,调用 walk() 方法缺少传入的 self 参数,所以导致程序出错。这说明在使用类调用实例方法时,Python 不会自动为第一个参数绑定调用者。实际上也没法自动绑定,因此实例方法的调用者是类本身,而不是对象。
[root@kube class]# cat demo11.py class User: def walk (self): print(self, '正在慢慢地走') # 通过类调用实例方法 #User.walk() a = User() User.walk(a) #这个是将类本省当做self进行传递 [root@kube class]# py demo11.py <__main__.User object at 0x7f57994d0a10> 正在慢慢地走 [root@kube class]#
总结 很重要
Python 的类可以调用实例方法,但使用类调用实例方法时,Python 不会自动为方法的第一个参数 self 绑定参数值;程序必须显式地为第一个参数 self 传参,这种方式调用的方法被称为“未绑定方法”。
用类的实例对象访问的类成员方法称为绑定方法;用类名调用的类成员方法称为非绑定方法。
浅谈Python类命名空间
所有位于 class 语句中的代码,其实都位于特殊的命名空间中,通常称之为类命名空间。Python 中,编写的整个程序默认处于全局命名空间内,而类体则处于类命名空间内。
Python 允许在全局范围内放置可执行代码,当 Python 执行该程序时,这些代码就会获得执行的机会。类似地,Python 同样允许在类范围内放置可执行代码,当 Python 执行该类定义肘,这些代码同样会获得执行的机会。
例如,如下程序测试了类命名空间:
[root@kube class]# py demo12.py 正在定义Item类 偶数: 0 奇数: 1 偶数: 2 奇数: 3 偶数: 4 奇数: 5 偶数: 6 奇数: 7 偶数: 8 奇数: 9 [root@kube class]# cat demo12.py class Item: # 直接在类空间中放置执行性质代码 print('正在定义Item类') for i in range(10): if i % 2 == 0 : print('偶数:', i) else: print('奇数:', i) [root@kube class]# py demo12.py 正在定义Item类 偶数: 0 奇数: 1 偶数: 2 奇数: 3 偶数: 4 奇数: 5 偶数: 6 奇数: 7 偶数: 8 奇数: 9 [root@kube class]#
[root@kube class]# cat demo13.py global_fn = lambda p: print('执行lambda表达式,p参数: ', p) class Category: cate_fn = lambda p: print('执行lambda表达式,p参数: ', p) # 调用全局范围内的global_fn,为参数p传入参数值 global_fn('fkit') # ① c = Category() # 调用类命名空间内的cate_fn,Python自动绑定第一个参数 c.cate_fn() # ② [root@kube class]# py demo13.py 执行lambda表达式,p参数: fkit 执行lambda表达式,p参数: <__main__.Category object at 0x7fc9039ab910> #在类中调用lambda 表达式,python 会自动将自身最为第一个参数传递进去,相当于调用实例的的方法 [root@kube class]#
上面程序分别在全局空间、类命名空间内定义了两个 lambda 表达式,在全局空间内定义的 lambda 表达式就相当于一个普通函数,因此程序使用调用函数的方式来调用该 lambda 表达式,并显式地为第一个参数绑定参数值,如上面程序中 ① 号代码所示。
对于在类命名空间内定义的 lambda 表达式,则相当于在该类命名空间中定义了一个函数,这个函数就变成了实例方法,因此程序必须使用调用方法的方式来调用该 lambda 表达式,Python 同样会为该方法的第一个参数(相当于 self 参数)绑定参数值,如上面程序中 ② 号代码所示。
什么是描述符,Python描述符详解
python 描述符是一中绑定属性的行为。先看一下每个对象包含的属性
[root@kube class]# cat demo14.py #coding:utf-8 class Test: a = 100 def __init__(self): self.b = 200 print(Test.__dict__) #查看类属性 print('-------------------------------') t = Test() print(t.__dict__) #查看实例属性 [root@kube class]# py demo14.py {'__module__': '__main__', 'a': 100, '__init__': <function Test.__init__ at 0x7f6b1b43e320>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None} ------------------------------- {'b': 200} [root@kube class]#
对象属性的访问顺序: ①.实例属性 ②.类属性 ③.父类属性 ④.__getattr__()方法
本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类
魔法方法:__get__(), __set__(), __delete__()
方法的原型为:
① __get__(self, instance, owner)
② __set__(self, instance, value)
③ __del__(self, instance)
[root@kube class]# cat demo15.py #coding:utf-8 class Desc: def __get__(self,instance,owner): print('------- __get__ ------------') print('self: ', self) print('instance: ', instance) print('owner: ', owner) print('=='*20 ,' ') def __set__(self,instance,value): print('------- __sert__ ----------') print('self: ', self) print('instance: ', instance) print('vlaue: ', instance) #定义一个类,并且类的属性就是调用一个其他类 class TestDesc: x = Desc() #类实例化 t = TestDesc() t.x [root@kube class]# py demo15.py ------- __get__ ------------ self: <__main__.Desc object at 0x7ff9f1a7ca50> instance: <__main__.TestDesc object at 0x7ff9f1a7cb10> owner: <class '__main__.TestDesc'> ======================================== [root@kube class]#
可以看到,实例化类TestDesc后,调用对象t访问其属性x,会自动调用类Desc的 __get__方法,由输出信息可以看出:
① self: Desc的实例对象,其实就是TestDesc的属性x
② instance: TestDesc的实例对象,其实就是t
③ owner: 即谁拥有这些东西,当然是 TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的
到此,我可以揭开小小的谜底了,其实,Desc类就是是一个描述符(描述符是一个类哦),为啥呢?因为类Desc定义了方法 __get__, __set__.
所以,某个类,只要是内部定义了方法 __get__, __set__, __delete__ 中的一个或多个,就可以称为描述符
[root@kube class]# cat demo16.py #coding:utf-8 #描述符类 class revealAccess: def __init__(self, initval = None, name = 'var'): self.val = initval self.name = name def __get__(self, obj, objtype): print("Retrieving",self.name) return self.val def __set__(self, obj, val): print("updating",self.name) self.val = val class myClass: x = revealAccess(10,'var "x"') y = 5 m = myClass() print(m.x) print('-------------1---------------') m.x = 20 print('-------------1---------------') print(m.x) print('-------------1---------------') print(m.y) [root@kube class]# py demo16.py Retrieving var "x" 10 -------------1--------------- updating var "x" -------------1--------------- Retrieving var "x" 20 -------------1--------------- 5 [root@kube class]# 从这个例子可以看到,如果一个类的某个属性有数据描述符,那么每次查找这个属性时,都会调用描述符的 __get__() 方法,并返回它的值;同样,每次在对该属性赋值时,也会调用 __set__() 方法。 注意,虽然上面例子中没有使用 __del__() 方法,但也很容易理解,当每次使用 del 类对象.属性(或者 delattr(类对象,属性))语句时,都会调用该方法。
Python property()函数:定义属性
我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。换句话说,正常情况下的类,它包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。
操作类属性的方式比较麻烦,更习惯使用“类对象.属性”这种方式。庆幸的是,Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。
property() 函数的基本使用格式如下:
属性名=property(fget=None, fset=None, fdel=None, doc=None)
其中,fget 参数用于指定获取该属性值的类方法,fset 参数用于指定设置该属性值的方法,fdel 参数用于指定删除该属性值的方法,最后的 doc 是一个文档字符串,用于提供说明此函数的作用。
开发者调用 property() 函数时,可以传入 0 个(既不能读,也不能写的属性)、1 个(只读属性)、2 个(读写属性)、3 个(读写属性,也可删除)和 4 个(读写属性,也可删除,包含文档说明)参数。
[root@kube class]# cat demo19.py #coding:utf-8 class Test: #定义类的默认构造方法 def __init__(self,a,b): self.a = a self.b = b def get1(self): return self.a ,self.b def set1(self,t1): self.a , self.b = t1 def del1(self): self.a = [] self.b = [] t1 = property(get1,set1,del1,'测试property 功能') print(Test.t1.__doc__) print('-----------------------') help(Test.t1) print('-----------------------') a1 = Test('tom','jojo') print(a1.t1) print('-----------------------') a1.t1 = ('qqq','mmmm') print(a1.a) print(a1.b) print('----------------') del a1.t1 print(a1.a) print(a1.b) [root@kube class]# py demo19.py 测试property 功能 ----------------------- ----------------------- ('tom', 'jojo') ----------------------- qqq mmmm ---------------- [] [] [root@kube class]#
Python @property装饰器详解
既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性,除了使用 property() 函数,Python 还提供了 @property 装饰器。通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
@property 就是把一个方法装饰成一个属性调用,优化代码
[root@kube class]# cat demo21.py #coding:utf-8 class Student: def __init__(self,name): self.name = name @property def score(self): return self._score @score.setter def socre(self,value): self._score = value @socre.deleter def score(self): raise AttributeError("You Can't dleter") a = Student('zheng') a.score = 99 print(a.score) [root@kube class]# py demo21.py 99 [root@kube class]#
Python封装机制及实现方法
封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
就好比使用计算机,我们只需要使用计算机提供的键盘,就可以达到操作计算机的目的,至于在敲击键盘时计算机内部是如何工作,我们根本不需要知道。
封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。总的来说,对一个类或对象实现良好的封装,可以达到以下目的:
- 隐藏类的实现细节。
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。
- 可进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码的可维护性。
为了实现良好的封装,需要从以下两个方面来考虑:
- 将对象的属性和实现细节隐藏起来,不允许外部直接访问。
- 把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。
因此,实际上封装有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。
Python 并没有提供类似于其他语言的 private 等修饰符,因此 Python 并不能真正支持隐藏。为了隐藏类中的成员,Python 玩了一个小技巧:只要将 Python 类的成员命名为以双下画线开头的,Python 就会把它们隐藏起来。
[root@kube class]# cat demo22.py #coding:utf-8 class User: def __init__(self): print('python 封装') @property def name(self): return self.__name @name.setter def name(self,name): if len(name) < 3 or len(name) > 8: raise ValueError('用户名长度必须在3~8 之间') self.__name = name @property def age(self): return self.__age @age.setter def age(self,age): if age < 18 or age > 80: raise ValueError('年龄必须在18~80 之间') self.__age = age u = User() u.name = 'zhengyue' u.age = 66 print(u.name) print(u.age) print(u._User__name) #Python 并没有提供真正的隐藏机制,所以 Python 类定义的所有成员默认都是公开的;如果程序希望将 Python 类中的某些成员隐藏起来,那么只要让该成员的名字以双下画线开头即可。
即使通过这种机制实现了隐藏,其实也依然可以绕过去 [root@kube class]# py demo22.py python 封装 zhengyue 66 zhengyue [root@kube class]#
Python继承机制及其使用
继承是面向对象的三大特征之一,也是实现代码复用的重要手段。继承经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。
例如,有一个 Shape 类,该类的 draw() 方法可以在屏幕上画出指定的形状,现在需要创建一个 Rectangle 类,要求此类不但可以在屏幕上画出指定的形状,还可以计算出所画形状的面积。要创建这样的 Rectangle 类,除了将 draw() 方法直接复制到新类中,并添加计算面积的方法,其实还有更简单的方法,即让 Rectangle 类继承 Shape 类,这样当 Rectangle 类对象调用 draw() 方法时,Python 解释器会自动去 Shape 类中调用该方法,如此,我们只需在 Rectangle 类中添加计算面积的方法即可。
Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类、超类)。子类继承父类的语法是:在定义子类时,将多个父类放在子类之后的圆括号里。语法格式如下:
class 类名(父类1, 父类2, ...): #类定义部分
注意,Python 的继承是多继承机制,即一个子类可以同时拥有多个直接父类
object 类是所有类的父类,要么是直接父类,要么是间接父类。
注意,Python 的继承是多继承机制,即一个子类可以同时拥有多个直接父类。
从上面的语法格式来看,定义子类的语法非常简单,只需在原来的类定义后增加圆括号,并在圆括号中添加多个父类,即可表明该子类继承了这些父类。如果在定义一个 Python 类时,并未显式指定这个类的直接父类,则这个类默认继承 object 类。
object 类是所有类的父类,要么是直接父类,要么是间接父类。
父类和子类的关系,就好像水果和苹果的关系,苹果是一种特殊的水果,苹果是水果的子类,水果是苹果的父类,因此可以说,苹果继承了水果。不仅如此,由于子类是一种特殊的父类,因此父类包含的范围总比子类包含的范围要大,所以可以认为父类是大类,而子类是小类。
从实际意义上看,子类是对父类的扩展,子类是一种特殊的父类。从这个意义上看,使用继承来描述子类和父类的关系是错误的,用扩展更恰当。因此,这样的说法更加准确:苹果扩展了水果这个类。
从子类的角度来看,子类扩展(extend)了父类;但从父类的角度来看,父类派生(derive)出子类。也就是说,扩展和派生所描述的是同一个动作,只是观察角度不同而已。
[root@kube class]# cat demo24.py #coding:utf-8 class Animal: def info(self): print('我是一种动物') class Plant: def check(self): print('我不是植物') class Cat(Animal,Plant): #Cat 继承了 Animal 和Plant 的类实例 pass u = Cat() u.info() u.check() [root@kube class]# py demo24.py 我是一种动物 我不是植物 [root@kube class]#
即子类扩展(继承)了父类,将可以继承得到父类定义的方法,这样子类就可复用父类的方法了
关于Python的多继承
Python 虽然在语法上明确支持多继承,但通常推荐如果不是很有必要,则尽量不要使用多继承,而是使用单继承,这样可以保证编程思路更清晰,而且可以避免很多麻烦。
当一个子类有多个直接父类时,该子类会继承得到所有父类的方法,这一点在前面示例中己经做了示范。现在的问题是,如果多个父类中包含了同名的方法,此时会发生什么呢?此时排在前面的父类中的方法会“遮蔽”排在后面的父类中的同名方法。
[root@kube class]# cat demo25.py #coding:utf-8 class Item: def info (self): print("Item中方法:", '这是一个商品') class Product: def info (self): print("Product中方法:", '这是一个工业产品') class Mouse(Item, Product): # ① pass m = Mouse() m.info() [root@kube class]# py demo25.py #排在前面的父类优先级高,会被一直调用 Item中方法: 这是一个商品 [root@kube class]# py demo25.py Item中方法: 这是一个商品 [root@kube class]# py demo25.py Item中方法: 这是一个商品 [root@kube class]# py demo25.py Item中方法: 这是一个商品 [root@kube class]#
Python MRO方法解析顺序详解
我们知道,Python 类是支持(多)继承的,一个类的方法和属性可能定义在当前类,也可能定义在基类。针对这种情况,当调用类方法或类属性时,就需要对当前类以及它的基类进行搜索,以确定方法或属性的位置,而搜索的顺序就称为方法解析顺序。
方法解析顺序(Method Resolution Order),简称 MRO。对于只支持单继承的编程语言来说,MRO 很简单,就是从当前类开始,逐个搜索它的父类;而对于 Python,它支持多继承,MRO 相对会复杂一些。
实际上,Python 发展至今,经历了以下 3 种 MRO 算法,分别是:
- 从左往右,采用深度优先搜索(DFS)的算法,称为旧式类的 MRO;
- 自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化;
- 自 Python 2.3 版本,对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法。
Python父类方法重写
子类扩展了父类,子类是一种特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的方法。但在一些场景中,子类需要重写父类的方法。
例如,鸟类都包含了飞翔方法,其中驼鸟是一种特殊的鸟类,因此驼鸟应该是鸟的子类,它也将从鸟类获得飞翔方法,但这个飞翔方法明显不适合驼鸟,为此,驼鸟需要重写鸟类的方法。
[root@kube class]# py demo26.py 子类重写父类的方法 [root@kube class]# cat demo26 cat: demo26: No such file or directory [root@kube class]# cat demo26.py #coding:utf-8 class BaseClass: def foo(self): print('父类定义foo 的方法') class SubClass(BaseClass): def foo(self): print('子类重写父类的方法') f = SubClass() f.foo() [root@kube class]# py demo26.py 子类重写父类的方法 [root@kube class]#
使用未绑定方法调用被重写的方法
如果在子类中调用重写之后的方法,Python 总是会执行子类重写的方法,不会执行父类中被重写的方法。如果需要在子类中调用父类中被重写的实例方法,那该怎么办呢?
别忘了,Python 类相当于类空间,因此 Python 类中的方法本质上相当于类空间内的函数。所以,即使是实例方法,Python 也允许通过类名调用。区别在于:在通过类名调用实例方法时,Python 不会为实例方法的第一个参数 self 自动绑定参数值,而是需要程序显式绑定第一个参数 self。这种机制被称为未绑定方法。
[root@kube class]# cat demo26.py #coding:utf-8 class BaseClass: def foo(self): print('父类定义foo 的方法') class SubClass(BaseClass): def foo(self): print('子类重写父类的方法') def bar(self): BaseClass.foo(self) f = SubClass() f.foo() f.bar() [root@kube class]# py demo26.py 子类重写父类的方法 父类定义foo 的方法 [root@kube class]#
如何使用Python继承机制(子类化内置类型)
我们知道,Python 中内置有一个 object 类,它是所有内置类型的共同祖先,也是所有没有显式指定父类的类(包括用户自定义的)的共同祖先。因此在实际编程过程中,如果想实现与某个内置类型具有类似行为的类时,最好的方法就是将这个内置类型子类化。
内置类型子类化,其实就是自定义一个新类,使其继承有类似行为的内置类,通过重定义这个新类实现指定的功能
Python super()函数:调用父类的构造方法
Python 的子类也会继承得到父类的构造方法,但如果子类有多个直接父类,那么会优先选择排在最前面的父类的构造方法。例如如下代码:
[root@kube class]# cat demo27.py class Employee : def __init__ (self, salary): self.salary = salary def work (self): print('普通员工正在写代码,工资是:', self.salary) class Customer: def __init__ (self, favorite, address): self.favorite = favorite self.address = address def info (self): print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address)) # Manager继承了Employee、Customer #class Manager (Employee, Customer): # pass # Manager继承了Employee、Customer class Manager(Employee, Customer): # 重写父类的构造方法 def __init__(self, salary, favorite, address): print('--Manager的构造方法--') # 通过super()函数调用父类的构造方法 super().__init__(salary) # 与上一行代码的效果相同 #super(Manager, self).__init__(salary) # 使用未绑定方法调用父类的构造方法 Customer.__init__(self, favorite, address) # 创建Manager对象 m = Manager(25000, 'IT产品', '广州') m.work() #① m.info() #② [root@kube class]# py demo27.py --Manager的构造方法-- 普通员工正在写代码,工资是: 25000 我是一个顾客,我的爱好是: IT产品,地址是广州 [root@kube class]#
Python __slots__:限制类实例动态添加属性和方法
_slots__ 属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名(对于 Python 而言,方法相当于属性值为函数的属性)
需要说明的是,__slots__ 属性并不限制通过类来动态添加属性或方法,
[root@kube class]# cat demo31.py class base(object): __slots__=('x') var=8 def __init__(self): pass b=base() b.x=88 #添加实例变量 b.xx = 99 print(b.x) print(b.xx) [root@kube class]# py demo31.py Traceback (most recent call last): File "demo31.py", line 9, in <module> b.xx = 99 AttributeError: 'base' object has no attribute 'xx' [root@kube class]#
Python type()函数:动态创建类
实际上 Python 完全允许使用 type() 函数(相当于 type 类的构造器函数)来创建 type 对象,又由于 type 类的实例就是类,因此 Python 可以使用 type() 函数来动态创建类。
[root@kube class]# cat demo32.py def fn(self): print('fn函数') # 使用type()定义Dog类 Dog = type('Dog', (object,), dict(walk=fn, age=6)) # 创建Dog对象 d = Dog() # 分别查看d、Dog的类型 print(type(d)) print(type(Dog)) d.walk() print(Dog.age) [root@kube class]# py demo32.py <class '__main__.Dog'> <class 'type'> fn函数 6 [root@kube class]#
上面第 4 行代码使用 type() 定义了一个 Dog 类。在使用 type() 定义类时可指定三个参数:
参数一:创建的类名。
参数二:该类继承的父类集合。由于 Python 支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。
参数三:该字典对象为该类绑定的类变量和方法。其中字典的 key 就是类变量或方法名,如果字典的 value 是普通值,那就代表类变量;如果字典的 value 是函数,则代表方法。
由此可见,第 4 行代码定义了一个 Dog 类,该类继承了 object 类,还为该类定义了一个 walk() 方法和一个 age 类变量。
https://www.cnblogs.com/intimacy/p/8119449.html
Python MetaClass元类详解
MetaClass(元类),简单的理解,就是创建类的类,即创建类之后,再由类来创建实例进行应用。使用元类可以在创建类时动态修改类定义。为了使用元类动态修改类定义,程序需要先定义元类。
[root@kube class2]# py demo1.py False True bip [root@kube class2]# cat demo1.py #-*- coding:utf-8 -*- def upper_attr(class_name, class_parents, class_attr): #遍历属性字典,把不是__开头的属性名字变为大写 new_attr = {} for name,value in class_attr.items(): if not name.startswith("__"): new_attr[name.upper()] = value #调用type来创建一个类 return type(class_name, class_parents, new_attr) class Foo(object, metaclass=upper_attr): bar = 'bip' print(hasattr(Foo, 'bar')) print(hasattr(Foo, 'BAR')) f = Foo() print(f.BAR) [root@kube class2]# py demo1.py False True bip [root@kube class2]#
什么是多态,Python多态及用法详解
多态就是一个对象可以有多中形态,取决于调用的方法和属性,没有一种具体的形态
[root@kube class2]# cat demo7.py class Canvas: def duotai_pic(self, shape): print('--开始绘图--') shape.duotai(self) class Rectangle: def duotai(self, canvas): print('在%s上绘制矩形' % canvas) class Triangle: def duotai(self, canvas): print('在%s上绘制三角形' % canvas) class Circle: def duotai(self, canvas): print('在%s上绘制圆形' % canvas) c = Canvas() print(Canvas()) print(c) # 传入Rectangle参数,绘制矩形 c.duotai_pic(Rectangle()) # 传入Triangle参数,绘制三角形 c.duotai_pic(Triangle()) # 传入Circle参数,绘制圆形 c.duotai_pic(Circle()) [root@kube class2]# py demo7.py <__main__.Canvas object at 0x7f5a34484c90> <__main__.Canvas object at 0x7f5a34484c10> --开始绘图-- 在<__main__.Canvas object at 0x7f5a34484c10>上绘制矩形 --开始绘图-- 在<__main__.Canvas object at 0x7f5a34484c10>上绘制三角形 --开始绘图-- 在<__main__.Canvas object at 0x7f5a34484c10>上绘制圆形 [root@kube class2]#
Python枚举类定义和使用(详解版)
在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有 4 个对象;再比如行星类,目前只有 8 个对象。这种实例有限且固定的类,在 Python 中被称为枚举类。
程序有两种方式来定义枚举类:
- 直接使用 Enum 列出多个枚举值来创建枚举类。
- 通过继承 Enum 基类来派生枚举类。
枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、状态等。在没有专门提供枚举类型的时候我们是怎么做呢,一般就通过字典或类来实现:
Color = { 'RED' : 1, 'GREEN': 2, 'BLUE' : 3, } class Color: RED = 1 GREEN = 2 BLUE = 3
#这种来实现枚举如果小心翼翼地使用当然没什么问题,毕竟是一种妥协的解决方案。它的隐患在于可以被修改
[root@kube class2]# cat demo8.py #coding:utf-8 from enum import Enum class Color(Enum): red = 1 green = 2 blue = 3 print(Color.red) print(Color.green) print(Color.blue) [root@kube class2]# py demo8.py Color.red Color.green Color.blue [root@kube class2]#
[root@kube class2]# cat demo9.py #coding:utf-8 from enum import Enum class Orientation(Enum): # 为序列值指定value值 EAST = '东' SOUTH = '南' WEST = '西' NORTH = '北' def info(self): print('这是一个代表方向【%s】的枚举' % self.value) #Python 还为枚举提供了一个 __members__ 属性,该属性返回一个 dict 字典,字典包含了该枚举的所有枚举实例。程序可通过遍历 __members__ 属性来访问枚举的所有实例。
print(Orientation.__members__) print(Orientation.SOUTH) print(Orientation.SOUTH.value) # 通过枚举变量名访问枚举 print(Orientation['WEST']) # 通过枚举值来访问枚举 print(Orientation('南')) # 调用枚举的info()方法 Orientation.EAST.info() # 遍历Orientation枚举的所有成员 for name, member in Orientation.__members__.items(): print(name, '=>', member, ',', member.value) [root@kube class2]# py demo9.py OrderedDict([('EAST', <Orientation.EAST: '东'>), ('SOUTH', <Orientation.SOUTH: '南'>), ('WEST', <Orientation.WEST: '西'>), ('NORTH', <Orientation.NORTH: '北'>)]) Orientation.SOUTH 南 Orientation.WEST Orientation.SOUTH 这是一个代表方向【东】的枚举 EAST => Orientation.EAST , 东 SOUTH => Orientation.SOUTH , 南 WEST => Orientation.WEST , 西 NORTH => Orientation.NORTH , 北 [root@kube class2]#
上面程序通过继承 Enum 派生了 Orientation 枚举类,通过这种方式派生的枚举类既可额外定义方法,如上面的 info() 方法所示,也可为枚举指定 value(value 的值默认是 1、2、3、…)。
枚举的构造器
枚举也是类,因此枚举也可以定义构造器。为枚举定义构造器之后,在定义枚举实例时必须为构造器参数设置值。例如如下程序:
[root@kube class2]# cat demo10.py from enum import Enum class Gender(Enum): MALE = '男', '阳刚之力' #MALE 和 FEMALE 就是定义的参数值 FEMALE = '女', '柔顺之美' def __init__(self, cn_name, desc): self._cn_name = cn_name self._desc = desc @property def desc(self): return self._desc @property def cn_name(self): return self._cn_name # 访问FEMALE的name print('FEMALE的name:', Gender.FEMALE.name) #将FEMALE 作为参数传入构造函数 # 访问FEMALE的value print('FEMALE的value:', Gender.FEMALE.value) # 访问自定义的cn_name属性 print('FEMALE的cn_name:', Gender.FEMALE.cn_name) # 访问自定义的desc属性 print('FEMALE的desc:', Gender.FEMALE.desc) [root@kube class2]# py demo10.py FEMALE的name: FEMALE FEMALE的value: ('女', '柔顺之美') FEMALE的cn_name: 女 FEMALE的desc: 柔顺之美 [root@kube class2]#
上面代码为 MALE 枚举指定的 value 是‘男’和‘阳刚之力’这两个字符串,其实它们会被自动封装成元组后传给 MALE 的 value 属性;而且此处传入的‘男’和‘阳刚之力’ 这两个参数值正好分别传给 cnname 和 desc 两个参数。简单来说,枚举的构造器需要几个参数,此处就必须指定几个值。