python类和异常处理

本文详细介绍了Python中的类和异常处理机制。涵盖了类的定义、对象、方法、成员变量、隐藏和封装、继承、多态性以及异常处理的使用,包括try-except、finally、raise等。此外,还讲解了Python的动态性、元类、枚举类以及特殊方法如repr、del等。通过对这些概念的深入理解,读者可以更好地掌握Python的面向对象编程和错误处理。

类和对象

类和对象基础

定义类

定义类的语法:

class 类名:
	执行语句...
	变量...
	方法...

类命名:
由一个或多个有意义的单词连缀而成,每个单词首字母大写,其他小写,单词之间不要使用任何分隔符。

类中各个成员之间的定义顺序没有任何影响,可以互相调用

在类中定义的方法默认是实例方法,定义实例方法的方法与定义函数的方法基本相同,只是实 例方法的第一个参数会被绑定到方法的调用者(该类的实例)一因此实例方法至少应该定义一个参数,该参数通常会被命名为 self。

init,这个方法被称为构造方法。 构造方法用于构造该类的对象, Python 通过调用构造方法返回该类的对象(无须使用 new)。

Python 也允许为类定义说明文挡, 该文档同样被放在类声明之后、类体之前

对象

创建对象的途径是构造方法,调用某个类的构造方法就可以创建这个类的对象,

变量 = 类名()

对象的作用

  • 操作对象的实例变量(访问实例变量的值,添加实例变量,删除实例变量)
  • 调用对象的方法

访问对象的方法或变量

对象.变量|方法(参数)
class Person :
    '这是一个学习Python定义的一个Person类'
    # 下面定义了一个类变量
    hair = 'black'
    def __init__(self, name = 'Charlie', age=8):
        # 下面为Person对象增加2个实例变量
        self.name = name
        self.age = age
    # 下面定义了一个say方法
    def say(self, content):
        print(content)
help(Person)
# 调用Person类的构造方法,返回一个Person对象
# 将该Person对象赋给p变量
p = Person()
# 输出p的name、age实例变量
print(p.name, p.age)  # Charlie 8
# 访问p的name实例变量,直接为该实例变量赋值
p.name = '李刚'
# 调用p的say()方法,声明say()方法时定义了2个形参
# 但第一个形参(self)是自动绑定的,因此调用该方法只需为第二个形参指定一个值
p.say('Python语言很简单,学习很容易!')
# 再次输出p的name、age实例变量
print(p.name, p.age)  # 李刚 8

# 为p对象增加一个skills实例变量
p.skills = ['programming', 'swimming']
print(p.skills)
# 删除p对象的name实例变量
del p.name
# 再次访问p的name实例变量
#print(p.name)  # AttributeError

# 先定义一个函数
def info(self):
    print("---info函数---", self)
# 使用info对p的foo方法赋值(动态绑定方法)
p.foo = info
# Python不会自动将调用者绑定到第一个参数,
# 因此程序需要手动将调用者绑定为第一个参数
p.foo(p)  # ①

# 使用lambda表达式为p对象的bar方法赋值(动态绑定方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p) # ②

def intro_func(self, content):
    print("我是一个人,信息为:%s" % content)
# 导入MethodType
from types import MethodType
# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
p.intro = MethodType(intro_func, p)
# 第一个参数已经绑定了,无需传入
p.intro("生活在别处")

class Dog:
    # 定义一个jump()方法
    def jump(self):
        print("正在执行jump方法")
    # 定义一个run()方法,run()方法需要借助jump()方法
    def run(self):
        # 使用self参数引用调用run()方法的对象
        self.jump()
        print("正在执行run方法")

d = Dog()
d.run()


#  self 作为返回值
class ReturnSelf :
    def grow(self):
        if hasattr(self, 'age'):
            self.age += 1
        else:
            self.age = 1
        # return self返回调用该方法的对象
        return self
rs = ReturnSelf()
# 可以连续调用同一个方法
rs.grow().grow().grow()
print("rs的age属性值是:", rs.age)

方法

python 的类可以调用实例方法,但使用类调用 实例方法时, Python 不会 自 动为 方法的第一个参数 self绑定参数值;程序必须显式地为第一个参数 self传入方法调用 者。 这种调用方式被称为“未绑定方法”。

class User:
    def walk (self):
        print(self, '正在慢慢地走')
# 通过类调用实例方法
#User.walk()
u = User()
# 显式为方法的第一个参数绑定参数值
User.walk(u)

# 显式为方法的第一个参数绑定fkit字符串参数值
User.walk('fkit')

类方法与静态方法

Python 会自动绑定类方法的第一个参数,类方法的第一个参数(通常建议参数名为 cls)会自动绑 定到类本身:但对于静态方法则不会自动绑定。

使用@classmethod 修饰的方法就是类方法;使用@staticmethod 修饰的方法就是静态方法。

class Bird:
    # classmethod修饰的方法是类方法
    @classmethod
    def fly (cls):
        print('类方法fly: ', cls)
    # staticmethod修饰的方法是静态方法
    @staticmethod
    def info (p):
        print('静态方法info: ', p)
# 调用类方法,Bird类会自动绑定到第一个参数
Bird.fly()  #①
# 调用静态方法,不会自动绑定,因此程序必须手动绑定第一个参数
Bird.info('crazyit')
# 创建Bird对象
b = Bird()
# 使用对象调用fly()类方法,其实依然还是使用类调用,
# 因此第一个参数依然被自动绑定到Bird类
b.fly()  #②
# 使用对象调用info()静态方法,其实依然还是使用类调用,
# 因此程序必须为第一个参数执行绑定
b.info('fkit')


@装饰器

使用@符号引用己有的函数(比如@staticmethod、@classmethod )后,可用于修饰其他函数, 装饰被修饰

当程序使用“@函数”(比如函数 A)装饰另一个函数(比如函数 B )时, 实际上完成如下两
步。

  • 将被修饰的函数(函数 B )作为参数传给@符号引用的函数(函数 A )。
  • 将函数 B 替换(装饰)成第1步的返回值。
def funA(fn):
    print('A')
    fn() # 执行传入的fn参数
    return 'fkit'
'''
下面装饰效果相当于:funA(funB),
funB将会替换(装饰)成该语句的返回值;
由于funA()函数返回fkit,因此funB就是fkit
'''
@funA
def funB():
    print('B')
print(funB) # fkit

def foo(fn):
    # 定义一个嵌套函数
    def bar(*args):
        print("===1===", args)
        n = args[0]
        print("===2===", n * (n - 1))
        # 查看传给foo函数的fn函数
        print(fn.__name__)
        fn(n * (n - 1))
        print("*" * 15)
        return fn(n * (n - 1))
    return bar
'''
下面装饰效果相当于:foo(my_test),
my_test将会替换(装饰)成该语句的返回值;
由于foo()函数返回bar函数,因此funB就是bar
'''
@foo
def my_test(a):
    print("==my_test函数==", a)
# 打印my_test函数,将看到实际上是bar函数
print(my_test) # <function foo.<locals>.bar at 0x00000000021FABF8>
# 下面代码看上去是调用my_test(),其实是调用bar()函数
my_test(10)
my_test(6, 5)

成员变量

类变量和实例变量

class Address :
    detail = '广州'
    post_code = '510660'
    def info (self):
        # 尝试直接访问类变量
#        print(detail) # 报错
        # 通过类来访问类变量
        print(Address.detail) # 输出 广州
        print(Address.post_code) # 输出 510660
