面向对象编程

Python的类提供了面向对象变成的所有标准特性:类继承机制允许指定多个基类,派生类可以覆盖基类的任何方法,一个方法可以调用基类中同名的方法。对象可以包含任意数量和类型的数据。与模块一样,类也拥有Python语言的动态特性:类在运行时创建,可以在创建后修改。

1,认识面向对象编程

【类成员】:也称为类的变量,描述类的各种概念。在Python中主要包括3种类成员:字段、方法和属性。

类的特征属性

1
2
继承:
不同类型之间可能会存在部分代码重叠,例如,共享数据或方法,但是我们又不想重写雷同的代码,于是就利用继承机制来快速实现代码的“复制”。继承机制简化了类的创建,提高了代码的可用性。
1
2
封装:
封装就是信息隐藏,将类的使用和实现分开,只保留有限的接口(即方法)与外部联系。对于开发人员来说,只要知道类的使用即可,而不用关心类的实现过程,以及涉及的技术细节。这样可以让开发人员把更多的精力集中于应用层面开发,同时也避免了程序之间的依赖和耦合。
1
2
多态:
多态就是接口的多种不同的实现方式。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行效果。Python是弱类型语言,天生支持多态,多态关注的不是传入对象是否符合指定类型,而关注的是传入对象是否有符合要执行的方法,如果有就执行,因此也称为鸭子类型。

2,使用类

使用类之前,需要先定义类,然后再实例化类,类的实例就是一个具体对象,调用对象的属性和方法就可以访问类的成员,完成特定任务。

1,定义类

​ 在Python中,使用class关键字可以定义一个类,具体语法格式如下:

1
2
3
4
5
class 类名
'''
帮助信息(可选)
'''
类主体

根据惯例,类名一般使用大写字母开头,如果类名包含两个单词,第2个单词的首字母也要大写。类帮助信息与函数的帮助信息一样,一般位于类主体的首行,用来指定类的帮助信息,在创建类实例时,当输入类名和左括号时,会显示帮助信息。

类主体由各种类成员组成。

【示例一】定义空类,在定义类时,如果暂时没有设计好具体的功能,可以使用pass语句定义占位符,暂时暂停设计空类。

1
2
class No:
pass

空类不执行任何操作,也不包含任何成员信息

【示例2】定义一个包含两个类成员的Student类型,其中name为类的字段,saying()作为类的方法。

1
2
3
4
class Student():
name = "学生"
def saying(self):
return "Hi,Python"

在类中,包含Self参数的函数称为实例方法,实例方法的第一个参数指向实例对象,通过类或者实例对象可以访问类的成员。

2,实例化类

类与函数一样的都是静态代码,必须被调用时才会执行。使用小括号语法可以调用类,将返回一个对象,它是类的实例,这个过程称为类的实例化。语法格式如下:

1
实例对象 = 类名()

【示例】实例化Student类,然后通过语法使用实例对象访问类的成员

1
2
3
4
5
6
7
8
class Student():
name = "学生"
def saying(self):
return "Hi,Python"

studnt1 = Student()
print(student1.name)
print(student1.saying())

3,初始化类

​ Python类拥有一个名为init()的魔术方法,也被称为初始化函数,该方法在类的实例化过程中会自动被调用。因此利用init()初始化函数可以初始化类,为类的实例化对象配置初始值。

【示例1】定义一个Student类,包含一个初始化函数init和一个方法saying()。在初始化函数中包含3个参数,其中self表示实例对象,必须设置name和age的初始化配置参数,用于实例化类过程中设置初始值。在saying()方法中输出实例对象的初始值信息。

1
2
3
4
5
6
7
8
9
10
11
12
def Student:
def __init__(self,name,age):
self.name = name
self.age = age
def saying(self):
return "我的名字是{},今年{}岁了。".format(self.name,self.age)

student1 = Student("张三",19)
print(student1.saying())

输出为:
我的名字是张三,今年19岁了。

self代表类的实例,而不是类的自身。在Python中,与普通的函数不同,所有方法都必须有一个额外的参数(self),他作为第1个参数而存在,代表类的实例对象。通过self.class可以访问实例对象的类。

【示例2】本示例简单演示了类中self和self.class分别代表什么。

1
2
3
4
5
6
7
class Test:
def prt(self):
print(self)
print(self.__class__)

test1 = Test()
test1.prt()

输出结果:

1
2
<__main__.Test object at 0x000001C682AD7550>
<class '__main__.Test'>

从执行结果可以很明显看出,self代表的是类的实例,代表当前对象的地址,而self.class则指向类。

【示例3】self可以换成任意变量名,也能够正常执行。由于self表示本身的含义,在Python中self定义为类型的实例对象,与其他语言中的this含义相似。

1
2
3
4
5
6
7
class Test:
def prt(who):
print(who)
print(who.__class__)

test1 = Test()
test1.prt()

运行结果

1
2
<__main__.Test object at 0x00000220F9FC7550>
<class '__main__.Test'>

4,定义学生类

本案例定义一个学生类,包含学生的姓名,性别,学号等信息,实例化对象之后,调用introduce()方法可以打印个人信息。

1
2
3
4
5
6
7
8
9
10
11
12
class Student:
def __init__(self,id,name,gender):
self.id = id
self.name = name
self.gender = gender

def introduce(self):
print('大家好!我是:{},性别:{},学号:{}'.format(self.name,self.gender,id))

Stu1 = Student('2019','张三','男')
Stu1.introduce()

运行结果:

1
大家好!我是:张三,性别:男,学号:2019

5,案例:定义员工类

本案例定义一个员工类,包含员工姓名、部门、年龄等信息,并添加统计员工总人数的功能。

1
2
3
4
5
6
7
8
9
10
11
class Employee:
'''员工类'''
count = 0
def __init__(self,name,age,department):
self.name = name
self.age = age
self.department
Employee.count += 1

emp1 = Employee('zhangsan',19,'A')
print('总共创建%d个员工对象' % Employee.count)

运行结果:

1
总共创建1个员工对象

3,类成员

在Pyhon中,类的成员包括字段、方法和属性。

1,字段

字段用来存储值。Python字段包括普通地段(也称为动态字段)和动态字段。下面通过一个简单的示例来认识普通字段和静态字段的不同和用法。

【示例】定义Province类,其中包含country和name两个字段,其中country为静态字段,name为普通字段。

1
2
3
4
5
6
7
8
9
10
11
class Province:
country = '中国' #静态字段,保存在类中
def __init__(self,name):
self.name = name #普通字段,保存在对象中

#访问普通字段
obj = Provice('北京')
print(obj.name)
#访问静态字段
print(Provice.country)
print(obj.country)

【提示】

​ 在所有成员中,只有普通字段保存在实例对象中,及创建了多少个实例对象,在内存中就有多少个普通字段。其他成员都保存在类中,不管创建多少个实例对象,在内存中只创建一份。

【小结】

下面简单比较普通字段和动态的不同。

1
2
3
4
5
6
#保存位置:普通字段保存在实例中。动态字段保存在类对象中
#归属对象:普通字段属于实例对象,静态字段属于类对象
#访问对象:普通字段必须通过实例对象来访问;静态字段可以通过类对象直接访问,也可以通过实例对象来访问。建议最好使用类访问,在必要的情况下再使用实例对象进行访问,但是实例对象无权修改动态静态。
#存储方式:普通字段在每个实例对象中都保存一份,静态字段在内存中仅保存一份。
#加载方式:普通字段只在实例化类的时候创建,动态字段在类的代码被加载时创建。
应用场景:如果在每个实例对象中字段的值都不相同,那么就可以使用普通字段;如果在每个实例对象中字段的值都相同,那么可以使用动态字段。

