文章目录
面向过程编程:将程序分解为一系列的步骤,每个步骤都是一个函数,以事物发生过程为主要目标进行编程(什么正在发生)。程序的状态通常由全局变量来维护,而函数则被用来封装可重用的代码块。
函数式编程,函数式的思想:函数内部需要的数据均通过参数的形式传递。强调函数的纯度和不可变性。
面向对象编程,面向对象的思想:将数据和操作数据的方法组合在一起,形成一个对象。每个对象都有自己的状态(data)和行为(methods),并且可以通过调用其他对象的方法来改变自身状态或执行其他操作。面向对象编程的核心概念包括类、对象、继承、封装和多态性。
6.1 类与对象
类与对象的关系即模板与产品:模板只能有一个,但对象可以有很多个
面向对象实现功能的步骤:
- 定义类,在类中定义方法,在方法中去实现具体的功能。
- 实例化类给一个对象,通过对象去调用并执行方法。
名词解释
- 类是对一些具有相同属性特征和行为的事物的分类,它是抽象的。
- 属性:属性是指类或对象中的变量,用于存储对象的特征状态,比如年龄、身高、性别、姓名等都叫做属性。
- 方法:用来描述事物的行为,用函数来完成这些行为。比如说话、走路、吃饭等
- 实例(对象):一个对象即是一个类的实例化后实例,是具体的。一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象可以有不同的属性,能调用类中共同的方法。
- 实例化:把一个类转变为一个对象的过程就叫实例化。
1. 类
我们可以使用关键字 class
定义类,关键字后面紧跟类的名称、分号和类的实现。
1. 类名称首字母大写&驼峰式命名
2. 在类种编写的函数称为方法,每个方法的第一个参数是self(类的方法与普通的函数唯一区别)
3. py3之后默认类都继承object,
旧式类:
class Ab Doct:
pass
新式类:
class Ab Doct(object):
pass
2. 对象
对象 = 类() ,基于类实例化出来”一块内存“,默认里面没有数据;
__init__
初始化方法(也称构造方法),在类实例化过程中会自动执行,可以在内存中初始化一些属性数据。
- 不需要初始化的话,可以不写__init__方法,因为解释器会自动生成一个__init__方法来执行。
- 实例化时,先调用了__new__(cls)方法,创建并返回一个类(cls)的新实例对象,传递给构造方法。
- 封装,就是使用构造方法将属性数据封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容
class Stu(object):
def __new__(cls, *args, **kwargs): # __new__方法的第一个参数是类本身,后面的参数是传递给构造方法的参数。
print('no.1')
obj = super().__new__(cls) # 重写需要先调用实现过程,并返回一个类的实例
return obj
def __init__(self): # 第一个参数为self的实例方法,实例化一个新对象时,会自动调用这个方法。全名initialize。
print('no.2')
s1 = Stu()
# no.1
# no.2
3. 类对象
在python中,一切都是对象,当我们定义了一个类,这个类也是对象,叫做类对象。
- 类对象是一个类的实例,它是用来创建对象的模板。类对象可以访问类属性和方法,也可以被用来创建实例对象。
- 对象是类对象的实例,它是由类对象创建出来的。对象可以访问实例属性和方法,也可以访问类属性和方法
- 类对象也具有id,type,value三大特点。类的类型是“class type”
class Stu():
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))
# <class '__main__.Stu'> and id is: 2150663355640 type is: <class 'type'>
# <class '__main__.Stu'> and id is: 2150663355640 type is: <class 'type'>
6.2 类的成员
在 Python 中,面向对象编程中的成员包括属性和方法。
- 属性是指类或对象中的变量,而方法则是类或对象中的函数。在类中定义的属性和方法可以被该类的所有实例对象所共享。
- 属性和方法可以使用点运算符来访问,语法形式为 对象名.属性名 和 对象名.方法名()。
- Python 还支持访问控制机制,即通过使用下划线来表示成员的可见性,例如单下划线表示该成员是受保护的,双下划线表示该成员是私有的。
1. 类属性与实例属性
- 类属性:定义在类中,但不属于任何实例对象的属性。也叫类变量,属于类对象所有,可以被所有该类的实例对象所共享,可以通过类名直接访问和修改。
- 实例属性:加了self的属性,它隶属于对象而不是类(这些属性是放在实例对象的内存空间里的,而不是类对象的内存空间里),能被不同的对象赋值,对象不同,self指向的内存地址也不同。这个对象的所有方法都可以访问和使用本对象的实例属性。实例属性一般定义在__init__方法里,也可以定义在其他方法里。
class Student():
num = 10 # 定义了一个属性,没有加self,也没有放在方法里边,他是类属性(类变量),属于Student这个类
def __init__(self, name, age): # name,和age两个和普通的函数形参变量一样,用来接收参数。方法执行完被清空。
self.name = name # 加了self,说明这个属性对象可以拿去使用,赋值后放在实例对象的内存空间里。供对象调用
self.age = age
print(self.num) # 当对类变量只做访问不做修改的话,也可以使用self.变量名来访问。
def showinfo(self):
gender = 'male' # 这个变量没加self,就是一普通局部变量,showinfo方法执行完毕,他就没了
print(gender)
print(self.age,self.name) # 调用属性,用self来确定到底是哪一个对象的age属性
# 创建了Student类的两个实例对象stu1和stu2,自动初始化, 需要传入两个属于stu1这个对象的特性属性
stu1 = Student(name='xiaoming', age=12) # 10
# 类创建的对象也拥有类变量的使用权,但是没有修改权,一旦产生同名变量,则会隐藏类变量
print(stu1.num) # 10
# 类属性的修改需要直接基于类,用s1.num会创建一个实例属性。
Student.num = 50
stu2 = Student('lihua', 18) # 50
# 因为不同的对象都会享有他们类的的方法,属性各自赋值,现在测试一下
stu1.showinfo()
# male
# 12 xiaoming
stu2.showinfo()
# male
# 18 lihua
#果然都能调用showinfo方法,打印了male,但是name和age各是各的。
2.实例方法
Python 严格要求实例方法需要有实例才能被调用,这种限制其实就是 Python 所谓的绑定概念。
实例方法是属于类对象的,也就是说实例方法包含在类对象的内存空间里。通过对象名和点号来访问。
class Stu:
def info_age(self, age): # 实例方法,第一个参数是self,可被此类所有对象共享
self.age = age
def show_age(self):
print(self.age) # 可以访问其他方法里定义的实例属性,因为他和我同属于一个对象
s1 = Stu()
Stu.info_age(s1, 20)
Stu.show_age(s1)
# 常规写成下边两行的形式,但python解释器其实以上面两句代码形式执行。
s1.info_age(18) # 将类对象s1的年龄修改成了18
s1.show_age()
# 20
# 18
3. 方法装饰器
Python内置了两个装饰器函数,使用它们可以把类中函数转换为专用类方法或静态方法。
- classmethod:装饰为类方法。对于类方法来说,习惯上使用cls设置第一个形参,当然也可以使用其他名称。
- staticmethod:装饰为静态方法。无默认实参,如果要在静态方法中访问类的属性,只能通过引用类对象来实现。
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
静态方法与普通函数没有本质区别,只是放的位置有差异。普通函数定义在模块级别。静态方法本质上就是:普通函数放在类里边了,也没有类方法的cls参数。静态方法通常用于执行与类相关的操作,例如创建和销毁对象、获取类信息等。
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
实例方法、类方法和静态方法的区别
- 实例对象可以调用3种方法,类对象只能调用类方法和静态方法。
- 类对象调用实例方法时,实例方法变为普通函数,将失去默认的上下文运行环境。实例方法的上下文运行环境是实例对象,而类方法的上下文运行环境是类对象。
- 访问属性的不同,实例属性只有实例方法可以访问。实例方法也能使用类属性,但是不能修改。静态方法只能通过参数或者类对象间接访问类属性。
3.属性装饰器
静态属性存在一个缺陷:无法对用户的访问进行监控。例如,设置price属性,写入时要求必须输入数字,读取时显示两位小数。
Python内置了property装饰器函数,使用该装饰器可以把一个普通函数转换为函数式属性。这样通过函数的行为对用户的访问进行监控,避免乱操作。
属性的访问包括:读、写、删,对应的装饰器为:@property、@方法名.setter、@方法名.deleter。
在函数的上一行添加@property装饰器,就可以定义函数式属性。在函数式属性中,第一个实参自动被设置为实例对象,一般以self作为形参名,也可以使用其他名称。
当访问函数式属性时,与静态属性的用法相同,使用点语法即可,不需要使用小括号调用属性函数。这种简化的语法形式符合属性的习惯用法。
【示例】设计一个商品报价类,初始化参数为原价和折扣,然后可以读取商品实际价格,也可以修改商品原价,或者删除商品的价格属性。
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()函数构造属性可以快速封装,语法格式如下:
class property([fget[, fset[, fdel[, doc]]]])
参数说明如下。
- fget:获取属性值的实例方法。
- fset:设置属性值的实例方法。
- fdel:删除属性值的实例方法。
- doc:属性描述信息。
class Goods(object):
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 ) # 不存在,将抛出异常
5.一些相关的内置函数(BIF)
issubclass(class, classinfo)
方法用于判断参数 class 是否是类型参数 classinfo 的子类。- 一个类被认为是其自身的子类。
classinfo
可以是类对象的元组,只要class是其中任何一个候选类的子类,则返回True
。
class A:
pass
class B(A):
pass
print(issubclass(B, A)) # True
print(issubclass(B, B)) # True
print(issubclass(A, B)) # False
print(issubclass(B, object)) # True
isinstance(object, classinfo)
方法用于判断一个对象是否是一个已知的类型,类似type()
。type()
不会认为子类是一种父类类型,不考虑继承关系。isinstance()
会认为子类是一种父类类型,考虑继承关系。- 如果第一个参数不是对象,则永远返回
False
。 - 如果第二个参数不是类或者由类对象组成的元组,会抛出一个
TypeError
异常。
【例子】
a = 2
print(isinstance(a, int)) # True
print(isinstance(a, str)) # False
print(isinstance(a, (str, int, list))) # True
class A:
pass
class B(A):
pass
print(isinstance(A(), A)) # True
print(type(A()) == A) # True
print(isinstance(B(), A)) # True
print(type(B()) == A) # False
hasattr(object, name)
用于判断对象是否包含对应的属性。True Falsegetattr(object, name[, default])
用于返回一个对象属性值。 不存在报错。setattr(object, name, value)
对应函数getattr()
,用于设置属性值,该属性不一定是存在的。delattr(object, name)
用于删除属性。不存在报错。class property([fget[, fset[, fdel[, doc]]]])
用于在新式类中返回属性值。
class A(object):
bar = 1
def set(self, a, b):
x = a
a = b
b = x
print(a, b)
a = A()
print(hasattr(a, 'bar')) # True
print(getattr(a, 'bar')) # 1
print(getattr(a, 'bar2', 3)) # 3
c = getattr(a, 'set')(a='1', b='2') # 2 1
setattr(a, "age", 28)
print(a.age) # 28
delattr(a, "age")
6.再识数据类型
在初步了解面向对象之后,再来看看我们之前学习的:str、list、dict等数据类型,他们其实都一个类,根据类可以创建不同类的对象。
- dir(obj)函数可以获得obj对象的属性;
- obj.dict:对象的属性字典;
isinstance(对象,类型):用来判断对象是不是某种类型的实例对象;
print(dir(1))
print(isinstance(1, int))
# ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
# True
6.3 魔术方法
Python内置了一组变量和函数,习惯上称为魔法变量和魔术方法。为了方便识别,这些内置变量和函数名称首尾都带有双下画线。
1.公有和私有
在 Python 中定义私有变量只需要在变量名或函数名前加上“__”两个下划线,那么这个函数或变量就会为私有的了。
【例子】类的私有属性实例
class JustCounter:
__secretCount = 0 # 私有变量
publicCount = 0 # 公开变量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print(self.__secretCount)
counter = JustCounter()
counter.count() # 1
counter.count() # 2
print(counter.publicCount) # 2
# Python的私有为伪私有
print(counter._JustCounter__secretCount) # 2
print(counter.__secretCount)
# AttributeError: 'JustCounter' object has no attribute '__secretCount'
【例子】类的私有方法实例
class Site:
def __init__(self, name, url):
self.name = name # public
self.__url = url # private
def who(self):
print('name : ', self.name)
print('url : ', self.__url)
def __foo(self): # 私有方法
print('这是私有方法')
def foo(self): # 公共方法
print('这是公共方法')
self.__foo()
x = Site('老马的程序人生', 'https://blog.youkuaiyun.com/LSGO_MYP')
x.who()
# name : 老马的程序人生
# url : https://blog.youkuaiyun.com/LSGO_MYP
x.foo()
# 这是公共方法
# 这是私有方法
x.__Site__foo()
# 这是私有方法
x.__foo()
# AttributeError: 'Site' object has no attribute '__foo'
2.内置变量
大部分内置变量为类的默认成员,称为内置属性,常用内置属性说明如下。
- doc:当前类的描述信息,定义在类的第一行注释,通过类对象直接访问。
- module:当前对象属于哪个模块。
- class:当前对象属于哪个类。
- dict:当前类对象或当前实例对象包含的所有成员。
- base:当前类的基类。如果是单继承,使用__base__可以获取父类。
- bases:当前类的基类。如果是多继承,使用__bases__可以获取所有父类,并以元组类型返回。
提示:下面两个内置变量不是类成员,但是比较常用,简单介绍如下。
- file:返回当前文件的绝对路径。注意,在终端直接运行时,则返回文件本身。因此,如果要使用绝对路径,推荐使用os.path.abspath(file),确保始终返回绝对路径。
- name:返回当前模块的名称。如果为main,表示为顶层模块,能够调用其他非main的模块,并且可以运行;如果为非main,则无法运行,只能用来导入使用。
3.内置函数
基本的魔法方法
__init__(self[, ...])
构造函数 ,当一个实例被创建的时候调用的初始化方法__new__(cls[, ...])
实例化函数 。在一个对象实例化的时候所调用的第一个方法,在调用__init__
初始化前,先调用__new__
。对当前类进行了实例化,并将实例返回,传给__init__
的self
。若__new__
没有正确返回当前类cls
的实例,那__init__
是不会被调用的__new__
方法主要是当你继承一些不可变的 class 时(比如int, str, tuple
), 提供给你一个自定义这些类的实例化过程的途径。
class CapStr(str):
def __new__(cls, string):
string = string.upper()
return str.__new__(cls, string)
a = CapStr("i love lsgogroup")
print(a) # I LOVE LSGOGROUP
_call__(self, *args)
调用实例函数 ,使用小括号()调用实例对象时触发__del__(self)
析构函数,用于销毁实例对象。当类的实例在内存中被释放时,自动触发析构函数。
Python 采用自动引用计数(ARC)方式来回收对象所占用的空间,当程序中有一个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 1;当程序中有两个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 2,依此类推,如果一个对象的引用计数变成了 0,则说明程序中不再有变量引用该对象,表明程序不再需要该对象,因此 Python 就会回收该对象。
大部分时候,Python 的 ARC 都能准确、高效地回收系统中的每个对象。但如果系统中出现循环引用的情况,比如对象 a 持有一个实例变量引用对象 b,而对象 b 又持有一个实例变量引用对象 a,此时两个对象的引用计数都是 1,而实际上程序已经不再有变量引用它们,系统应该回收它们,此时 Python 的垃圾回收器就可能没那么快,要等专门的循环垃圾回收器(Cyclic Garbage Collector)来检测并回收这种引用循环。
【例子】
class C(object):
def __init__(self):
print('into C __init__')
def __del__(self):
print('into C __del__')
c1 = C()
# into C __init__
c2 = c1
c3 = c2
del c3
del c2
del c1
# into C __del__
-
__str__(self)
: 转化成字符串- 当你打印一个对象的时候,触发
__str__
- 当你使用
%s
格式化的时候,触发__str__
str
强转数据类型的时候,触发__str__
- 当你打印一个对象的时候,触发
-
__repr__(self)
:repr
是str
的备胎- 有
__str__
的时候执行__str__
,没有实现__str__
的时候,执行__repr__
repr(obj)
内置函数对应的结果是__repr__
的返回值- 当你使用
%r
格式化的时候 触发__repr__
【例子】
class Cat:
"""定义一个猫类"""
def __init__(self, new_name, new_age):
"""在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
self.name = new_name
self.age = new_age
def __str__(self):
"""返回一个对象的描述信息"""
return "名字是:%s , 年龄是:%d" % (self.name, self.age)
def __repr__(self):
"""返回一个对象的描述信息"""
return "Cat:(%s,%d)" % (self.name, self.age)
def eat(self):
print("%s在吃鱼...." % self.name)
def drink(self):
print("%s在喝可乐..." % self.name)
def introduce(self):
print("名字是:%s, 年龄是:%d" % (self.name, self.age))
# 创建了一个对象
tom = Cat("汤姆", 30)
print(tom) # 名字是:汤姆 , 年龄是:30
print(str(tom)) # 名字是:汤姆 , 年龄是:30
print(repr(tom)) # Cat:(汤姆,30)
tom.eat() # 汤姆在吃鱼....
tom.introduce() # 名字是:汤姆, 年龄是:30
__str__(self)
的返回结果可读性强。也就是说,__str__
的意义是得到便于人们阅读的信息,就像下面的 ‘2019-10-11’ 一样。
__repr__(self)
的返回结果应更准确。怎么说,__repr__
存在的目的在于调试,便于开发者使用。
【例子】
import datetime
today = datetime.date.today()
print(str(today)) # 2019-10-11
print(repr(today)) # datetime.date(2019, 10, 11)
print('%s' %today) # 2019-10-11
print('%r' %today) # datetime.date(2019, 10, 11)
__dict__
转化成字典
class Foo(object):
def __init__(self, name, age):
self.name = name
self.age = age
obj = Foo("武沛齐",19)
print(obj.__dict__)
enter() 和 exit() 是 用于实现上下文管理器。
上下文管理器是一种对象,它定义了 enter() 和 exit() 方法,可以在进入和退出上下文时执行特定的代码块。通常情况下,上下文管理器用于控制资源的分配和释放,例如文件句柄、数据库连接等。
class Foo(object):
def __enter__(self):
print("进入了")
return 666 # 返回什么,data就是什么
def __exit__(self, exc_type, exc_val, exc_tb):
print("出去了")
obj = Foo()
with obj as data:
print('123')
print(data)
# 进入了
# 123
# 666
# 出去了
下面是一个简单的示例,展示了如何使用上下文管理器来控制一个文件的打开和关闭:
class FileContextManager:
def __enter__(self):
self.file = open("example.txt", "r")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
with FileContextManager() as f:
contents = f.read()
print(contents)
4.属性访问
__getattr__(self, name)
: 定义当用户试图获取一个不存在的属性时的行为。__getattribute__(self, name)
:定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__
)。__setattr__(self, name, value)
:定义当一个属性被设置时的行为。__delattr__(self, name)
:定义当一个属性被删除时的行为。
【例子】
class C:
def __getattribute__(self, item):
print('__getattribute__')
return super().__getattribute__(item)
def __getattr__(self, item):
print('__getattr__')
def __setattr__(self, key, value):
print('__setattr__')
super().__setattr__(key, value)
def __delattr__(self, item):
print('__delattr__')
super().__delattr__(item)
c = C()
c.x
# __getattribute__
# __getattr__
c.x = 1
# __setattr__
del c.x
# __delattr__
5.描述符
描述符就是将某种特殊类型的类的实例指派给另一个类的属性。
__get__(self, instance, owner)
用于访问属性,它返回属性的值。__set__(self, instance, value)
将在属性分配操作中调用,不返回任何内容。__del__(self, instance)
控制删除操作,不返回任何内容。
【例子】
class MyDecriptor:
def __get__(self, instance, owner):
print('__get__', self, instance, owner)
def __set__(self, instance, value):
print('__set__', self, instance, value)
def __delete__(self, instance):
print('__delete__', self, instance)
class Test:
x = MyDecriptor()
t = Test()
t.x
# __get__ <__main__.MyDecriptor object at 0x000000CEAAEB6B00> <__main__.Test object at 0x000000CEABDC0898> <class '__main__.Test'>
t.x = 'x-man'
# __set__ <__main__.MyDecriptor object at 0x00000023687C6B00> <__main__.Test object at 0x00000023696B0940> x-man
del t.x
# __delete__ <__main__.MyDecriptor object at 0x000000EC9B160A90> <__main__.Test object at 0x000000EC9B160B38>```
6. 迭代器
迭代器类型的定义:
1. 当类中定义了 __iter__
和 __next__
两个方法。
2. __iter__
方法需要返回对象本身,即:self
3. __next__
方法,返回下一个数据,如果没有数据了,则需要抛出一个StopIteration的异常。
迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
- 迭代器只能往前不会后退。
- 字符串,列表或元组对象都可用于创建迭代器:
迭代器,含有__iter__方法和__next__方法,__iter__返回自身,__next__可以获取数据(终止是抛出StopIteration异常。可以被for循环。
生成器,在定义时是函数中重要包含yield就是生成器函数,执行函数获得生成器对象(一种特殊的迭代器);可以通过next取值 & 也可以通过for循环取值。生成器详见5.5
可迭代对象,含有 __iter__方法,且返回一个迭代器对象。可以被for循环。
# 创建 迭代器类型 :
class IT(object):
def __init__(self):
self.counter = 0
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == 3:
raise StopIteration() # 返回一个异常
return self.counter
# 根据类实例化创建一个迭代器对象:
obj1 = IT()
# v1 = obj1.__next__()
# v2 = obj1.__next__()
# v3 = obj1.__next__() # 抛出异常
v1 = next(obj1) # obj1.__next__()
print(v1)
v2 = next(obj1)
print(v2)
# v3 = next(obj1)
# print(v3)
obj2 = IT()
for item in obj2: # 首先会执行迭代器对象的__iter__方法并获取返回值,一直去反复的执行 next(对象)
print(item)
将创建一个返回数字的迭代器,初始值为1,逐步递增1。在20次迭代后停止输出。
class Add: # 自定义类
def __iter__(self): # 魔术函数,当迭代器初始化时调用
self.a = 1 # 初始设置a为1
return self
def __next__(self): # 魔术函数,当调用next()函数时调用
if self.a <= 20:
x = self.a # 临时缓存递增变量值
self.a += 1 # 递增变量a的值
return x # 返回递增之前的值
else:
raise StopIteration # 抛出异常
add = Add() # 实例化Add类型
myiter = iter(add) # 调用iter()函数,初始化为迭代器对象
for x in myiter: # 遍历迭代器
print(x, end=" ") # 输出迭代器中每个元素的值
7.算术运算符
类型工厂函数,指的是“不通过类而是通过函数来创建对象”。
class C:
pass
print(type(len)) # <class 'builtin_function_or_method'>
print(type(dir)) # <class 'builtin_function_or_method'>
print(type(int)) # <class 'type'>
print(type(list)) # <class 'type'>
print(type(tuple)) # <class 'type'>
print(type(C)) # <class 'type'>
print(int('123')) # 123
# 这个例子中list工厂函数把一个元祖对象加工成了一个列表对象。
print(list((1, 2, 3))) # [1, 2, 3]
__add__(self, other)
定义加法的行为:+
__sub__(self, other)
定义减法的行为:-
【例子】
class MyClass:
def __init__(self, height, weight):
self.height = height
self.weight = weight
# 两个对象的长相加,宽不变.返回一个新的类
def __add__(self, others):
return MyClass(self.height + others.height, self.weight + others.weight)
# 两个对象的宽相减,长不变.返回一个新的类
def __sub__(self, others):
return MyClass(self.height - others.height, self.weight - others.weight)
# 说一下自己的参数
def intro(self):
print("高为", self.height, " 重为", self.weight)
def main():
a = MyClass(height=10, weight=5)
a.intro()
b = MyClass(height=20, weight=10)
b.intro()
c = b - a
c.intro()
d = a + b
d.intro()
if __name__ == '__main__':
main()
# 高为 10 重为 5
# 高为 20 重为 10
# 高为 10 重为 5
# 高为 30 重为 15
__mul__(self, other)
定义乘法的行为:*
__truediv__(self, other)
定义真除法的行为:/
__floordiv__(self, other)
定义整数除法的行为://
__mod__(self, other)
定义取模算法的行为:%
__divmod__(self, other)
定义当被divmod()
调用时的行为divmod(a, b)
把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)
。
【例子】
print(divmod(7, 2)) # (3, 1)
print(divmod(8, 2)) # (4, 0)
__pow__(self, other[, module])
定义当被power()
调用或**
运算时的行为__lshift__(self, other)
定义按位左移位的行为:<<
__rshift__(self, other)
定义按位右移位的行为:>>
__and__(self, other)
定义按位与操作的行为:&
__xor__(self, other)
定义按位异或操作的行为:^
__or__(self, other)
定义按位或操作的行为:|
8.反算术运算符
反运算魔方方法,与算术运算符保持一一对应,不同之处就是反运算的魔法方法多了一个“r”。当文件左操作不支持相应的操作时被调用。
__radd__(self, other)
定义加法的行为:+
__rsub__(self, other)
定义减法的行为:-
__rmul__(self, other)
定义乘法的行为:*
__rtruediv__(self, other)
定义真除法的行为:/
__rfloordiv__(self, other)
定义整数除法的行为://
__rmod__(self, other)
定义取模算法的行为:%
__rdivmod__(self, other)
定义当被 divmod() 调用时的行为__rpow__(self, other[, module])
定义当被 power() 调用或**
运算时的行为__rlshift__(self, other)
定义按位左移位的行为:<<
__rrshift__(self, other)
定义按位右移位的行为:>>
__rand__(self, other)
定义按位与操作的行为:&
__rxor__(self, other)
定义按位异或操作的行为:^
__ror__(self, other)
定义按位或操作的行为:|
a + b
这里加数是a
,被加数是b
,因此是a
主动,反运算就是如果a
对象的__add__()
方法没有实现或者不支持相应的操作,那么 Python 就会调用b
的__radd__()
方法。
【例子】
class Nint(int):
def __radd__(self, other):
return int.__sub__(other, self) # 注意 self 在后面
a = Nint(5)
b = Nint(3)
print(a + b) # 8
print(1 + b) # -2
9.增量赋值运算符
__iadd__(self, other)
定义赋值加法的行为:+=
__isub__(self, other)
定义赋值减法的行为:-=
__imul__(self, other)
定义赋值乘法的行为:*=
__itruediv__(self, other)
定义赋值真除法的行为:/=
__ifloordiv__(self, other)
定义赋值整数除法的行为://=
__imod__(self, other)
定义赋值取模算法的行为:%=
__ipow__(self, other[, modulo])
定义赋值幂运算的行为:**=
__ilshift__(self, other)
定义赋值按位左移位的行为:<<=
__irshift__(self, other)
定义赋值按位右移位的行为:>>=
__iand__(self, other)
定义赋值按位与操作的行为:&=
__ixor__(self, other)
定义赋值按位异或操作的行为:^=
__ior__(self, other)
定义赋值按位或操作的行为:|=
10.一元运算符
__neg__(self)
定义正号的行为:+x
__pos__(self)
定义负号的行为:-x
__abs__(self)
定义当被abs()
调用时的行为__invert__(self)
定义按位求反的行为:~x
11.定制序列
协议(Protocols)与其它编程语言中的接口很相似,它规定你哪些方法必须要定义。然而,在 Python 中的协议就显得不那么正式。事实上,在 Python 中,协议更像是一种指南。
容器类型的协议
- 如果说你希望定制的容器是不可变的话,你只需要定义
__len__()
和__getitem__()
方法。 - 如果你希望定制的容器是可变的话,除了
__len__()
和__getitem__()
方法,你还需要定义__setitem__()
和__delitem__()
两个方法。
【例子】编写一个不可改变的自定义列表,要求记录列表中每个元素被访问的次数。
class CountList:
def __init__(self, *args):
self.values = [x for x in args]
self.count = {}.fromkeys(range(len(self.values)), 0)
def __len__(self):
return len(self.values)
def __getitem__(self, item):
self.count[item] += 1
return self.values[item]
c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1]) # 3
print(c2[2]) # 6
print(c1[1] + c2[1]) # 7
print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
print(c2.count)
# {0: 0, 1: 1, 2: 1, 3: 0, 4: 0}
__len__(self)
定义当被len()
调用时的行为(返回容器中元素的个数)。__getitem__(self, key)
定义获取容器中元素的行为,相当于self[key]
。__setitem__(self, key, value)
定义设置容器中指定元素的行为,相当于self[key] = value
。__delitem__(self, key)
定义删除容器中指定元素的行为,相当于del self[key]
。
【例子】编写一个可改变的自定义列表,要求记录列表中每个元素被访问的次数。
class CountList:
def __init__(self, *args):
self.values = [x for x in args]
self.count = {}.fromkeys(range(len(self.values)), 0)
def __len__(self):
return len(self.values)
def __getitem__(self, item):
self.count[item] += 1
return self.values[item]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
for i in range(0, len(self.values)):
if i >= key:
self.count[i] = self.count[i + 1]
self.count.pop(len(self.values))
c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1]) # 3
print(c2[2]) # 6
c2[2] = 12
print(c1[1] + c2[2]) # 15
print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
print(c2.count)
# {0: 0, 1: 0, 2: 2, 3: 0, 4: 0}
del c1[1]
print(c1.count)
# {0: 0, 1: 0, 2: 0, 3: 0}
6.4 类的特性
-
封装,将方法封装到类中 或 将数据封装到对象中,便于以后使用。
-
继承,将类中公共的方法提取到基类中去实现。
-
多态,Python默认支持多态,关注点在于对象的行为,能作什么;而不是关注对象所属的数据类型,
1. 封装
封装: 封装数据和方法
- 数据封装到对象,以后再去获取。规范数据(约束)
- 将数据分装到对象中,在方法中对原始数据进行加工处理。
- 创建同一类的多个对象且同类数据可以具有相同的功能(方法)。
- 数据封装到对象,以后再去获取。
"""
# 需求
1. while循环提示 户输 : 户名、密码、邮箱(正则满足邮箱格式)
2. 为每个用户创建一个个对象,并添加到user_list中。
3. 当列表中的添加 3个对象后,跳出循环并以此循环打印所有用户的姓名和邮箱
"""
import re
class Userinfo:
def __init__(self,user,pwd,email):
self.user = user
self.pwd =pwd
self.email = email
user_list = []
while True:
user = input("请输入用户名:")
pwd = input("请输入密码:")
email = input("请输入邮箱:")
match_email = re.match('(\w+([+-.]\w+)*@\w+([+-.]\w+)*\.\w([+-.]\w+)*)',email,re.ASCII)
if not match_email:
print("格式错误,请重新输入")
continue
user_object = Userinfo(user,pwd,email) # 类名可以直接用,赋值给一个变量,
user_list.append(user_object) # 变量带着封装的数据存入列表
if len(user_list) == 3:
break
for item in user_list:
print(item.user,item.email) # 取出封装的数据
- 将数据分装到对象中,在方法中对原始数据进行加工处理。
# 将数据分装到对象中,在方法中对原始数据进行加工处理。
class Pagination:
def __init__(self, current_page, per_page_num=10):
self.per_page_num = per_page_num
if not current_page.isdecimal():
self.current_page = 1
return
current_page = int(current_page)
if current_page < 1:
self.current_page = 1
return
self.current_page = current_page
def start(self):
return (self.current_page - 1) * self.per_page_num
def end(self):
return self.current_page * self.per_page_num
user_list = ["用户-{}".format(i) for i in range(1, 3000)]
# 分页显示,每页显示10条
while True:
page = input("请输入页码:")
# page,当前访问的页码
# 10,每页显示10条数据
# 内部执行Pagination类的init方法。
pg_object = Pagination(page, 10)
page_data_list = user_list[ pg_object.start() : pg_object.end() ]
for item in page_data_list:
print(item)
- 创建同一类的数据且同类数据可以具有相同的功能(方法)
# 创建同一类的数据且同类数据可以具有相同的功能(方法)
class Police:
"""警察"""
def __init__(self, name, role):
self.name = name
self.role = role
if role == "队员":
self.hit_points = 200
else:
self.hit_points = 500
def show_status(self):
""" 查看警察状态 """
message = "警察{}的生命值为:{}".format(self.name, self.hit_points)
print(message)
def bomb(self, terrorist_list):
""" 投炸弹,炸掉恐怖分子 """
for terrorist in terrorist_list:
terrorist.blood -= 200
terrorist.show_status()
class Terrorist:
""" 恐怖分子 """
def __init__(self, name, blood=300):
self.name = name
self.blood = blood
def shoot(self, police_object):
""" 开枪射击某个警察 """
police_object.hit_points -= 5
police_object.show_status()
self.blood -= 2
def strafe(self, police_object_list):
""" 扫射某些警察 """
for police_object in police_object_list:
police_object.hit_points -= 8
police_object.show_status()
def show_status(self):
""" 查看恐怖分子状态 """
message = "恐怖分子{}的血量值为:{}".format(self.name, self.blood)
print(message)
def run():
# 1.创建3个警察
p1 = Police("simba", "队员")
p2 = Police("bo", "队员")
p3 = Police("zhanlang", "队长")
# 2.创建2个匪徒
t1 = Terrorist("fox")
t2 = Terrorist("dog")
# fox匪徒射击zhanlang警察
t1.shoot(p3)
# fox扫射
t1.strafe([p1, p2, p3])
# dog射击bo
t2.shoot(p2)
# simba炸了那群匪徒王八蛋
p1.bomb([t1, t2])
# simba又炸了一次fox
p1.bomb([t1])
if __name__ == '__main__':
run()
【案例】用户注册和登录
# 用户登录和注册
class User:
def __init__(self, name, pwd):
self.name = name
self.pwd = pwd
class Account:
def __init__(self):
# 用户列表,数据格式:[user对象,user对象,user对象]
self.user_list = []
def login(self):
"""
用户登录,输入用户名和密码然后去self.user_list中校验用户合法性
:return:
"""
print("用户登录")
while True:
user = input("请输入用户名(Q/q):")
if user.upper() == 'Q':
break
pwd = input("请输入密码:")
for user_object in self.user_list:
if user == user_object.name and pwd == user_object.pwd:
print("登录成功")
break
else:
print("登录失败")
def register(self):
"""
用户注册,每注册一个用户就创建一个user对象,然后添加到self.user_list中,表示注册成功。
:return:
"""
print("用户注册")
while True:
user = input("请输入用户名(Q/q):")
if user.upper() == 'Q':
break
pwd = input("请输入密码:")
user_object = User(user, pwd)
self.user_list.append(user_object)
def run(self):
"""
主程序
:return:
"""
method_dict = {
"1": {"title": "登录", "method": self.login},
"2": {"title": "注册", "method": self.register}
}
message = ";".join([f"{k}.{v['title']}" for k, v in method_dict.items()])
while True:
print(message)
choice = input("请选择功能(Q/q):")
if choice.upper() == 'Q':
break
info = method_dict.get(choice)
if not info:
print("选择错误,请重新选择")
continue
method = info['method']()
obj = Account()
obj.run()
2. 继承
不同类之间可能存在代码重叠,如果不想重写代码,可以利用继承机制快速实现代码"复制"。
继承机制简化了类的创建,提高了代码的可重用性,还可以构建类与类之间的关系。
新建类可以继承一个或者多个父类,父类又可以成为基类或超类,新建类称为子类或派生类。
class 子类(基类1, 基类2, …, 基类N):
基类可以是一个或多个,基类之间通过逗号分隔。
如果不指定基类,则将继承Python对象系统的根类object。
如果只有一个父类,则称为单继承;如果有多个父类,则称为多继承。
class MyList(list):
pass
lst = MyList([1, 5, 2, 7, 8])
lst.append(9)
lst.sort()
print(lst)
# [1, 2, 5, 7, 8, 9]
- 执行对象.方法时,优先去当前对象所关联的类中找,没有的话才去她的父类中查找。
class Base:
def func(self):
print("Base.func")
class Son(Base): # 继承base
def show(self):
print("Son.show")
s1 = Son()
s1.show()
s1.func() # 优先在自己的类中找,自己没有才去父类。
s2 = Base()
s2.func()
- 重写,如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性。
# 类定义
class people:
# 定义基本属性
name = ''
age = 0
# 定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
# 定义构造方法
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))
# 单继承示例
class student(people):
grade = ''
def __init__(self, n, a, w, g):
# 调用父类的构函
people.__init__(self, n, a, w)
self.grade = g
# 覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级" % (self.name, self.age, self.grade))
s = student('小马的程序人生', 10, 60, 3)
s.speak()
# 小马的程序人生 说: 我 10 岁了,我在读 3 年级
注意:如果上面的程序去掉:people.__init__(self, n, a, w)
,则输出: 说: 我 0 岁了,我在读 3 年级
,因为子类的构造方法把父类的构造方法覆盖了。
【例子】
import random
class Fish:
def __init__(self):
self.x = random.randint(0, 10)
self.y = random.randint(0, 10)
def move(self):
self.x -= 1
print("我的位置", self.x, self.y)
class GoldFish(Fish): # 金鱼
pass
class Shark(Fish): # 鲨鱼
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print("吃货的梦想就是天天有得吃!")
self.hungry = False
else:
print("太撑了,吃不下了!")
self.hungry = True
g = GoldFish()
g.move() # 我的位置 9 4
s = Shark()
s.eat() # 吃货的梦想就是天天有得吃!
s.move()
# AttributeError: 'Shark' object has no attribute 'x'
解决该问题可用以下两种方式:
- 调用未绑定的父类方法
Fish.__init__(self)
class Shark(Fish): # 鲨鱼
def __init__(self):
Fish.__init__(self) # 如果父类有多个参数,也要带上
self.hungry = True #新属性
- 使用super函数
super().__init__()
class Shark(Fish): # 鲨鱼
def __init__(self):
super().__init__()
self.hungry = True
**多继承:从左到右,深度优先,大小钻石,留住顶端(广度优选)
- self到底是谁?去self对应的那个类中去获取成员,没有就按照继承关系向上查找 。
- 对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,
- mro调取,底层用c3算法
# 调用mro查看
class C(object):
pass
class B(object):
pass
class A(B, C):
pass
print( A.mro() ) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
print( A.__mro__ ) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
两种继承方式的区别:调用未绑定的父类方法是跟继承没有关系的,super()是依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找。
#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A,B):
pass
c=C()
c.test() #打印结果:from B
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
复杂案例说明C3算法
mro(A) = [A] + merge( mro(B), mro(C), mro(P), [B,C,P])
[B,D,G,H,K,E,M] [C,E,F,M,N]
mro(A) = [A,B,D,G,H,K,C,E,F,M,N,P]
-----------------------------------------------------
mro(B) = [B] + merge( mro(D), mro(E), [D,E])
mro(D) = [D] + merge(mro(G),mro(H), [G,H])
mro(G) = [G]
mro(H) = [H,K]
mro(B) = [B] + merge( [], [E,M], [E])
mro(B) = [B,D,G,H,K,E,M]
-----------------------------------------------------
mro(C) = [C] + merge(mro(E),mro(F),[E,F])
mro(E) = [E,M]
mro(F) = [F,M,N]
mro(C) = [C] + merge([M],[M,N] ,[])
mro(C) = [C,E,F,M,N]
3. 多态
Python 中的多态性是指同一个函数可以被不同的对象调用,并且在运行时根据不同的对象类型来执行不同的代码。
在 Python 中,类的多态性可以通过继承、重写、重载和动态绑定等机制来实现。
- 方法的重写是指在子类中定义与父类中同名的方法,从而覆盖父类中的方法。当调用该方法时,会自动调用子类中的方法,而不是父类中的方法。这种方式可以实现运行时多态。
- 方法的重载是指在同一个类中定义多个同名的方法,但是这些方法的参数类型或参数个数不同。当调用该方法时,会根据传入的参数类型或参数个数自动调用对应的方法。这种方式可以实现编译时多态。
- 动态绑定是指在运行时根据对象的实际类型来确定调用哪个方法或访问哪个属性。通过 getattr() 和 call() 方法实现的。
鸭子类型(duck typing)是动态类型的一种风格。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型,(一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。)
例如,A是否为B的子类,不是由继承关系决定的,而是由A和B的接口决定的,当集合B有的,集合A都有,就认为A和B是一类,这种编程思想也称为鸭子类型。
4. 组合
代码重用有两种方式:继承和组合。
组合是指在一个类中使用另一个类的对象作为数据属性。
- 通过继承建立派生类与基类之间的关系,这是一种从属关系。
- 使用组合建立类与组合类之间的关系,这是一种交集关系。
class Turtle:
def __init__(self, x):
self.num = x
class Fish:
def __init__(self, x):
self.num = x
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x)
self.fish = Fish(y)
def print_num(self):
print("水池里面有乌龟%s只,小鱼%s条" % (self.turtle.num, self.fish.num))
p = Pool(2, 3)
p.print_num()
# 水池里面有乌龟2只,小鱼3条
【示例】计算圆环的面积和周长。
from math import pi
class Circle: # 圆形类
def __init__(self, radius): # 初始化半径
self.radius = radius
def area(self): # 计算圆的面积
return pi * self.radius * self.radius
def perimeter(self): # 计算圆的周长
return 2 * pi * self.radius
circle = Circle(10) # 实例化一个圆
area1 = circle.area() # 计算圆面积
per1 = circle.perimeter() # 计算圆周长
print(area1, per1) # 打印圆面积和周长
class Ring: # 圆环类
def __init__(self, radius_outside, radius_inside):
self.outsid_circle = Circle(radius_outside) # 组合外圆实例
self.inside_circle = Circle(radius_inside) # 组合内圆实例
def area(self): # 计算圆环的面积
return self.outsid_circle.area() - self.inside_circle.area()
def perimeter(self): # 计算圆环的周长
return self.outsid_circle.perimeter() + self.inside_circle.perimeter()
ring = Ring(10, 5) # 实例化一个圆环
print(ring.perimeter()) # 计算圆环的周长
print(ring.area()) # 计算圆环的面积
类和方法都可以作为元素
class Student(object):
""" 学生类 """
def __init__(self, name, age, class_object):
self.name = name
self.age = age
self.class_object = class_object
def message(self):
data = "我是一名{}班的学生,我叫:{},我今年{}岁".format(self.class_object.title, self.name, self.age)
print(data)
class Classes(object):
""" 班级类 """
def __init__(self, title, school_object):
self.title = title
self.school_object = school_object
class School(object):
""" 学校类 """
def __init__(self, name):
self.name = name
s1 = School("北京校区")
s2 = School("上海校区")
c1 = Classes("Python全栈", s1)
c2 = Classes("Linux云计算", s2)
user_object_list = [
Student("abc", 19, c1),
Student("simba", 19, c1),
Student("bo", 19, c2)
]
for obj in user_object_list:
print(obj.name, obj.class_object.title , obj.class_object.school_object.name)
# abc Python全栈 北京校区
# simba Python全栈 北京校区
# bo Linux云计算 上海校区
注:内容主体来自阿里天池AI训练营,增加的部分来自于看其他的书籍和报名的付费课程。