# 通过类来访问Address类的类变量
print(Address.detail)
addr = Address()
addr.info()
# 修改Address类的类变量
Address.detail = '佛山'
Address.post_code = '460110'
addr.info()
class Record:
    # 定义两个类变量
    item = '鼠标'
    date = '2016-06-16'
    def info (self):
        print('info方法中: ', self.item)
        print('info方法中: ', self.date)

rc = Record()
print(rc.item) # '鼠标'
print(rc.date) # '2016-06-16'
rc.info()
    
# 修改Record类的两个类变量
Record.item = '键盘'
Record.date = '2016-08-18'
# 调用info()方法
rc.info()
class Inventory:
    # 定义两个类变量
    item = '鼠标'
    quantity = 2000
    # 定义实例方法
    def change(self, item, quantity):
        # 下面赋值语句不是对类变量赋值,而是定义新的实例变量
        self.item = item
        self.quantity = quantity
# 创建Inventory对象
iv = Inventory()
iv.change('显示器', 500)
# 访问iv的item和quantity实例变量
print(iv.item) # 显示器
print(iv.quantity) # 500
# 访问Inventory的item和quantity类变量
print(Inventory.item) # 鼠标
print(Inventory.quantity) # 2000

Inventory.item = '类变量item'
Inventory.quantity = '类变量quantity'
# 访问iv的item和quantity实例变量
print(iv.item)
print(iv.quantity)

iv.item = '实例变量item'
iv.quantity = '实例变量quantity'
print(Inventory.item)
print(Inventory.quantity)

使用property函数定义属性

如果为 Python 类定义了 getter、 S巳忧巳r 等访问器方法,则可使用 property()函数将它们定义成属 性(相当于实例变量)。

property()函数的语法:

property(fget=None, fset=None, fdel=None, doc=None)
class Rectangle:
    # 定义构造方法
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setsize()函数
    def setsize (self , size):
        self.width, self.height = size
    # 定义getsize()函数
    def getsize (self):
        return self.width, self.height
     # 定义getsize()函数
    def delsize (self):
        self.width, self.height = 0, 0  
    # 使用property定义属性
    size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')
# 访问size属性的说明文档
print(Rectangle.size.__doc__)
# 通过内置的help()函数查看Rectangle.size的说明文档
help(Rectangle.size)
rect = Rectangle(4, 3)
# 访问rect的size属性
print(rect.size) # (4, 3)
# 对rect的size属性赋值
rect.size = 9, 7 
# 访问rect的width、height实例变量
print(rect.width) # 9
print(rect.height) # 7
# 删除rect的size属性
del rect.size
# 访问rect的width、height实例变量
print(rect.width) # 0
print(rect.height) # 0
print(dir(Rectangle))


# 传入少量属性,属性不能删除
class User :
    def __init__ (self, first, last):
        self.first = first
        self.last = last
    def getfullname(self):
        return self.first + ',' + self.last
    def setfullname(self, fullname):
        first_last = fullname.rsplit(',');
        self.first = first_last[0]
        self.last = first_last[1]
    # 使用property()函数定义fullname属性,只传入2个参数
    # 该属性是一个读写属性,但不能删除
    fullname = property(getfullname, setfullname)
u = User('悟空', '孙')
# 访问fullname属性
print(u.fullname)
# 对fullname属性赋值
u.fullname = '八戒,朱'
print(u.first)
print(u.last)
# @property 装饰器定义属性
class Cell:
    # 使用@property修饰方法,相当于为该属性设置getter方法
    @property
    def state(self):
        return self._state
    # 为state属性设置setter方法
    @state.setter
    def state(self, value):
        if 'alive' in value.lower():
            self._state = 'alive'
        else:
            self._state = 'dead'
    # 为is_dead属性设置getter方法
    # 只有getter方法属性是只读属性
    @property
    def is_dead(self):
        return not self._state.lower() == 'alive'
c = Cell()
# 修改state属性
c.state = 'Alive'
# 访问state属性
print(c.state)
# 访问is_dead属性
print(c.is_dead)

隐藏和封装

Python 类的成员命名为以双下画线开 头的, Python 就会把它们隐藏起来。

 
class User :
   def __hide(self):
       print('示范隐藏的hide方法')
   def getname(self):
       return self.__name
   def setname(self, name):
       if len(name) < 3 or len(name) > 8:
           raise ValueError('用户名长度必须在3~8之间')
       self.__name = name
   name = property(getname, setname)
   def setage(self, age):
       if age < 18 or age > 70:
           raise ValueError('用户名年龄必须在18在70之间')
       self.__age = age
   def getage(self):
       return self.__age
   age = property(getage, setage)

# 创建User对象
u = User()
# 对name属性赋值,实际上调用setname()方法
#u.name = 'fk' # 引发 ValueError: 用户名长度必须在3~8之间
u.name = 'fkit'
u.age = 25
print(u.name) # fkit
print(u.age) # 25

# 尝试调用隐藏的__hide()方法
#u.__hide()

# 调用隐藏的__hide()方法
u._User__hide()
# 对隐藏的__name属性赋值
u._User__name = 'fk'
# 访问User对象的name属性(实际上访问__name实例变量)
print(u.name)

类的继承

python的继承是多继承机制,一个子类可以同时又多个直接父类

继承的语法

class SubClass(SuperClass1,SuperClass2,...):
	# 类的定义部分

定义类是没有显式的指定直接父类,则这个类默认继承object类
子类是对父类的拓展,子类是一种特殊的父类

class Fruit:
    def info(self):
        print("我是一个水果!重%g克" % self.weight)

class Food:
    def taste(self):
        print("不同食物的口感不同")

# 定义Apple类,继承了Fruit和Food类
class Apple(Fruit, Food):
    pass

# 创建Apple对象
a = Apple()
a.weight = 5.6
# 调用Apple对象的info()方法
a.info()
# 调用Apple对象的taste()方法
a.taste()

多继承

python支持多继承,但不推荐

子类有多个直接父类时,会得到所有父类的方法,如果父类中方法有同名,排在前面的父类中的方法会‘遮蔽’后面的父类中的同名方法。

class Item:
    def info (self):
        print("Item中方法:", '这是一个商品') 
class Product:
    def info (self):
        print("Product中方法:", '这是一个工业产品')
#class Mouse(Item, Product): # ①
class Mouse(Product, Item): # ①
    pass
m = Mouse()
m.info()
    

重写父类的方法

子类包含与父类同名的方法的现象称为方法重写,也叫方法覆盖

class Bird: 
    # Bird类的fly()方法
    def fly(self):
        print("我在天空里自由自在地飞翔...")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("我只能在地上奔跑...")
  
# 创建Ostrich对象
os = Ostrich()
# 执行Ostrich对象的fly()方法,将输出"我只能在地上奔跑..."
os.fly()

   

使用未绑定方法调用被重写的方法

class BaseClass:
    def foo (self):
        print('父类中定义的foo方法')
class SubClass(BaseClass):
    # 重写父类的foo方法
    def foo (self):
        print('子类重写父类中的foo方法')
    def bar (self):
        print('执行bar方法')
        # 直接执行foo方法,将会调用子类重写之后的foo()方法
        self.foo() 
        # 使用类名调用实例方法(未绑定方法)调用父类被重写的方法
        BaseClass.foo(self)
sc = SubClass()
sc.bar()

使用super函数调用父类的构造方法