2,方法

方法与函数的用法基本相同,用来完成特定的任务,或者执行特定的行为。Python方法包括普通方法、类方法和静态方法。

普通方法

由实例对象拥有,并由实例对象调用。

在类结构中,未添加类的方法和静态方法装饰器的函数搜可以作为普通方法。

对于普通方法来说,第一个参数必须是实例对象,一般以self作为第一个参数的名称,也可以使用其他名字来进行命名。

当使用实例对象调用普通方法时,系统会自动把实例对象传递给第一个参数。

当使用类对象调用普通方法时,系统会把他视为普通函数,不会自动传入实例对象。

【提示】

1
在普通方法中,可以通过self访问类成员、也可以使用实例的私有成员,如果存在相同名称的类成员和实例成员,则实例成员优先级高于类成员。

类方法

由类对象拥有,由类调用,也允许实例化对象调用。

定义类方法时,需要使用修饰器@classmethod标识。

对于类方法拉来说,第一个参数必须是类对象,一般以cls作为第一个参数的名称,当然也可以使用其他的名字进行命名。当调用类方法时,系统会自动把类对象传递给第一个参数。

使用实例对象和类对象都可以访问类方法。在类方法中,可以通过cls访问类对象的属性和方法,其主要作用就是修改类属性和方法。

静态方法

使用修饰器@staitcmethod标识。无默认参数,如果要在静态方法中引用类属性,可以通过类对象或实例对象实现。

【示例一】本示例简单演示了如何定义普通方法、类方法和静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Test:
name = 'Test'
def __init__(self,name):
self.name=name

def ord_func(self):
"""普通方法,至少包含一个self参数"""
print('普通方法')
print(self.name)
@classmethod
def class_func(cls):
"""类方法,至少包含一个cls参数"""
print('类方法')
print(cls.name)
@staticmethod
def static_func():
"""静态方法,无确认参数"""
print('静态方法')
print(name)


f = Test("test")
f.ord_func()
f.class_func()
f.static_func()
Test.class_func()
Test.static_func()
Test.ord_func()

运算结果:

1
2
3
4
5
普通方法
test
类方法
Test
静态方法

通过比较可以看到

三种类型的方法都可以有实例对象调用,类对象只能调用类方法和静态方法。如果类对象直接调用普通方法,将失去self默认参数,以普通函数的方式调用。

调用方法时,自动传入的参数也不同,普通方法传入的是实例对象,而类方法传入的是类对象。

当调用普通方法时,可以读,写普通字段,也可以只读静态字段;当调用类方法时,只能够访问静态字段的值;静态方法只能通过参数传入或者类对象间接访问类的字段。

【示例2】本示例演示了实例对象如何使用类方法修改动态字段的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class People():
country = '中国'
#类方法,使用classmethod进行修饰
@classmethod
def get(cls):
return cls.country
@classmethod
def set(cls,country):
cls.country = country

p = People()
print(p.get())
print(People.get())
p.set('美国')
print(p.get())

运行结果:

1
2
3
中国
中国
美国

【示例3】本示例演示了在静态方法中如何使用类对象访问静态字段。

1
2
3
4
5
6
7
8
9
class people:
country ='中国'
@staticmethod
def get():
return people.country

P = people()
print(people.get())
print(P.get())

运行结果:

1
2
中国
中国

3,属性

​ 在Python中,属性(propetry)是一个特殊的概念,它实际上是普通方法的变种。使用@propetry修饰器进行标识,可以被类的方法变成属性。

【示例1】本例简单比较方法和属性的不同

1
2
3
4
5
6
7
8
9
10
11
class Test:
_name = "test"
def get_name(self):
return self._name
#定义属性
@property
def name(self):
return self.name
obj = Test()
print(obj.get_name())
print(obj.name)

通过上面的代码可以看到,属性有3种特征:

在普通方法的基础上添加@property修饰器,可以定义数字属性。

在属性函数中,第一个参数必须是实例对象,一般以self作为第一个参数的名称,也可以使用其他名字进行命名,

在调用属性函数时,不需要使用小括号。

属性由方法演变而来,在Python中如果没有属性,完全可以使用方法代替属性实现其功能,属性存在的意义是访问属性时可以模拟出访问字段完全相同的语法形式。

【示例2】设计一个数据库分页显示的功能模块。在向数据库请求数据时,能够根据用户请求的当前页数及预定的每页显示的记录数计算将要显示的从第m条到第n条的记录起止数。最后,可以根据m和n去数据库中请求数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Pager:
def __init__(self,current_page):
self.current_page = current_page
self.per_items = 10
@property
def start(self):
val = (self.current_page - 1) * self.per_items
return val
@property
def end(self):
val = self.current_page * self.per_items
return val

p = Pager(3)
print(p.start)
print(p.end)

运算结果:

1
2
20
30

通过上面的示例可以看到,属性与字段虽然都用来读,写值,但是在属性内部封装了一系列的逻辑,并最终将计算结果返回,而字段仅仅记录一个值。

属性的访问方式有3种:读、写、删,对应的装饰器为@property、@方法名.setter、@方法名.deleter。【示例3】结合示例演示如何读取,修改和修除属性。除本例设计一个商品报价类,初始化参数为原价和折扣,然后可以读取商品实价格,也可以修改商品原价或者删除商品的价格属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Goods(object):
def __init__(self,price,discount=1):
self.orig_price = price
self.discount = discount
@property
def price(self):
new_price = self.orig_price * self.discount
return new_price
@price.setter
def price(self,value):
self.orig_price = value
@price.deleter
def price(self):
del self.orig_price

obj = Goods(120,0.7)
print(obj.price)
obj.price = 200
del obj.price
print(obj.price)

【注意】

如果定义只读属性,则可以仅定义@property和@price.deleter修饰器函数。

4,构造属性

使用property()构造函数可以把属性操作的函数绑定到字段上,这样可以快速定义属性。

具体语法格式如下:

1
class property ([fget[,fset[,fdel[,doc]]]])

说明如下:

1
2
3
4
#fget:获取属性值的普通方法
#fset:设置属性值的普通方法
#fdel:删除属性值的普通方法
#doc:属性描述信息

该函数放回一个属性,定义的属性与使用@property修饰器定义的属性具有相同的功能。

【示例】针对上一节示例3,本示例把它转换为property()构造函数生成属性的方法来设计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Goods():
def __init__(self,price,discount=1):
self.orig_price = price
self.discount = discount
def get_price(self):
new_price = self.orig_price * self.discount
return new_price
def set_price(self,value):
self.orig_price = value
def del_price(self):
del self.orig_price

# 构造price属性
price = property(get_price,set_price,del_price,"可读、可写、可删除属性:商品价格")

obj = Goods(120,0.7)
print(obj.price)
obj.price = 200
del obj.price
print(obj.price)

obj是Goods的实例化,obj.price将触发get_price()方法,obj.price=200将触发set.price()方法,del obj.price将触发del price()方法。

property()构造函数中的前三个参数函数分别对应的是获取属性的方法、设置属性的方法、以及删除属性的方法。外部对象可以通过访问price的方式达到获取、设置或删除属性的目的。如果允许用户直接调用这三个方法,使用体验不及属性,同时存在安全隐患。