python要求:子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。
子类调用父类的构造方法有两种方式:

  • 使用未绑定调用,
  • 使用super()函数调用父类的构造方法
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):
    # 重写父类的构造方法
    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()  


python的动态性

动态性与__slots__

通过为类添加方法,可以为所有实例都添加方法

class Cat:
    def __init__(self, name):
        self.name = name
def walk_func(self):
    print('%s慢慢地走过一片草地' % self.name)
d1 = Cat('Garfield')
d2 = Cat('Kitty')
#d1.walk() # AttributeError
# 为Cat动态添加walk()方法,该方法的第一个参数会自动绑定
Cat.walk = walk_func  # ①
# d1、d2调用walk()方法
d1.walk()
d2.walk()
    

动态性的隐患:程序定义好的类,可能在后面被其他程序修改,这就带来了不确定性

__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名

并不限制调用类来动态的添加


class Dog:
    __slots__ = ('walk', 'age', 'name')
    def __init__(self, name):
        self.name = name
    def test():
        print('预先定义的test方法')
d = Dog('Snoopy')
from types import MethodType
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)
d.age = 5
d.walk()
#d.foo = 30 # AttributeError
# __slots__属性并不限制通过类来动态添加方法
Dog.bar = lambda self: print('abc') # AttributeError
d.bar()

class GunDog(Dog):
    def __init__(self, name):
        super().__init__(name)
    pass
gd = GunDog('Puppy')
# 完全可以为Gundog实例动态添加属性
gd.speed = 99
print(gd.speed)


使用type()函数定义类

使用type()查看类的类型是type,程序使用class定义的所有类都是type类的实例

python可以使用type()函数来动态的创建类

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)


type()创建类时需要指三个参数:

  • 创建的类名
  • 该类继承的父类集合
  • 类绑定的类变量和方法的字典 。key是类的变量或方法名,value如果是普通值就代表是类变量;如果是函数,则是方法。

metaclass

如果希望创建某一批类全部具有某种特征,则可以通过metaclass来实现,使用metaclass可以在创建类时动态修改类定义

为了使用metaclass动态修改类定义,程序需要先定义metaclass,metaclass应该继承type类,并重写__new__()方法。


# 定义ItemMetaClass,继承type
class ItemMetaClass(type):
    # cls代表动态修改的类
    # name代表动态修改的类名
    # bases代表被动态修改的类的所有父类
    # attr代表被动态修改的类的所有属性、方法组成的字典
    def __new__(cls, name, bases, attrs):
        # 动态为该类添加一个cal_price方法
        attrs['cal_price'] = lambda self: self.price * self.discount
        return type.__new__(cls, name, bases, attrs)
# 定义Book类
class Book(metaclass=ItemMetaClass):
    __slots__ = ('name', 'price', '_discount')
    def __init__(self, name, price):
        self.name = name
        self.price = price
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self, discount):
        self._discount = discount
# 定义Book类
class CellPhone(metaclass=ItemMetaClass):
    __slots__ = ('price', '_discount' )
    def __init__(self, price):
        self.price = price
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self, discount):
        self._discount = discount

b = Book("疯狂Python讲义", 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())


多态性

同一个变量,在执行同一个方法时,由于变量指向的对象不同,因此呈现出不同的行为特征,这就是多态性

class Bird:
    def move(self, field):
        print('鸟在%s上自由地飞翔' % field)
class Dog:
    def move(self, field):
        print('狗在%s里飞快的奔跑' % field)
# x变量被赋值为Bird对象
x = Bird()
# 调用x变量的move()方法
x.move('天空')
# x变量被赋值为Dog对象
x = Dog()
# 调用x变量的move()方法
x.move('草地')

class Canvas:
    def draw_pic(self, shape):
        print('--开始绘图--')
        shape.draw(self)

class Rectangle:
    def draw(self, canvas):
        print('在%s上绘制矩形' % canvas)
class Triangle:
    def draw(self, canvas):
        print('在%s上绘制三角形' % canvas)
class Circle:
    def draw(self, canvas):
        print('在%s上绘制圆形' % canvas)
c = Canvas()
# 传入Rectangle参数,绘制矩形
c.draw_pic(Rectangle())
# 传入Triangle参数,绘制三角形
c.draw_pic(Triangle())
# 传入Circle参数,绘制圆形
c.draw_pic(Circle())
print(hasattr(c, 'draw_pic'))
print(hasattr(c.draw_pic, '__call__'))
print(Circle.__dict__)


检查类型

python提供两个函数来检查类型:

  • issubclass(cls,class_or_tuple):检查cls是否为后一个类或元组包含的多个类的中任意类的子类
  • isinstance(obj,class_or_tuple):检查obj是否为后一个类或元组包含多个类中任意类的对象
# 定义一个字符串
hello = "Hello";
# "Hello"是str类的实例,输出True
print('"Hello"是否是str类的实例: ', isinstance(hello, str))
# "Hello"是object类的子类的实例,输出True
print('"Hello"是否是object类的实例: ', isinstance(hello, object))
# str是object类的子类,输出True
print('str是否是object类的子类: ', issubclass(str, object))
# "Hello"不是tuple类及其子类的实例,输出False
print('"Hello"是否是tuple类的实例: ', isinstance(hello, tuple))
# str不是tuple类的子类,输出False
print('str是否是tuple类的子类: ', issubclass(str, tuple))
# 定义一个列表
my_list = [2, 4]
# [2, 4]是list类的实例,输出True
print('[2, 4]是否是list类的实例: ', isinstance(my_list, list))
# [2, 4]是object类的子类的实例,输出True
print('[2, 4]是否是object类及其子类的实例: ', isinstance(my_list, object))
# list是object类的子类,输出True
print('list是否是object类的子类: ', issubclass(list, object))
# [2, 4]不是tuple类及其子类的实例,输出False
print('[2, 4]是否是tuple类及其子类的实例: ', isinstance([2, 4], tuple))
# list不是tuple类的子类,输出False
print('list是否是tuple类的子类: ', issubclass(list, tuple))

data = (20, 'fkit')
print('data是否为列表或元组: ', isinstance(data, (list, tuple))) # True
# str不是list或者tuple的子类,输出False
print('str是否为list或tuple的子类: ', issubclass(str, (list, tuple)))
# str是list或tuple或object的子类,输出True
print('str是否为list或tuple或object的子类 ', issubclass(str, (list, tuple, object)))


python为所有类提供了一个__bases__属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组

python还为所有类提供了一个__subclasses__()方法,通过该方法可以查看该类的所有直接子类,该方法返回该类的所有子类组成的列表。

class A:
    pass
class B:
    pass
class C(A, B):
    pass
print('类A的所有父类:', A.__bases__)
print('类B的所有父类:', B.__bases__)
print('类C的所有父类:', C.__bases__)
 
print('类A的所有子类:', A.__subclasses__())
print('类B的所有子类:', B.__subclasses__())

枚举类

实例有限且固定的类叫枚举类

枚举入门

定义方法:

  • 直接使用Enum列出多个枚举值来创建枚举类
  • 通过继承Enum基类来拍摄枚举类
import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)

# 根据枚举变量名访问枚举对象
print(Season['SUMMER']) # Season.SUMMER
# 根据枚举值访问枚举对象
print(Season(3)) # Season.FALL

# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)

上而程序使用 Enum()函数(就是 Enum 的构造方法)来创建枚举类,该构造方法的第一个参 数是枚举类的类名 ; 第二个参数是一个元组,用于列出所有枚举值。 在定义了上面的 Season 枚举类之后,程序可直接通过枚举值进行前问,这些枚举值都是该枚 举的成员,每个成员都有 name、 value 两个属性,其中 name 属性值为该枚举值的变量名, value 代表该枚举值的序号(序号通常从 1开始〉。

程序除可直接使用枚举之外,还可通过枚举变量名或枚举值来访问指定枚举对象

Python 还为枚举提供了一个__members__一属性,该属性返回一个 diet 字典, 字典包含了 该枚举的所有枚举实例。程序可通过遍历 members 属性来访问枚举的所有实例


import enum
class Orientation(enum.Enum):
    # 为序列值指定value值
    EAST = '东'
    SOUTH = '南'
    WEST = '西'
    NORTH = '北'
    def info(self):
        print('这是一个代表方向【%s】的枚举' % self.value)
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)

通过继承 Enum 来派生枚举类,在这种方式下程序就可以为枚 举额外定义方法了

枚举的构造器

枚举也是类,因此枚举也可以定义构造器。 为枚举定义构造器之后,在定义枚举实例时必须为构造器参数设置值


import enum
class Gender(enum.Enum):
    MALE = '男', '阳刚之力'
    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的value
print('FEMALE的value:', Gender.FEMALE.value)
# 访问自定义的cn_name属性
print('FEMALE的cn_name:', Gender.FEMALE.cn_name)
# 访问自定义的desc属性
print('FEMALE的desc:', Gender.FEMALE.desc)

异常处理

python异常处理依赖:try,except,else,finally,raise
try:try关键字后缩进的代码块简称try块,里面放置的是可能引发异常的代码。
except:对应的是异常类型和一个代码块,用于表明该except块处理这种类型的代码块。
else:在多个except块之后可以用放一个else块,表明程序不出现异常时还有执行else块。
finally:用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行。
raise:用于引发一个实际的异常,可以单独作为语句使用,引发一个具体的异常对象

异常处理机制

当程序运行出现意外情况时,系统会自动生成一个Error对象来通知程序,从而实现将“业务实现代码”和“错误处理代码”分离,提供更好的可读性。

使用try…except捕获异常

python提出一种假设:如果程序可以顺利完成,那就‘一切正常’,把系统的业务实现代码放在try块中定义,把所有的异常处理逻辑放在except块张总进行处理

try:
	# 业务实现代码
	...
except (Error1,Error2,...) as e:
	alert 输入不合法
	goto retry

try出现异常,系统自动生成一个异常对象,异常对象被交给python解释器,这个过程被称为引发异常。

当解释器接收到异常对象时,会寻找能处理该异常的except块,找到,就把该异常对象交给该except块处理,这个过程被称为捕获异常,找不到,则运行时环境终止,解释器退出。

# 定义棋盘的大小
BOARD_SIZE = 15
# 定义一个二维列表来充当棋盘
board = []
def initBoard() :
    # 把每个元素赋为"╋",用于在控制台画出棋盘
    for i in range(BOARD_SIZE) :
        row = ["╋"] * BOARD_SIZE
        board.append(row)
# 在控制台输出棋盘的方法
def printBoard() :
    # 打印每个列表元素
    for i in range(BOARD_SIZE) :
        for j in range(BOARD_SIZE) :
            # 打印列表元素后不换行
            print(board[i][j], end="")
        # 每打印完一行列表元素后输出一个换行符
        print()
initBoard()
printBoard()
inputStr = input("请输入您下棋的坐标,应以x,y的格式:\n")
while inputStr != None :
    try:
        # 将用户输入的字符串以逗号(,)作为分隔符,分隔成2个字符串
        x_str, y_str = inputStr.split(sep = ",")
        # 如果要下棋的点不为空
        if board[int(y_str) - 1][int(x_str) - 1] != "╋":
            inputStr = input("您输入的坐标点已有棋子了,请重新输入\n")
            continue
        # 把对应的列表元素赋为"●"。
        board[int(y_str) - 1][int(x_str) - 1] = "●"
    except Exception:
        inputStr = input("您输入的坐标不合法,请重新输入,下棋坐标应以x,y的格式\n")
        continue
    '''
     电脑随机生成2个整数,作为电脑下棋的坐标,赋给board列表
     还涉及
        1.坐标的有效性,只能是数字,不能超出棋盘范围
        2.下的棋的点,不能重复下棋
        3.每次下棋后,需要扫描谁赢了
    '''
    printBoard()
    inputStr = input("请输入您下棋的坐标,应以x,y的格式:\n")

异常类的继承体系

python所有异常类都继承自BaseException

B

import sys
try:
    a = int(sys.argv[1])
    b = int(sys.argv[2])
    c = a / b
    print("您输入的两个数相除的结果是:", c )
except IndexError:
    print("索引错误:运行程序时输入的参数个数不够")
except ValueError:
    print("数值错误:程序只能接收整数参数")
except ArithmeticError:
    print("算术错误")
except Exception:
    print("未知异常")

  • 运行程序是输入的参数不够,将会发生索引错误,python将调用IndexError对应的except块来处理该异常
  • 运行程序是输入的参数不是数字,而是字母,将会发生数值错误,python会焦勇ValueError对应的except块来处理
  • 运行程序时出现其他异常,该异常对象总是Exception类或其子类的实例,python调用Exception对应的except块处理异常

多异常捕获

python的except块可以捕获多种类型的异常

将多个异常类用圆括号括起来,中间用逗号隔开–其实就是构建多个异常的元组

import sys
try:
    a = int(sys.argv[1])
    b = int(sys.argv[2])
    c = a / b
    print("您输入的两个数相除的结果是:", c )
except (IndexError, ValueError, ArithmeticError):
    print("程序发生了数组越界、数字格式异常、算术异常之一")
except:
    print("未知异常")

访问异常信息

要访问异常对象的信息,可通过为异常对象声明变量来实现。当解释器决定调用某个except块来处理异常对象时,会将异常对象赋值给except块后面的异常变量,程序可通过该变量来获得异常对象的相关信息。

异常对象包含常用属性和方法

  • args: 该属性返回异常的错误编号和描述字符串
  • erron:该属性返回异常的错误编号
  • strerror:该属性返回异常的描述字符串
  • with_traceback(): 通过该方法可处理异常的传播轨迹信息

def foo():
    try:
        fis = open("a.txt");
    except Exception as e:
        # 访问异常的错误编号和详细信息
        print(e.args)
        # 访问异常的错误编号
        print(e.errno)
        # 访问异常的详细信息
        print(e.strerror)
foo()

else块

当try没有出现异常时,程序就会执行else块


s = input('请输入除数:')
try:
    result = 20 / int(s)
    print('20除以%s的结果是: %g' % (s , result))
except ValueError:
    print('值错误,您必须输入数值')
except ArithmeticError:
    print('算术错误,您不能输入0')
else:
    print('没有出现异常')

def else_test():
    s = input('请输入除数:')
    result = 20 / int(s)
    print('20除以%s的结果是: %g' % (s , result))
def right_main():
    try:
        print('try块的代码,没有异常')
    except:
        print('程序出现异常')
    else:
        # 将else_test放在else块中
        else_test()
def wrong_main():
    try:
        print('try块的代码,没有异常')
        # 将else_test放在try块代码的后面
        else_test()
    except:
        print('程序出现异常')
wrong_main()
right_main()