5,成员访问限制

类的所有成员都有以下两种形式。

公有成员:在任何地方都能访问

私有成员:只有类的内部才能访问

私有成员和公有成员的定义方式不同:私有成员命名时,前两个字符必须是下划线,特殊成员除外,如initcalldict

【示例】在类Test中定义两个成员:字段a是公有属性,字段b是私有属性。a可以通过实例对象直接访问,而b只能在类内访问,如果在外部访问b,只能通过公共方法间接访问。

1
2
3
4
5
6
7
8
9
10
11
class Test:
def __init__(self):
self.a = '公有字段'
self.__b = '私有字段'
def get(self):
return self.__b

test = Test()
print(test.a)
print(test.get())
print(test.__b)

运行结果:

1
2
公有字段
私有字段

【提示】

如果非要访问私有属性,也可以通过如下的方式访问。

1
对象._类__属性名

可以根据上述代码实现访问私有属性;

1
print(test._Test__b)

6,案例四则运算

设计一个MyMath类,该类能够实现简单的加、减、乘、除四则运算。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MyMath:
def __init__(self,a,b):
self.a = a
self.b = b
def addition(self):
return self.a + self.b
def subtraction(self):
return self.a - self.b
def multiplication(self):
return self.a * self.b
def division(self):
if self.b == 0:
print("除数不能为0")
else:
return self.a / self.b
5
while True:
a = int(input('参数a:'))
b = int(input('参数b:'))
myMath = MyMath(a,b)
print('加法结果:',myMath.addition())
print('减法结果:',myMath.subtraction())
print('乘法结果:',myMath.multiplication())
if myMath.division() != None:
print('除法结果:',myMath.division())
flag = input('是否退出运算[y/n]:')
if flag == 'y':
break

运算结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
参数a:5
参数b:6
加法结果: 11
减法结果: -1
乘法结果: 30
除法结果: 0.8333333333333334
是否退出运算[y/n]:n
参数a:3
参数b:4
加法结果: 7
减法结果: -1
乘法结果: 12
除法结果: 0.75
是否退出运算[y/n]:y

7,圆类

本案例设计一个圆类,该类能够表示圆的位置和大小,能够计算圆的面积和周长,能够对圆的位置进行修改,然后创建圆的实例对象,并进行相应的操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Circle:
def __init__(self,x,y,r):
self.x = x
self.y = y
self.r = r
def get_position(self):
return (self.x,self.y)
def set_position(self,x,y):
self.x = x
self.y = y
def get_area(self):
return 3.14 * self.r
def get_circumference(self):
return 3.14 * 2 * self.r

circle = Circle(2,4,4)
area = circle.get_area()
circumference = circle.get_circumference()
print('圆的面积:%d' % area)
print('圆的周长:%d' % circumference)
print('圆的初始位置:',circle.get_position())
circle.set_position(3,4)
print('修改后圆的位置:',circle.get_position())

运算结果:

1
2
3
4
圆的面积:12
圆的周长:25
圆的初始位置: (2, 4)
修改后圆的位置: (3, 4)

4,类的特殊成员

Python内置了一组特殊的成员,他们有着特殊的名称,开头和结尾以及双下划线标识,这些成员可以直接访问,及有特殊的用途,俗称魔法变量或魔术方法,下面将详细介绍。

1,doc

doc表示类的描述信息,通过类对象直接访问。

【提示】

1
类的描述信息就是在定义类时,其内第一行的注释信息。

【示例】定义一个空类,然后直接使用类对象访问doc属性,获取类的描述信息。

1
2
3
4
5
6
7
class Test:
"""Test空类
暂时没有任何代码"""
pass

print(Test.__doc__)

输出结果:

1
2
Test空类
暂时没有任何代码

2,moduleclass

module表示当前操作对象在哪个模块中,class表示当前操作的对象属于哪个类。

【示例1】定义Student类,然后实例化后,通过实例对象访问moduleclass属性,获取模块名称和类的名称。

1
2
3
4
5
6
7
8
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
student = Student('zjj',21)
print(student.__class__)
print(student.__module__)

输出结果:

1
2
<class '__main__.Student'>
__main__

其中,main表示当前文档

【注意】

1
2
3
4
5
6
7
如果使用类对象访问__module__和__class__属性,将获取如下信息。
print(student.__class__)
print(student.__module__)
输出为
<class 'type'>
__main__
对于实例对象和类对象,__module__返回的值都是相同的,而sudent.__class__表示数据类型。

3,new

new表示类的实例化函数,该函数将在调用类创建实例对象时被自动执行,并且先于init被执行,注意,new函数会返回一个实例,而init函数会返回None。

【注意】

1
所有对象都是通过new方法实例化的,在__new__函数里面调用了init方法,所以在实例化的过程中先执行的是new,而不是init方法。

【示例1】本示例中重写了new函数,这将导致init函数不能够被自动执行。

1
2
3
4
5
6
7
8
9
10
class F:
def __init__(self,name):
self.name = name
print("Foo __init__")
def new(cls,*arg,**kwargs):
cls.name = "test"
return object.__new__(cls)
f = F("ok")
print(F.name)
print(f.name)

【提示】

1
类的生成、调用顺序依次是__new__、__init__、__call__。

【示例3】

new方法在继承一些不可变的类时会用到,如int、str、tuple。下面创建一个永远保留两个小数的float类型。

1
2
3
4
class RoundFloat(float):
def __new__(cls,value):
return super().__new__(cls,round(value,2))
print(RoundFloat(3.14159))

4,init

init表示类的初始化函数,该函数将在实例化类的过程中被自动调用,主要任务是完成类的初始化配置,如设置的初始化、配置运行环境等。例如,在数据库访问类中,可以在初始化函数中完成数据库的登录和验证工作,避免每次访问数据库都需要进行登录和验证操作。

【示例】设计一个数据库操作类,在init初始化函数中完全数据库的连接操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
import MySQLdb
class DB:
def __init__(self,name,passwd):
self.name = name
self.passwd = passwd
self.db = MySQLdb.connect("localhost",name,passwd,"DatabaseName",charset='utf-8')
def getData(self,sql):
pass
def updateData(self,sql):
pass
def delData(self,id):
pass

5,call

当使用小括号调用类对象时,将触发执行new函数,即创建类的实例,同时还会触发初始化函数init的执行。而当使用小括号调用实例对象时,将触发执行call函数。

​ 【示例】设计一个加法器的类,允许当类实例化时初始传入多个被加数字,然后调入对象,可以继续传入多个数字,并返回他们的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Add:
'''
Add加法器类
#可以在实例化时传入多个数字,调用对象时也可以继续传入多个数字,然后返回他们的和
'''
def __init__(self,*args):
self.__sum = 0
for i in args:
if(isinstance(i,(int,float))):
self.__sum += i
def __call__(self,*args):
for i in args:
if(isinstance(i,(int,float))):
self.__sum += i
return self.__sum
def __del__(self):
self.__sum = 0


add = Add()
print(add(3,4,5))
add = Add(1,2,3)
print(add(3,4,5))

运算结果:

1
2
12
18

6,dict

dict能够获取类对象或实例对象包含的所有成员。

【示例】定义一个Test类,包含一个静态动态ver与两个方法init和func,同时定义两个普通字段name和passwd。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test:
ver = 'test'
def __init__(self,name,passwd):
self.name = name
self.passwd = passwd
def func(self,*args,**kwargs):
print('func')

print(Test.__dict__)

obj1 = Test('other',10000)
print(obj1.__dict__)

obj2 = Test('this',3888)
print(obj2.__dict__)

运算结果:

1
{'__module__': '__main__', 'ver': 'test', '__init__': <function Test.__init__ at 0x000001EF6615E9E0>, 'func': <function Test.func at 0x000001EF6615F130>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
1
2
{'name': 'other', 'passwd': 10000}
{'name': 'this', 'passwd': 3888}

7,str

str函数能够返回实例对象的字符串表示。如果为类定义了str方法,那么在打印实例对象时,默认会输出该方法的返回值。

【示例】为Test类定义了str方法,设计当打印Test类的实例对象时,显示提示性的字符串表示:

1
2
3
4
5
6
class Test:
def __str__(self):
return "Test类的实例"

test = Test()
print(test)

8,getitemsetitemdelitem

getitemsetitemdelitem这三个函数主要用于序列的索引,切片、以及字典的映射操作,分别表示获取,设置和删除数据。

【示例1】使用getitemsetitemdelitem函数来模拟设计一个字典操作类,实现基本的字典操作功能,如添加元素、访问元素和删除元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Dict:
def __init__(self,**args):
self.__item = args
def __getitem__(self,key):
return self.__item.get(key)
def __setitem__(self,key,value):
if key in self.__item: del self.__item[key]
return self.__item.setdefault(key,value)
def __delitem__(self,key):
return self.__item.pop(key,None)

dict = Dict()
print(dict['a'])
dict['b'] = 'test'
print(dict['b'])
del dict['b']
print(dict['b'])
dict = Dict(a=1,b=2,c=3)
print(dict['a'])
dict['b'] = 'test'
print(dict['b'])
del dict['b']
print(dict['b'])

运行结果:

1
2
3
4
5
6
None
test
None
1
test
None

【提示】

1
在Python2.0中提供了__getslice__(),__setslice__()和__delslice__()3个函数,它们能够让类的实例对象拥有列表的切片功能。Python3.0废除了这3个函数,而是借助slice类整合到了__getitem__、__setitem__和__delitem__中。

【示例2】使用getitem()、setitem()和delitem()来模拟getslice(),setslice()和delslice()函数功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class List:
def __init__(self,*args):
self.__item = list(args)
def __getitem__(self,index):
if isinstance(index,slice):
return self.__item[index.start:index.stop:index.step]
return self.__item
def __setitem__(self,index,value):
if isinstance(index,slice):
self.__item[index.start:index.stop:index.step] = value
return self.__item
def __delitem__(self,index):
if isinstance(index,slice):
del self.__item[index.start:index.stop:index.step]
return self.__item


L = List(1,2,3,4,5,6)
print(L[2:4])
L[-1:5] = [1,2,3]
print(L[::])
del L[3:5]
print(L[::])

当执行切片操作时,getitem(),setitem(),delitem()函数的第2个参数为slice对象,即切片对象,使用该对象的start、stop和step属性可以获取切片的起始下标值,终点下标值和步长。

9,iter

iter函数用于返回迭代器,对于列表、字典、元组等可迭代对象来说,之所以可以进行for循环,是因为类型内部定义了iter函数。

【示例】为Test类定义了iter函数,设计iter函数返回一个可迭代的对象,这样当实例化Test类后,就可以使用for语句迭代实例对象了。

1
2
3
4
5
6
7
8
9
class Test:
def init__(self,sq=[]):
self.sq = sq
def __iter__(self):
return iter(self.sq)

obj = Test([1,2,3,4])
for i in obj:
print(i)

运算结果:

1
2
3
4
1
2
3
4

【提示】

1
iter()函数用来生成迭代器,可以把一个支持迭代的集合对象生成可迭代的对象。

上面的代码等效于下面的代码。

1
2
3
obj = iter([1,2,3,4])
for i in obj:
print(i)

【注意】

1
2
如果没有设计__iter__函数,那么使用for语句迭代Test的实例对象,将会抛出如下所示的错误。
TypeError:'Test' object is not iterable

10,del

del表示析构函数,当实例对象在内存中被释放时,会被自动触发执行。

【示例】在上一小节示例基础上添加析构函数,设计当不需要访问数据库时自动关闭数据库相连。

1
2
3
4
5
6
7
class DB:
def __init__(self,name,passwd):
self.name = name
self.passwd = passwd
self.db = MySQLdb.connect("localhost",name,passwd,"DatabaseName",charset='utf-8')
def __del__(self):
self.__db.close()

【提示】

1
python能够自动管理内存,用户在使用时无须无关内存的分配和释放,Python解释器能够自动执行,所以析构函数的调用是由解释器在进行垃圾回收时自动出发执行的。

11,getattrsetattrdelattr

getattrsetattrdelattr这三个函数主要用于对象的属性操作,分别表示获取、设置和删除属性值。

【注意】通过dict包含的信息进行属性查找,在实例对象以及对应类对象中没有找到指定属性,将调用类的getattr函数,如果没有定义这个函数,将抛出AttributeError异常。因此,getattr是属性查找的最后一步操作。

【示例】演示如何使用getattrsetattrdelattr,为类设置属性操作的基本行为,演示效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student:
def __init__(self,id,name,gender):
self.id = id
self.name = name
self.gender = gender
def __getattr__(self,item):
print('no attribute',item)
return False
def __setattr__(self,key,value):
self.__dict__[key] = value
def __delattr__(self,item):
print('beging remove',item)
self.__dict__.pop(item)
print('remove finished')
student = Student('2019123456','张三','male')
print(student.age)
student.age = 18
print(student.age)
#print(student.age)
print(student.__dict__)
del student.age
print(student.__dict__)

12,案例:重写比较运算符

Python为比较运算符提供了一组魔法方法,当使用比较运算符进行运算时,将触发这些方法的调用。简单的说明如下:

1
2
3
4
5
6
# __lt__(self,other):小于(<)
# __le__(self,other):小于或等于(<+=)
# __gt__(self,other):大于(>)
# __ge__(self,other):大于或等于(>=)
# __eq__(self,other):等于(==)
# __ne__(self,other):不等于(!=)

【示例】本例重写比较运算符,根据句子的长度来判断大小关系,而不是字符的编码顺序,示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Sentence(str):
def __init__(self,a):
if isinstance(a,str):
self.len = len(a)
else:
print('TypeError')
def __gt__(self,other):
if self.len > other.len:
return True
else:
return False
def __ge__(self,other):
if self.len >= other.len:
return True
else:
return False
def __lt__(self,other):
if self.len < other.len:
return True
else:
return False
def __le__(self,other):
if self.len <= other.len:
return True
else:
return False
def __eq__(self,other):
if self.len == other.len:
return True
else:
return False
def __ne__(self,other):
if self.len != other.len:
return True
else:
return False

a = Sentence('Hello world')
b = Sentence('Nice to meet you')
print(a>b)
print(a>=b)
print(a<b)
print(a<=b)
print(a==b)
print(a!=b)

5,继承

继承是面向对象编程的基本特性之一,通过继承不仅可以实现代码重用,而且可以构建类与类之间的关系。

1,定义继承

在Python中,新建的类可以继承自一个或者多个类,被继承的类称为父类、基类或者超类,新建的类称为子类或者派生类。

定义继承的基本语法格式如下:

1
2
3
class 子类(基类列表):
'''帮助信息(可选)'''
类主体

基类列表指定子类要继承的父类,可以是一个或多个,积累之间通过逗号分隔。如果不指定类,则将继承Python对象系统的根类object。

【示例1】创建一个基类Parent及其派生类Son1和Son2。在基类中定义一个字段name用来标识身份,定义一个方法get()访问当前实例的name属性。然后创建两个子类Son1和Son2,设计它们都继承自Parent,拥有get()方法,最后创建Son1和Son2的实例对象,在实例对象上调用get()方法,输出当前实例对象的name属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Parent:
name = "父类"
def get(self):
return self.name
class Son1(Parent):
name = '子类1'
class Son2(Parent):
name = '子类2'

son1 = Son1()
print(son1.get())
son2 = Son2()
print(son2.get())

在Python中,类的继承分为单继承和多继承。单继承就是积累只有一个,如示例1所示。多继承就是基类可以有多个,多个父类通过逗号分隔,如示例2所示。

【示例2】创建两个基类Parent1和Parent2,及其派生类Son。这样派生类Son将继承基类Parent1和Parent2的所有公有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent1:
name = "父类1"
def get(self):
return self.name
class Parent2:
name = "父类2"
def get(self,val):
self.name = val
class Son(Parent1,Parent2):
name = '子类'

son = Son()
print(son.get())
son.set("test")
print(son.get())

2,basebases

在Python中,每个类对象都有basebases属性,他们表示类的基类。如果是单继承,使用base可以获取父类;如果是多继承,使用bases可以获取所有父类,并以元组类型返回。

【示例1】设计一个单继承的示例,然后使用basebases访问基类。

1
2
3
4
5
6
7
class Parent:
name = "父类"
class Son:
name = "子类"

print(Son.__base__)
print(Son.__bases__)

【示例2】设计一个多继承的实例,然后使用basebases访问基类。

1
2
3
4
5
6
7
8
9
class Parent1:
name = "父类1"
class Parent2:
name = "父类2"
class Son(Parent1,Parent2):
name = "子类"

print(Son.__base__)
print(Son.__bases__)

3,接口和抽象类

​ 继承有两种作用:代码重用和接口兼容。在Python中,没有interface关键字,如果要模仿接口的概念,可以定义接口类,声明需要兼容的基类。在接口类中,可以定义一个或多个接口名(函数名),且并为实现接口的功能,子类继承接口类(接口继承),并且实现接口的功能。

​ 接口继承的设计原则:做出一个良好的抽象,这个抽象规定了一个兼容接口,使外部调用者无须关心具体细节,可一视同仁地处理实现了特定接口的所有对象。在程序设计中这被称为归一化。

【示例1】下面示例设计一个简单地类,规定了统一的方法,在方法中通过pass,省略了具体的功能,这样的方法被称为抽象方法。当接口被继承时,由派生类实现部分或者全部功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Life:
def eat(self):
pass
def sleep(self):
pass
def play(self):
pass

class People(Life):
def eat(self):
print("吃饭")
def sleep(self):
print("睡觉")
def play(self):
print("打豆豆")

ren = People()
ren.eat()
ren.sleep()
ren.play()

抽象类是一个特殊的类,它基于类抽象而来,与普通类的不同之处;抽象类中包含抽象方法,没有具体功能,不能被实例化,只能被继承,而子类必须实现抽象方法。

在Python中,通过第三方模块实现对象类的支持,如abc模块。抽象类常用于协调工作。

【提示】

1
2
3
4
抽象类与接口有点类似,但本质不同,简单比较如下。
#抽象类是一组类的相似性,包括数据属性和函数属性,而接口只强调函数属性的相似性。
#在继承抽象类的时候,应尽量避免多继承;而在继承接口的时候,鼓励多继承接口,使用多个专门的接口,而不使用单一的总接口。
#在抽象类中,可以对一些抽象方法做出基础实现;在接口类中,任何方法都只是一种规范,具体的功能需要子类实现。

【示例2】利用第三方abc模块在Python中定义抽象类

【注意】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在Python中,不要以有无执行提来区分是否为抽象,而是根据是否有@abc.abstractmethod装饰器作为标准。
import abc
class InMa(metaclass=abc.ABCMeta):
@abc.abstractmethod
def login(self):
pass
@abc.abstractmethod
def regist(self):
pass
class Login(InMa):
def login(self,name,pwd):
if self.name == "name" and self.password == "pwd":
print("恭喜登录成功")
else:
print("登录失败")
class Regist(Login):
def __init__(self,name,pwd):
self.name = name
self.password = pwd
def regist(self):
print("恭喜注册成功")
print("username:",self.name)
print("password:",self.password)

#实例对象
people = Regist("Jane","qqq")
people.regist()
people.login("Jane","qqq")

运算结果:

1
2
3
4
恭喜注册成功
username: Jane
password: qqq
登录失败

4,类的组合

除了继承之外,代码重用的另一种方式就是组合。组合是指在一个类中使用另一个类的对象作为数据属性,也称为类的组合。

【示例1】简单演示类的组合形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Teacher:
def __init__(self,name,gender,course):
self.name = name
self.gender = gender
self.course = course
class Course:
def __init__(self,name,price,period):
self.name = name
self.price = price
self.period = period
course_obj = Course('Python',15800,'5months')
t_c = Teacher('egon','male',course_obj)
print(t_c.course.name)

运算结果:

1
python

通过上面的代码可以看到,组合与继承都能够有效利用已有类资源的重要方式,但是二者使用方式不同。

组合与继承都能够有效利用已有类的资源,但是二者使用方法不同。

1
2
#通过继承建立派生类与基类之间的关系,这是一种“是”的关系,如教授是教师,教授属于教师职业的一种。
#使用组合建立类与组合类之间的关系,这是一种“有”的关系,如教授有生日,教授有课程安排等,教授与生日,课程等类有关联,但不是从属关系。

【示例2】下面示例使用组合方式演示教授有生日,教授教python课程的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class BirthDate:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
class Course:
def __init__(self,name,price,period):
self.name = name
self.price = price
self.period = period
class Teacher:
def __init__(self,name,gender):
self.name = name
self.gender = gender
def teach(self):
print('teaching')
class Professor(Teacher):
def __init__(self,name,gender,birth,course):
Teacher.__init__(self,name,gender)
self.birth = birth
self.course = course

P1 = Professor('egon','python',BirthDate('1998','1','20'),Course('python','58000','4 months'))
print(P1.birth.year,P1.birth.month,P1.birth.day)
print(P1.course.name,P1.course.price,P1.course.period)

运算结果:

1
2
1998 1 20
python 58000 4 months

5,方法重写与扩展

基类的成员都会被派生类继承,当基类中的某个方法不完全适用于派生类时,就需要在派生类中重写父类的这个方法,即当子类定义了一个和超类相同的名字时,那么子类的这个方法将覆盖掉基类同名的方法。

【示例1】定义两个类:Bird类定义了鸟的基本功能:吃,SongBird是Bird的子类,SongBird会唱歌。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Bird:
def eat(self):
print('Bird,吃东西...')
class SongBird:
def eat(self):
print('SongBird,吃东西...')
def song(self):
print('SongBird,唱歌...')
bird = Bird()
SongBird = SongBird()
bird.eat()
SongBird.eat()
SongBird.song()

【示例2】定义3个类:Fruit、Apple和Orange,其中Fruit是基类,Apple和Orange是派生类,其中Apple继承了Fruit基类的harvest方法,而Orange重写了harvest方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Fruit:
color = '绿色'
def harvest(self,color):
print(f"现在是{color}")
print(f"初始是{Fruit.color}")
class Apple(Fruit):
color = '红色'
def __init__(self):
print("苹果")
class Orange(Fruit):
color = '橙色'
def __init__(self):
print("橙色")
def harvest(self,color):
print(f"现在是{color}")
print(f"初始是{Fruit.color}")

apple = Apple()
apple.harvest(apple.color)

orange = Orange()
orange.harvest(orange.color)

运行结果:

1
2
3
4
5
6
苹果
现在是红色
初始是绿色
橙色
现在是橙色
初始是绿色

6,案例:自行车类的继承

本案例设计一个自行车Bike类,包含品牌字段、颜色字段和骑行功能,然后再派生出以下子类:折叠自行车类,包含骑行功能;电动自行车类,包含电池字段、骑行功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Bike:
def __init__(self,brand,color):
self.brand = brand
self.color = color
@property
def brand(self):
return self.oral_brand
@brand.setter
def brand(self,b):
self.oral_brand = b
@property
def color(self):
return self.oral_color
@color_setter
def color(self,c)
self.oral_brand = c
def riding(self):
print("Bike拥有骑行功能")
class Folding_Bike(Bike):
def __init__(self,brand,color):
super().__init__(brand,color)
def riding(self):
print('折叠自行车:{}{}可以折叠'.format(self.brand,self.color))
class Electric_Bike(Bike):
def __init__(self,brand,color,battery):
super().__init__(brand,color):
self.oral_battery = battery
@property
def battery(self):
return self.oral_battery = battery
@battery_setter
def color(self,b):
self oral_battery = b
def riding(self):
print('电动车{}{}使用{}电池'.format(self.brand,self.color,self.battery))

f_bike = Folding_Bike('捷安特','白色')
f_bike.riding()
f_bike.color = '白色'
f_bike.riding()
e_bike = Electric('宝马','曜石黑','55V20Ah')
e_bike.riding()
e_bike.battery = '60V20Ah'
e_bike.riding()

运行结果:

1
2
3
4
折叠自行车:捷安特白色可以折叠
折叠自行车:捷安特白色可以折叠
电动车宝马55V20Ah使用55V20Ah电池
电动车宝马55V20Ah使用55V20Ah电池

7,案例:抽象类继承和接口整合

本例设计机动车基类,包含刹车功能,定义两个抽象类,包括收费类和空调类。采用鸭子模型提供了3个接口函数,包括刹车函数,收费函数和空调函数。派生出3个子类。

1
2
3
#公交车类,包含刹车,收费功能
#出租车类,包括刹车,收费,空调功能。
#电影院类,包含收费,空调功能

案例代码演示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import abc
class Vehicle:
def stop(self):
print('汽车有刹车功能!')
class Charge(metaclass=abc.ABCMeta):
@abc.abstractmethod
def charge(self):
pass
class Air_condition(metaclass=abc.ABCMeta):
@abc.abstractmethod
def air_condition(self):
pass
class Bus(Vehicle,Charge):
def charge(self):
print('公交车有收费功能!')
def stop(self):
print('公交车有刹车功能!')
class Taxi(Vehicle,Charge,Air_condition):
def charge(self):
print('出租车有收费功能!')
def stop(self):
print('出租车有刹车功能!')
def air_condition(self):
print('出租车有空调功能!')
class Cinema(Charge,Air_condition):
def charge(self):
print('电影院有收费功能!')
def air_condition(self):
print('电影院有空调功能!')
def vehicle_stop(vehicle):
vehicle.stop()
def charge_cost(charge):
charge.charge()
def air(air_condition):
air_condition.air_condition()

bus = Bus()
vehicle_stop(bus)
charge_cost(bus)
taxi = Taxi()
vehicle_stop(taxi)
charge_cost(taxi)
air(taxi)
cinema = Cinema()
charge_cost(cinema)
air(cinema)

运行结果:

1
2
3
4
5
6
7
公交车有刹车功能!
公交车有收费功能!
出租车有刹车功能!
出租车有收费功能!
出租车有空调功能!
电影院有收费功能!
电影院有空调功能!

6,元类

1,认识元类

在Python中,元类(metaclass)就是创建类,即类的模板。例如,当类A被实例化返回类B,那么类A就是元类。其用法形式如下:

1
2
B = A()
object = B()

在Python中 ,一切尽为对象,如int,string,function,class,而所有对象都是通过类来创建的。任何对象都可以通过class访问创建自身的类。同理,一个类可以通过class访问创建自身的元类。

代码所示如下:

1
B.__class__== A

【示例】下面示例使用type元类创建一个类,然后使用class访问type。

1
2
Class = type(None)
print(Class.__class__)

通过上面实例可以看到,type()构造函数可以创建类,这是因为type是一个类。

【提示】

1
实际上type是Python用来创建所有类的元组,及元类的根类。

2,使用type()创建类

在Python中,类也是一个对象,当使用class关键字创建类时,Python会自动创建对应的类对象。使用type()函数可以直接创建类对象,语法格式如下:

1
class type(name,bases,dict)

参数说明如下:

1
2
3
#name:类的名称
#bases:基类组成的元组
#dict:类的属性字典,包括类中定义的成员变量,以及映射的值。

如果只有一个参数,则返回参数对象的类型;如果有3个参数,则返回新的类型对象。

【示例1】使用type()构造函数创建一个空类

1
2
Test = type("Test",(),())
print(Test)

输出为:

1

在上面的代码中,type()函数接收的第一个参数Test是该类的类名,同时使用Test作为存储该类对象的引用变量。变量名和类名可以不同,不过建议采用相同的名称,避免代码复杂化。

【示例2】使用type()构造函数还可以添加类的成员,以及继承的基类。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent
def get(self):
return self.name
def echo(self):
print(self.name)

Test = type("Test",(Parent,),{"name":"test","echo":echo})

print(Test)
print(hasattr(Test,'echo'))
test = Test()
test.echo()
print(test.get())

可以先创建一个类,然后根据需要再动态添加成员。例如,针对上面的Test类,可以继续添加如下普通方法。

1
2
3
4
5
6
7
def set(self,val):
self.name = val
Test.set = set
print(hasattr(Test,'get'))
test = Test()
test.set("new")
print(test.name)

3,使用metaclass声明元类

在定义类的时候,可以使用metaclass关键字声明当前类的元组。语法格式格式:

1
2
class 类名(metaclass = callable):
#执行代码

如果指定了metaclass,Python将使用callable来生成当前类。其中callable表示一个可调用的对象,如函数、方法、lambda函数表达式,类对象,以及实现了call方法的实例化。

【提示】

1
调用类对象[即类的实例化]实际上就是调用__new__方法,调用实例对象实际上就是调用__call__方法。

“class 类名(metaclass=callable)”的运行逻辑如下:

1
类名 = callable(类名,基类元组,类成员字典)

3个参数与type()构造函数的参数意义一一对应。在callable函数体内,可以根据需要修改3个参数,然后传递给type(),调用type(),并返回创建新的类。

【示例一】:演示使用metaclass关键字初始化元类的基本用法

1
2
3
4
5
6
7
8
9
10
11
12
class Meda(type):
def __new__(cls,name,bases,attr):
attr['info'] = '由Mada元类创建'
return super().__new__(cls,name,bases,attr)

class Test(metaclass = Meda):
def get(self):
return self.info

test = Test()
print(test.info)
print(test.get())

在上面的实例中,首先创建一个类Meda,实现元类的功能,并继承type根类。在这个类的new方法中传入4个参数。

当实例化type类时,也就是是调用该类的new方法生成新类,其中第一个参数cls为默认参数,表示当前类,后面3个参数对应type()函数的参数。因此,上面的写法与直接调用type()函数生成类的逻辑是完全一样的,只不过展现的形式不同。

在新建Test类的时候,使用metaclass = Meda指定该类的元类,虽然使用class关键字定义了本类,但是在实际执行的时候,这个类是以metaclass指定的元类来创建的。

【示例2】Python的原生list不支持add()方法,本例通过元类为新建的列表类型添加该方法,以方便快速添加元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ListMeta(type):
def __new__(cls,name,bases,attr):
#在类属性中添加add函数,通过匿名函数映射到append函数上
attrs['add'] = lambda self,value:self.append(value)
return super().__new__(cls,name,bases,attrs)

class MyList(list,metaclass=ListMeta):
pass

lt = MyList()
lt.add(1)
lt.add(2)
lt.add(3)

4,自定义元组

如果一个类没有声明自己的元类,默认元类为type,当然用户也可以通过继承type来自定义元类。

元类可以用来设计复杂的逻辑操作,如自省、修改继承、以及改变类的默认属性等。但是metaclass本身比较简单,主要作用如下:

1
2
#影响类的初始化过程
#对生成的类进行动态修改

自定义元类的方法有两种。

方法一,使用类的实例化函数构造新类

【示例1】演示使用类的new方法创建新类的基本模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Meda(type):
def __new__(cls,*arg):
print("__new__被执行")
return super().__new__(cls,*arg)

def __init__(self,*arg):
print("__init__被执行")
def __call__(self):
print("__call__被执行")

class Test(metaclass = Meda):
pass

运行上面示例代码,会在控制台输出下面信息,说明当为一个类设置元类时,实际上就是调用元类,实例化元类并返回新类。

1
2
__new__被执行
__init__被执行

而实例化Test类时,在控制台输出下面信息

1
__call__被执行

方法二:使用函数等可调用对象返回一个类

【实例2】下面示例设计通过元类修改当前类的属性名为大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def upper_attr(class_name,class_parents,class_attr):
uppercase_attr = {}
for name,val in class_attr,items():
if name.startswith('__')
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return type(class_name,class_parents,uppercase_attr)

class Test(metaclass = upper_attr):
a = '0'
print(hasattr(Test,'a'))
print(hasattr(Test,'A'))
test = Test()
print(test.A)

5,案例:创建Employee类

本例通过元类的方式创建一个雇员类,类成员通过字典结构提前定义,最后使用type()函数把他们构造为一个完整的新类。示例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def __init__(self,name,telphone,gender):
self.name = name
self.telphone = telphone
self.gender = gender
def say_hello(self):
print('Hello, I am {}, I come from {}, my phone is {}'.format(self.name,country,self.telphone))
return None
class_name = 'Employee'
class_bases = (object,)
country = 'China'
class_dic = {
'country':country,
'__init__':__init__,
'say_hello':say_hello,
}
Employee = type(class_name,class_bases,class_dic)
employee = type('zhangsan','15811112222','male')
print(Employee)
print(isinstance(Employee,type))
print(employee.say_hello())
print(employee,country)


执行程序,输出结果如下:

1
2
3
4
5
<class '__main__.Employee'>
True
Hello, I am zhangsan, I come from China, my phone is 15811112222
None
China

7,案例实战

1,迭代器

迭代,顾名思义,就是不停地代换。在程序设计中,表示同一个变量,使用不同的值来替代运算。

1,可迭代对象

可迭代对象表示一个数据集合对象,且可以通过iter()方法或者getitem()方法访问对象中的元素,他们是Python实现外部访问可迭代对象内部数据的通用接口。这两个方法的具体作用如下。

1
2
#__iter__():可以使用for循环遍历对象
#__getitem__():可以通过“对象[index]”的方式访问元素。

在Python中,迭代是通过for语句来完成的。凡是可迭代对象都可以直接使用for循环访问,for语句其实做了以下两件事:

1
2
#调用__iter__()方法获取一个迭代器
#循环调用__next__()方法访问元素。

可迭代对象包括两类:

1
2
#集合数据类型,如list(列表),set(集合),dict(字典),tuple(元组),str(字符串)等
#迭代器和生成器。

【提示】:

1
2
3
使用collection模块的Iterbale类型可以验证一个对象是否为可迭代对象:
from collection,abc import Iterable
print(isinstance('',Iterable))

2,迭代器

迭代器(iterator)是一个可以记住遍历位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前访问,不会往回访问。

1
2
#使用iter()函数可以把一个数据集合随想转换为迭代器对象。
#使用next()函数可以不断访问迭代器对象中的下一个元素。

【提示】

1
通常集合类型的数据会把所有的元素都存储在内存中,而迭代器和生成器仅在读取每个元素时,才动态生成。因此,当创建一个包含大容量的数据对象时,使用迭代器会更加高效。

【示例1】简单演示如何把列表转换为迭代器,然后读取每个元素的值。

1
2
3
4
5
6
list = [1,2,3,4]
it = iter(list)
print(next(it),end=" ")
print(next(it),end=" ")
print(next(it),end=" ")
print(next(it),end=" ")

输出结果为:

1
1,2,3,4

【示例2】迭代器对象可以使用for语句进行遍历。针对示例1,可以使用如下代码遍历元素的值。

1
2
3
4
list = [1,2,3,4]
it = iter(list)
for x in it:
print(x,end=" ")

【提示】

凡是可作用于for循环的对象都是Iterable(可迭代)类型;凡是可作用于next()函数的对象都是Iterator(迭代器)类型。例如:

1
2
3
4
5
6
7
8
from collections.abc import Iterable
from collections.abc import Iterator
list = [1,2,3,4]
it = iter(list)
print(isinstance(list,Iterable))
print(isinstance(it,Iterable))
print(isinstance(list,Iterator))
print(isinstance(it,Iterator))

运算结果:

1
2
3
4
True	#说明list是可迭代对象
True #说明it是可迭代对象
False #说明list是不可迭代对象
True #说明it是可迭代对象

3,自定义迭代器

自定义迭代器类型,需要在类中实现两个魔术方法:init()和next()。

iter()方法返回一个特殊的迭代器对象,这个迭代器对象实现了next()方法,通过StopIteration异常标识迭代的终止。

next()方法会返回下一个迭代器。

【示例3】本示例将创造一个返回数字的迭代器,初始值为1,逐步递增1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Add:
def __iter__(self):
self.a = 1
return self
def __next__(self):
x = self.a
self.a += 1
return x

add = Add()
myiter = iter(add)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

在上面的实例中,如果不断调用next()函数,将会连续输出递增值。如果要限定输出的次数,可以使用StopIteration异常。

StopIteration异常用于标识迭代的完成,防止出现无限循环。在next()方法中可以设置在完成指定的循环次数后触发Stopiteration异常来结束迭代。

【示例4】设计在20次迭代后停止输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Add:
def __init__(self):
self.a = a
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration

add = Add()
myiter = iter(add)
for x in myiter:
print(x,end=" ")

输出结果:

1

【示例5】创建一个迭代器,它既可以做为迭代器的集合对象,也可以作为可迭代的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MyIterator():
def __init__(self):
self.list = []
self.position = 0
def __iter__(self):
return self
def __next__(self):
if self.position < len(self.list):
item = self.list[self.position]
self.position += 1
return item
else:
raise StopIteration
def add(self,name):
self.list.append(name)

#a对象既是一个迭代器,也是一个可迭代对象
a = MyIterator()
a.add("张三")
a.add("李四")
a.add("王五")
print(next(a))
print(next(a))
print(next(a))

a = MyIterator()
a.add(1)
a.add(2)
a.add(3)
iterator = iter(b)
print(next(iterator))
print(next(iterator))
print(next(iterator))

运算结果:

1
2
3
4
5
6
张三
李四
王五
1
2
3

2,生成器

​ 在Python中一边循环一边计算的机制称为生成器。对于集合类型来说,他的所有数据都存储在内存中,如果有海量的数据,将会非常占用内存,且操作效率也不是很高。

​ 例如,如果仅仅需要访问前面几个元素,那么后面绝大多数元素占用的空间都是不必要的。如果每个元素的值可以按照某种算法生成,那么就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的数据集合,从而节省大量的空间。因此,生成器仅仅保存了一套生成数值的算法,并且没有让这个算法现在就开始执行,而是在需要时才开始计算并返回每一个值。

​ 生成器的工作原理:生成器是一种特殊的迭代器,作为可迭代的对象,它能够迭代的关键是因为拥有一个next()方法,如果重复调用next()方法,就可以获取每个元素的值,直到捕获一个异常。

创建生成器的方法有以下两种

通过推导式生成。

【示例1】使用推导式生成一个生成器。同时与列表推导式进行比较,比较它们在语法形式上的异同。

1
2
3
4
L = [x * x for x in range(10)]
g = (x * x for x in range(10))
print(L)
print(g)

从上面的代码可以看到,只要把一个列表推导式的[]改成()就可以创建一个生成器。

使用yield关键字生成

如果一个函数中包括yield关键字,那么这个函数就不再是一个普通的函数,而是一个生成器函数。调用函数就可以创建一个生成器对象。

【提示】

1
在函数体中,yield相当于return,都能够返回其后面表达式的值。但是,yield不是完全等价于return,return是直接结束函数的调用,而yield是挂起运行的函数,并记住返回的位置,当再次调用__next__函数时,将从yield所在位置的下一条语句开始执行。

【示例2】使用yield关键字创建一个生成器。

1
2
3
4
5
6
7
8
9
def test(n):
for i in range(n):
yield i*i

g = test(5)
print(next(g))
print(g.__next__())
for i in g:
print(i)

输出结果:

1
2
3
4
5
0
1
4
9
16

生成器对象还有一个send()方法,该方法能够向生成器内部投射一个值,作为yield表达式整体运行的结果,使用send()

方法可以强行修改上一次yield表达式的值。

【示例4】设计当迭代生成器过程中,使用send()方法中途改变迭代的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
def down(n):
while n >= 0:
m = yield n
if m:
n = m
else:
n -= 1

d = down(5)
for i in d:
print(i)
if i == 5:
d.send(3)

结果如下:

1
2
3
4
5
2
1
0

如果不设置if i == 5:d.send(3),则将连续打印递减正整数:5,4,3,2,1,0

【示例5】使用列表推导式可以输出斐波那契数列的前N个数字,主要用到元组的多重赋值:a,b = b,a+b,其实相当于t = a+b,a=b,b=t,所以不必定义临时变量t,就可以输出斐波那契数列的前n个数字。列表推导式是一次生成数列中所有求值,会占用大量内容。现在,使用生成器函数,仅存储计算方法,这样就会节省大量时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def fib(max):
n,a,b = 0,0,1
while n < max:
yield b
a,b = b,a+b
n = n+1
return 'done'
g = fib(6)
while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print(e.value)
break

运算结果:

1
2
3
4
5
6
7
1
1
2
3
5
8
Dones

3,静态字段和类方法

本例编写一个Shoes类,练习使用静态字段保存公共信息,记录鞋子实例数,使用类方法显示类中鞋子实例的总数,并通过普通方法修改信息,以便核减鞋子数量,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Shoes:
numbers = 0
def __init__(self,name,brand):
self.name = name
self.brand = brand
Shoes.numbers += 1
def useless(self):
Shoes.numbers -= 1
if Shoes.numbers == 0:
print('{} was the last one'.format(self.name))
else:
print('you have (:d) shoes'.format(Shoes.numbers))
def print_Shoes(self):
print('you got {:s} {:s}'.format(self.name,self.brand))

@classmethod
def how_maney(cls):
print('you have {:d} shoes'.format(clf.numbers))

shoes1 = Shoes('三叶草','ADIDAS')
shoes1.print_Shoes()
shose1.how_maney()

shoes2 = Shoes('AJ','NIKE')
shoes2.print_Shoes()
shose2.how_maney()

shoes3 = Shoes('安踏','NATA')
shoes3.print_Shoes()
shose3.how_maney()


shoes1.useless()
shoes2.useless()
Shoes.print_Shoes

4,向量加减运算

​ 提供了一组与运算符相关的魔法方法,其中包括加,减,乘和除等基本四则运算,方便用户根据运算对象的特殊需求,进行个性化定制,本节将重点介绍四则运算符的魔法方法,简单说明如下。

1
2
3
4
5
6
# __add__(self,other):相加(+)
# __sub__(self,other):相减(-)
# __mul__(self,other):相乘(*)
# __truediv(self,other):真相除(/)
# __floordiv(self,other):整数除法(//)
# __mod__(self,other):取余运算(%)

本案例编写一个向量Vector类,重写加法和减法,实现向量之间的加减运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return 'Vector(%d,%d)'%(self.x,self.y)
def __add__(self,other):
return Vector(self.x + other.x,self.y + other.y)
def __sub__(self,other):
return Vector(self.x - other.x,self.y - other.y)

vector1 = Vector(3,5)
vector2 = Vector(4,-6)
print(vector1,'+',vector2,'=',vector1 + vector2)
print(vector1,'-',vector2,'=',vector1 - vector2)

运行结果:

1
2
Vector(3,5) + Vector(4,-6) = Vector(7,-1) 
Vector(3,5) - Vector(4,-6) = Vector(-1,11)

5,点与矩形

本案例编写一个矩形类Rect,包含宽度width和高度height两个属性,矩形的面积area()和矩形的周长perimeter()两个方法,再编写一个具有位置参数的矩型类PlainRect,继承Rect类,包含两个坐标属性和一个判点是否在矩形内的方法,其中确定位置用左上角的矩形坐标表示,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Rect:
def __init__(self,width,height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class PlainRect(Rect):
def __init__(self,width,height,startX,startY):
super().__init__(width,height)
self.startX = startX
self.startY = startY
def isInside(self,x,y):
if (x>=self.startX and x<=(self.startX + self.width)) and (y>=self.startY and y<=(self.startY + self.height)):
return True
else:
return False
plainRect = PlainRect(10,5,10,10)
print('矩形的面积:',plainRect.area())
print('矩形的周长:',plainRect.perimeter())
if plainRect.isInside(15,11):
print('点在矩形内')
else:
print('点不在矩形内')

运行结果:

1
2
3
矩形的面积: 50
矩形的周长: 30
点在矩形内