对比上面两个输出结果, 不难发现, 放在 else 块中的代间所引发的异常不会被 except 块捕获。 所以, 如果希望某段代码的异常能被后面的 except 块捕款,那么就应该将这段代码放在 try 块的代 码之后; 如果希望某段代码的异常能向外传播(不被 except 块捕获〉,那么就应该将这段代码放在 else 块中。

finally 回收资源

try;
	# 业务实现代码
	...
except SubException as e:
   # 异常处理模块
   ...
...
else:
   # 正常处理块
finally# 资源回收块

import os
def test():
    fis = None
    try:
        fis = open("a.txt")
    except OSError as e:
        print(e.strerror)
        # return语句强制方法返回
#        return        # ①
        os._exit(1)     # ②
    finally:
        # 关闭磁盘文件,回收资源
        if fis is not None:
            try:
                # 关闭资源
                fis.close()
            except OSError as ioe:
                print(ioe.strerror)
        print("执行finally块里的资源回收!")
test()

return 语句 finally执行后退出
os.exit() 退出解释器,不执行finally语句
sys.exit() finally 执行

一般不要在;finally中使用return或raise等方法中止的语句,


def test():
    try:
        # 因为finally块中包含了return语句
        # 所以下面的return语句失去作用
        return True
    finally:
        return False
a = test()
print(a)


使用raise引发异常

raise常用的用法:

  • raise: 单独一个raise。该语句引发当前上下文中捕获的异常,或默认引发RuntimeError
  • raise 异常类:raise后带一个异常类。该语句引发指定异常类的默认实例。
  • raise 异常对象:引发指定的异常对象。

上面三种用法最终都是要引发一个异常实例( 即使指定的是异常类, 实际上也是引发该类的默 认实例), raise 语句每次只能引发一个异常实例。

不管是系统自动 引发的异常, 还是程序员于动引发的异常, Python 解释器对异常的处理没有任何差别。
即使是用户自行引 发的异常, 也可以使用 try… except 来捕获它。当然也可以不管它, 让该异常 向上(先调用者〉 传播,如果该异常传到 Python 解释器,那么程序就会中止。

import traceback
def main():
    try:
        # 使用try...except来捕捉异常
        # 此时即使程序出现异常,也不会传播给main函数
        mtd(3)
    except Exception as e:
        print('程序出现异常:', e)
#        help(e.with_traceback)
        traceback.print_exc()
#        e.with_traceback(e)
    # 不使用try...except捕捉异常,异常会传播出来导致程序中止
    mtd(3)
def mtd(a):
    if a > 0:
        raise ValueError("a的值大于0,不符合要求")
main()

从上面程序可以看到,程序既可在调用 mtd(3)时使用 try… except 来捕获异常, 这样该异常将会 被 except 块捕获,不会传播给调用它的函数; 也可直接调用 mtd(3),这样该函数的异常就会直接传播给它的调用函数, 如果该函数也不处理该异常, 就会导致程序中止。

自定义异常类

用户自定义异常应该继承Exception基类或Exception的子类,基本不需要书写更多的代码,只要指定自定义异常类的父类即可。


class AuctionException(Exception): pass

except 和raise同时使用

在实际应用中对异常可能需要更复杂的处理方式,当一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才能完全处理该异常。也就是说,在异常出席的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次引发异常,让该方法的调用者也能捕获到异常。
为了实现这种通过多个方法协作处理同一个异常的情形,可以在except块中结合raise语句来完成

class AuctionException(Exception): pass
class AuctionTest:
    def __init__(self, init_price):
        self.init_price = init_price
    def bid(self, bid_price):
        d = 0.0
        try:
            d = float(bid_price)
        except Exception as e:
            # 此处只是简单地打印异常信息
            print("转换出异常:", e)
            # 再次引发自定义异常
#            raise AuctionException("竞拍价必须是数值,不能包含其他字符!")  # ①
            raise AuctionException(e)
        if self.init_price > d:
            raise AuctionException("竞拍价比起拍价低,不允许竞拍!")
        initPrice = d
def main():
    at = AuctionTest(20.4)
    try:
        at.bid("df")
    except AuctionException as ae:
        # 再次捕获到bid()方法中的异常,并对该异常进行处理
        print('main函数捕捉的异常:', ae)
main()

如果程序需要将原始异常的详细信息直接传播出去,python也允许用自定义异常对原始异常进行包装,只要将上面 ①号粗体字代码改成

raise AuctionException(e)

raise不需要参数

raise可以不带参数,raise处于except块中,他将会自动引发当前上下文激活的异常 否则,通常默认引发RuntimeError异常

异常传播轨迹

异常对象提供了一个with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹可追踪异常触发的源头,也可看到异常一路触发的轨迹

class SelfException(Exception): pass

def main():
    firstMethod()
def firstMethod():
    secondMethod()
def secondMethod():
    thirdMethod()
def thirdMethod():
    raise SelfException("自定义异常信息")
main()

导入traceback模块后,提供 了两个常用方法

  • traceback.print_exc():将异常传播轨迹信息输出到控制台或指定文件中
  • format_exc():将异常传播轨迹信息转换成字符串。
# 导入trackback模块
import traceback
class SelfException(Exception): pass

def main():
    firstMethod()
def firstMethod():
    secondMethod()
def secondMethod():
    thirdMethod()
def thirdMethod():
    raise SelfException("自定义异常信息")
try:
    main()
except:
    # 捕捉异常,并将异常传播信息输出控制台
    traceback.print_exc()
    # 捕捉异常,并将异常传播信息输出指定文件中
    traceback.print_exc(file=open('log.txt', 'a'))

异常处理规则

成功的异常处理应该实现4个目标:

  • 使程序代码混乱最小化
  • 捕获并保留诊断信息
  • 通知合适的人员
  • 采用合适的方式结束异常活动

python类的特殊方法

python类中有戏方法名、属性名的前后都添加了双下划线,这种方法、属性通常都属于Python的特殊方法和特殊属性,开发者可以通过重写这些方法或直接调用这些方法来实现特殊的功能。最长见的特殊方法就是前面结束的构造方法__init__,开发者可以通过重写勒种的__init__方法来实现自己的初始化逻辑。

常见的特殊方法

repr 对象自我描述

repr()方法由object类提供,所有python类都是object子类,所以python对象都具有__repr__()方法

repr()是一个自我描述的方法,通常用于实现这样一个功能: 直接打印对象时,系统将会输出该对象的‘自我描述’信息,用来告诉外界该对象具有的状态信息。

object类提供的__repr__()方法总是返回该对象实现类的‘类名+object at +内存地址’值,这个返回值并不能真正实现 自我描述 的功能,因此,如果用户需要自定义类能实现 自我描述 的功能,就必须重写__repr__()方法

class Apple:
    # 实现构造器
    def __init__(self, color, weight):
        self.color = color;
        self.weight = weight;
    # 重写toString()方法,用于实现Apple对象的“自我描述”
    def __repr__(self):
        return "Apple[color=" + self.color +\
            ", weight=" + str(self.weight) + "]"
a = Apple("红色" , 5.68)
# 打印Apple对象
print(a)

repr()方法通常返回的字符串格式:

类名[filed1=值1,filed2=值2,...]

del 析构方法

在任何python对象将要被系统回收之时,系统都会自动调用__del__()方法来销毁对象

不要以为对一个变量执行 de!操作, 该变量所引用的对象就会被回收--只有 当对象的引用计数变成 0 时,该对象才会被回收。因此,如果一个对象有多个变量引用它,那么 del 其中一个变量是不会回收该对象的。


class Item:
    def __init__ (self, name, price):
        self.name = name
        self.price = price
    # 定义析构函数
    def __del__ (self):
        print('del删除对象')
# 创建一个Item对象,将之赋给im变量
im = Item('鼠标', 29.8)
#x = im   # ①
# 打印im所引用的Item对象
del im
print('--------------')

__dir__列出对象内部所有属性、方法名

当对某个对象执行dir(object)函数时,实际上就是讲该对象的__dir__()方法返回值进行排序,然后包装成列表。

class Item:
    def __init__ (self, name, price):
        self.name = name
        self.price = price
    def info ():
        pass
# 创建一个Item对象,将之赋给im变量
im = Item('鼠标', 29.8)
print(im.__dir__())  # 返回所有属性(包括方法)组成列表
print(dir(im))  # 返回所有属性(包括方法)排序之后的列表

dict 查看对象存储的属性名和值组成的字典

class Item:
    def __init__ (self, name, price):
        self.name = name
        self.price = price
im = Item('鼠标', 28.9)
print(im.__dict__)  # ①
# 通过__dict__访问name属性
print(im.__dict__['name'])
# 通过__dict__访问price属性
print(im.__dict__['price'])
im.__dict__['name'] = '键盘'
im.__dict__['price'] = 32.8
print(im.name) # 键盘
print(im.price) # 32.8

getattrsetattr

  • __getattribute__(self,name):当程序访问对象的name属性是被自动调用
  • __getattr__(self,name): 当程序访问对象的name属性且该属性不存在是被自动调用
  • __setattr__(self,name,value): 当程序对对象的name属性赋值时被自动调用
  • __delattr__(self,name): 当程序删除对象的name属性是被自动调用
class Rectangle:
    def __init__ (self, width, height):
        self.width = width
        self.height = height
    def __setattr__(self, name, value):
        print('----设置%s属性----' % name)
        if name == 'size':
            self.width, self.height = value
        else:
            self.__dict__[name] = value
    def __getattr__(self, name):
        print('----读取%s属性----' % name)
        if name == 'size':
            return self.width, self.height
        else:
            raise AttributeError
    def __delattr__(self, name):
        print('----删除%s属性----' % name)
        if name == 'size':
            self.__dict__['width'] = 0
            self.__dict__['height'] = 0
           
rect = Rectangle(3, 4)
print(rect.size)
rect.size = 6, 8
print(rect.width)
del rect.size
print(rect.size)

如果程序需要在读取、设置属性之前进行某种拦截处理(比如检查数据是否合法之类的〉, 也 可通过重写__setattr__()或__getattribute__方法来实现。


class User:
    def __init__ (self, name, age):
        self.name = name
        self.age = age
    # 重写__setattr__()方法对设置的属性值进行检查
    def __setattr__ (self, name, value):
        # 如果正在设置name属性
        if name == 'name':
            if 2 < len(value) <= 8 or len(value) > 8:
                self.__dict__['name'] = value
            else:
                raise ValueError('name的长度必须在2~8之间')
            
        elif name == 'age':
            if 10 < value < 60:
                self.__dict__['age'] = value
            else:
                raise ValueError('age值必须在10~60之间')
            
u = User('fkit', 24)
print(u.name)
print(u.age)
#u.name = 'fk' # 引发异常
u.age = 2  # 引发异常

反射相关的属性和方法

如果程序在运行过程中要动态的判断是否包含某个属性、方法,甚至要动态设置某个属性值,可通过反射来实现

动态操作属性

在动态检查对象是否包含某些属性相关的函数:

  • hasattr(obj,name):检查obj对象是否包含名为name的属性或方法
  • getattr(obj,name[,default]):获取obj对象中名为name的属性的属性值
  • setattr(obj,name,value,/):将obj对象的name属性设置为value

class Comment:
    def __init__ (self, detail, view_times):
        self.detail = detail
        self.view_times = view_times
    def info ():
        print("一条简单的评论,内容是%s" % self.detail)
        
c = Comment('疯狂Python讲义很不错', 20)
# 判断是否包含指定的属性或方法
print(hasattr(c, 'detail')) # True
print(hasattr(c, 'view_times')) # True
print(hasattr(c, 'info')) # True
# 获取指定属性的属性值
print(getattr(c, 'detail')) # '疯狂Python讲义很不错'
print(getattr(c, 'view_times')) # 20
# 由于info是方法,故下面代码会提示:name 'info' is not defined
#print(getattr(c, info, '默认值'))
# 为指定属性设置属性值
setattr(c, 'detail', '天气不错')
setattr(c, 'view_times', 32)
# 输出重新设置后的属性值
print(c.detail)
print(c.view_times)

# 设置不存在的属性,即为对象添加属性
setattr(c, 'test', '新增的测试属性')
print(c.test) # 新增的测试属性

def bar ():
    print('一个简单的bar方法')
# 将c的info方法设为bar函数    
setattr(c, 'info', bar)
c.info()

# 将c的info设置为字符串'fkit'
setattr(c, 'info', 'fkit')
c.info()



程序使用 setattr()函数可改变 Python 对象的属性值;如果使用该函 数对 Python 对象设置的属性不存在,那么就表现为添加属性 反正 Python 是动态语言

call 函数执行,调用的方法

用hasattr()函数判断指定属性或方法是否包含__call__属性来确定他是否可调用


class User:
    def __init__(self, name, passwd):
        self.name = name
        self.passwd = passwd
    def validLogin (self):
        print('验证%s的登录' % self.name)        
u = User('crazyit', 'leegang')
# 判断u.name是否包含__call__方法,即判断是否可调用
print(hasattr(u.name, '__call__')) # False
# 判断u.passwd是否包含__call__方法,即判断是否可调用
print(hasattr(u.passwd, '__call__')) # False
# 判断u.validLogin是否包含__call__方法,即判断是否可调用
print(hasattr(u.validLogin, '__call__')) # True

# 定义Role类
class Role:
    def __init__ (self, name):
        self.name = name
    # 定义__call__方法
    def __call__(self):
        print('执行Role对象')
r = Role('管理员')
# 直接调用Role对象,就是调用该对象的__call__方法
r()

def foo ():
    print('--foo函数--')
# 下面示范了2种方式调用foo()函数
# 输出相同
foo()
foo.__call__()

一个函数(甚至对象)之所以能执行,关键就在于__cal__()方法。实际上 x(argl, arg2, … ) 只是 x.call(argl, arg2, …)的快捷写法,因此我们甚至可以为自定义类添加__call__ 方法,从而使 得该类的实例也变成可调用的

序列相关的特殊方法

序列相关方法

  • __len__(self): 该方法的返回值是序列中元素的个数
  • __getitem__(self,key): 该方法获取指定索引对应的元素,该方法的key应该是整数值或slice对象,否则会引发KeyError异常
  • __contains__(self,item): 该方法判断序列是否包含指定元素
  • __setitem__(self,key,value):该方法设置指定索引对应的元素。该方法的key应该是整数值或slice对象,否则会引发KeyError异常。
  • __delitem__(self,key):该方法删除指定索引对应的元素
def check_key (key):
    '''
    该函数将会负责检查序列的索引,该索引必须是整数值,否则引发TypeError
    且程序要求索引必须为非负整数,否则引发IndexError
    '''
    if not isinstance(key, int): raise TypeError('索引值必须是整数')
    if key < 0: raise IndexError('索引值必须是非负整数')
    if key >= 26 ** 3: raise IndexError('索引值不能超过%d' % 26 ** 3)   
class StringSeq:
    def __init__(self):
        # 用于存储被修改的数据
        self.__changed = {}
        # 用于存储已删除元素的索引
        self.__deleted = []
    def __len__(self):
        return 26 ** 3
    def __getitem__(self, key):
        '''
        根据索引获取序列中元素
        '''
        check_key(key)
        # 如果在self.__changed中找到已经修改后的数据
        if key in self.__changed :
            return self.__changed[key]
        # 如果key在self.__deleted中,说明该元素已被删除
        if key in self.__deleted :
            return None
        # 否则根据计算规则返回序列元素
        three = key // (26 * 26)
        two = ( key - three * 26 * 26) // 26
        one = key % 26
        return chr(65 + three) + chr(65 + two) + chr(65 + one) 
    def __setitem__(self, key, value):
        '''
        根据索引修改序列中元素
        '''
        check_key(key)
        # 将修改的元素以key-value对的形式保存在__changed中
        self.__changed[key] = value
    def __delitem__(self, key):
        '''
        根据索引删除序列中元素
        '''
        check_key(key)
        # 如果__deleted列表中没有包含被删除key,添加被删除的key
        if key not in self.__deleted : self.__deleted.append(key)
        # 如果__changed中包含被删除key,删除它
        if key in self.__changed : del self.__changed[key]
# 创建序列
sq = StringSeq()
# 获取序列的长度,实际上就是返回__len__()方法的返回值
print(len(sq))
print(sq[26*26])
# 打印没修改之后的sq[1]
print(sq[1]) # 'AAB'
# 修改sq[1]元素
sq[1] = 'fkit'
# 打印修改之后的sq[1]
print(sq[1]) # 'fkit'
# 删除sq[1]
del sq[1]
print(sq[1]) # None
# 再次对sq[1]赋值
sq[1] = 'crazyit'
print(sq[1]) # crazyit


迭代器

实现迭代器的,需要实现的两个方法:

  • __iter__(slef): 该方法返回一个迭代器(iterator),该迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素
  • __reversed__(self): 该方法主要为内建的reversed()反转函数提供支持,当程序调用reversed()函数对指定迭代器执行反转时,实际上就是由该方法实现的
# 定义一个代表斐波那契数列的迭代器
class Fibs:
    def __init__(self, len):
        self.first = 0
        self.sec = 1
        self.__len = len
    # 定义迭代器所需的__next__方法
    def __next__(self):
        # 如果__len__属性为0,结束迭代
        if self.__len == 0:
            raise StopIteration
        # 完成数列计算:
        self.first, self.sec = self.sec, self.first + self.sec
        # 数列长度减1
        self.__len -= 1
        return self.first
    # 定义__iter__方法,该方法返回迭代器
    def __iter__(self):
        return self
# 创建Fibs对象
fibs = Fibs(10)
# 获取迭代器的下一个元素
print(next(fibs))
# 使用for循环遍历迭代器
for el in fibs:
    print(el, end=' ')

# 将列表转换为迭代器
my_iter = iter([2, 'fkit', 4])
# 依次获取迭代器的下一个元素
print(my_iter.__next__()) # 2
print(my_iter.__next__()) # fkit


拓展列表、元组和字典

拓展字典类,根据value值获取key

# 定义ValueDict类,继承dict类
class ValueDict(dict):
    # 定义构造函数
    def __init__(self, *args, **kwargs):
        # 调用父类的构造函数
        super().__init__(*args, **kwargs)
    # 新增getkeys方法
    def getkeys(self, val):
        result = []
        for key, value in self.items():
            if value == val: result.append(key)
        return result
my_dict = ValueDict(语文 = 92, 数学 = 89, 英语 = 92)
# 获取92对应的所有key
print(my_dict.getkeys(92)) # ['语文', '英语']
my_dict['编程'] = 92
print(my_dict.getkeys(92)) # ['语文', '英语', '编程']

生成器

迭代器通常是先定义一个迭代器类, 然后通过创建实例来创建 迭代器;而生成器则是先定义一个包含 yield 语句的函数,然后通过调用该函数来创建生成器。

创建生成器

  • 定义一个包含yield语句的函数
  • 调用函数得到生成器

def test(val, step):
    print("--------函数开始执行------")
    cur = 0
    # 遍历0~val
    for i in range(val):
        # cur添加i*step
        cur += i * step
        yield cur
#        print(cur, end=' ')
# 执行函数,返回生成器
t = test(10, 2)
print('=================')
# 获取生成器的第一个值
print(next(t)) # 0,生成器“冻结”在yield处
print(next(t)) # 2,生成器再次“冻结”在yield处

for ele in t:
    print(ele, end=' ')

# 再次创建生成器
t = test(10, 1)
# 将生成器转换成列表
print(list(t))
# 再次创建生成器
t = test(10, 3)
# 将生成器转换成列表
print(tuple(t))

yield 语句作用:

  • 每次返回一个值,类似于return语句
  • 冻结执行,程序每次执行到yield语句就会被暂停

调用包含yield语句的函数不会立即执行,只返回一个生成器,只有当程序通过next()函数调用生成器或遍历生成器时函数才会真正执行

创建生成器的方法

  • 使用for循环的生成器推导式 例如:(for i in args)
  • 调用带yield语句的生成器函数

生成器的优势:

  • 当使用生成器来生成多个数据时,程序是按需获取数据的,它不会一开始就把所有数据都 生成出来,而是每次调用 next()获取下一个数据时,生成器才会执行一次,因此可以减少 代码的执行次数。 比如前面介绍的示例,程序不会一开始就把生成器函数中 的循环都执行 完成, 而是每次调用 next()时才执行一次循环体。
  • 当函数需要返回多个数据时,如果不使用生成器,程序就需要使用列表或元组来收集函数返回的多个值,当函数要返回的数据量较大时,这些列表、元组会带来一定的内存开销; 如果使用生成器就不存在这个问题,生成器可以按需、逐个返回数据。
  • 使用生成器的代码更加简洁。

生成器的方法

send()
close() 停止生成器
throw() yield语句内引发一个异常

send()类似next()方法,但可以带参数,用于生成器与外部程序交换数据,外部程序通过send()发送数据,生成器函数使用yield语句接收数据

只有等生成器被冻结 后,才可使用send()向生成器发送数据,获取生成器第一次生成的值,应该用next(),;如果程序非要使用 send()方法 获取生成器第一次所生成的值,也不能向生成器发送数据,只能为该方法传入 None 参数。


def square_gen(val):
    i = 0
    out_val = None
    while True:
        # 使用yield语句生成值,使用out_val接收send()方法发送的参数值
        out_val = (yield out_val ** 2) if out_val is not None else (yield i ** 2)
        # 如果程序使用send()方法获取下一个值,out_val会获取send()方法的参数
        if out_val is not None : print("====%d" % out_val)
        i += 1

sg = square_gen(5)
# 第一次调用send()方法获取值,只能传入None作为参数
print(sg.send(None))  # 0
print(next(sg))  # 1
print('--------------')
# 调用send()方法获取生成器的下一个值,参数9会被发送给生成器
print(sg.send(9))  # 81
# 再次调用next()函数获取生成器的下一个值
print(next(sg))  # 9

# 让生成器引发异常
#sg.throw(ValueError)

# 关闭生成器
sg.close()
print(next(sg)) # StopIteration

运算符重载的特殊方法

与数值运算符相关的特殊方法

  • object.__add__(self,other):加法运算,‘+’
  • object.__sub__(self,other):减法运算,‘-’
  • object.__mul__(self,other):乘法运算,‘*’
  • object._matmul_(self,other):矩阵乘法,‘@’
  • object.__truediv__(self,other):除法运算,‘/’
  • object.__floordiv__(self,other):整除运算,‘//’
  • object.__mod__(self,other):求余运算,‘%’
  • object.__divmod__(self,other):求余运算,‘divmod’
  • object.__pow__(self,other):乘方运算,‘**’
  • object.__lshift__(self,other):左移运算,‘<<’
  • object.__rshift__(self,other):右移运算,‘>>’
  • object.__and__(self,other):按位与运算,‘&’
  • object.__xor__(self,other):按位异或运算,‘^’
  • object.__or__(self,other):按位或运算,‘|’
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__add__方法,该对象可执行+运算
    def __add__(self, other):
        # 要求参与+运算的另一个运算数必须是Rectangle
        if not isinstance(other, Rectangle):
            raise TypeError('+运算要求目标是Rectangle')
        return Rectangle(self.width + other.width, self.height + other.height)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
r2 = Rectangle(3, 4)
# 对两个Rectangle执行加法运算
r = r1 + r2
print(r) # Rectangle(width=7, height=9)


上面介绍的各种数值 运算相关方法,还有一个带 r 的版本。


class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__radd__方法,该对象可出现在+的右边
    def __radd__(self, other):
        # 要求参与+运算的另一个运算数必须是数值
        if not (isinstance(other, int) or isinstance(other, float)):
            raise TypeError('+运算要求目标是数值')
        return Rectangle(self.width + other, self.height + other)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
# r1有__radd__方法,因此它可以出现在+运算符的右边
r = 3 + r1
print(r) # Rectangle(width=7, height=8)



  • object.__iadd__(self,other):加法运算,‘+=’
  • object.__siub__(self,other):减法运算,‘-=’
  • object.__imul__(self,other):乘法运算,‘*=’
  • object._imatmul_(self,other):矩阵乘法,‘@=’
  • object.__itruediv__(self,other):除法运算,‘/=’
  • object.__ifloordiv__(self,other):整除运算,‘//=’
  • object.__imod__(self,other):求余运算,‘%=
  • object.__ipow__(self,other):乘方运算,‘**=’
  • object.__ilshift__(self,other):左移运算,‘<<=’
  • object.__irishift__(self,other):右移运算,‘>>=’
  • object.__iand__(self,other):按位与运算,‘&=’
  • object.__ixor__(self,other):按位异或运算,‘^=’
  • object.__ior__(self,other):按位或运算,‘|=’
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__iadd__方法,该对象可支持+=运算
    def __iadd__(self, other):
        # 要求参与+=运算的另一个运算数必须是数值
        if not (isinstance(other, int) or isinstance(other, float)):
            raise TypeError('+=运算要求目标是数值')
        return Rectangle(self.width + other, self.height + other)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
# r有__iadd__方法,因此它支持+=运算
r += 2
print(r) # Rectangle(width=6, height=7)


比较运算符相关的特殊方法

  • object.____(self,other): <
  • object.__le__(self,other):<=
  • object.__eq__(self,other):==
  • object.__ne__(self,other):!=
  • object.__gt__(self,other):>
  • object.__ge__(self,other):>=
class Rectangle:
   def __init__(self, width, height):
       self.width = width
       self.height = height
   # 定义setSize()函数
   def setSize (self , size):
       self.width, self.height = size
   # 定义getSize()函数
   def getSize (self):
       return self.width, self.height
   # 使用property定义属性
   size = property(getSize, setSize)
   # 定义__gt__方法,该对象可支持>和<比较
   def __gt__(self, other):
       # 要求参与>运算的另一个运算数必须是Rectangle
       if not isinstance(other, Rectangle):
           raise TypeError('>运算要求目标是Rectangle')
       return True if self.width * self.height > other.width * other.height else False
   # 定义__eq__方法,该对象可支持==和!=比较
   def __eq__(self, other):
       # 要求参与==运算的另一个运算数必须是Rectangle
       if not isinstance(other, Rectangle):
           raise TypeError('==运算要求目标是Rectangle')
       return True if self.width * self.height == other.width * other.height else False
   # 定义__ge__方法,该对象可支持>=和<=比较
   def __ge__(self, other):
       # 要求参与>=运算的另一个运算数必须是Rectangle
       if not isinstance(other, Rectangle):
           raise TypeError('>=运算要求目标是Rectangle')
       return True if self.width * self.height >= other.width * other.height else False 
   def __repr__(self):
       return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
r2 = Rectangle(3, 4)
print(r1 > r2) # True
print(r1 >= r2) # True
print(r1 < r2) # False
print(r1 <= r2) # False
print(r1 == r2) # False
print(r1 != r2) # True
print('------------------')
r3 = Rectangle(2, 6)
print(r2 >= r3) # True
print(r2 > r3) # False
print(r2 <= r3) # True
print(r2 < r3) # False
print(r2 == r3) # True
print(r2 != r3) # False



单目运算符相关特殊方法

  • object.__neg__(self): 单目求负, -
  • object.__pos__(self):单目求正, +
  • object.__invert__(self):单目取反,~

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__neg__方法,该对象可执行求负(-)运算
    def __neg__(self):
        self.width, self.height = self.height, self.width
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
# 对Rectangle执行求负运算
-r
print(r) # Rectangle(width=5, height=4)


与类型转换相关的特殊方法

  • object.__str__(self): 对应内置的str()函数,将该对象转换成字符串
  • object.__bytes__(self): 对应内置的bytes()函数,将该对象转换成字节内容,该方法应该返回bytes对象。
  • object.__complex__(self): 对应内置的complex()函数,将该对象转换成复数,该方法应该返回complex对象。
  • object.__int__(self): 对应内置的int()函数,将该对象转换成整数,该方法应该返回int对象。
  • object.__float__(self): 对应内置的float()函数,将该对象转换成浮点数,该方法应该返回float对象。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__int__方法,程序可调用int()函数将该对象转成整数
    def __int__(self):
        return int(self.width * self.height)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
print(int(r)) # 20


常见的内建函数相关的特殊方法

  • object.__format__(self,format_spec):对应调用内建的format()函数将对象转换成格式化字符串
  • object.__hash__(self):对应调用内建的hash()函数来获取hash码
  • object.__abs__(self):对应调用内建的abs()返回绝对值
  • object.__round__(self[,ndigits]):对应调用内建的round()函数执行四舍五入取整。
  • object.__trunc__(self):对应调用内建的trunc()函数执行截断取整。
  • object.__floor__(self):对应调用内建的floor()函数执行向下取整。
  • object.__ceil__(self):对应调用内建的ceil()函数执行向上取整。
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__round__方法,程序可调用round()函数将该对象执行四舍五入取整
    def __round__(self, ndigits=0):
        self.width, self.height = round(self.width, ndigits), round(self.height, ndigits)
        return self
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4.13, 5.56)
# 对Rectangle对象执行四舍五入取整
result = round(r, 1)
print(r) # Rectangle(width=4.1, height=5.6)
print(result) # Rectangle(width=4.1, height=5.6)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值