Python开发-029_面向对象的成员

通过面向对象进行编程时,会遇到很多种情况,也会使用不同的成员来实现,接下来我们来逐一介绍成员特性和应用场景

1 变量

image-20220601150436250

对于类来说,变量其实有两种:

  • 实例变量:属于对象,每个对象中各自封装维护的数据
  • 类变量:属于类,可以被所有对象共享,一般用于给对象提供公共数据
class Person:
	country = "中国" # 类变量
    def __init__(self,name,age):
        self.name = name # 实例变量 -> 需要实例化后可用
        self.age = age
    def show(self):
        message_class = Person.country # 调用类变量
        message_name = self.name # 调用实例变量

print(Person.country) # 成员变量可通过类.变量名使用
p1 = Person('kinght',22) # 实例变量需要先进行实例化
print(p1.name)
print(p1.country) # 成员变量也可以通过,对象名.变量名使用

提示:当把每个对象中都存在的相同的示例变量时,可以选择把它放在类变量中,这样就可以避免对象中维护多个相同数据

1.1 类变量和实例变量读的优先级

class Person:
    country = "中国" # 类变量
    age = 18
    def __init__(self,name,age):
        self.name = name # 实例变量 -> 需要实例化后可用
        self.age = age

p1 = Person('kinght',22)
print(p1.age) 
print(Person.age)

如果类变量和实例变量有重复的情况,p1.age那么肯定从实例变量读取

1.2 类变量和实例变量写的注意事项

class Person:
    country = "中国" # 类变量
    age = 18
    def __init__(self,name,age):
        self.name = name # 实例变量 -> 需要实例化后可用
        self.age = age

p1 = Person('kinght',22)

p1.age = 25 # 在p1中修改实例变量 age = 25 没有修改类变量
print(p1.age) # 25 实例变量改变
print(Person.age) # 18 类变量不会被改变

Person.country = "China" # 修改了类变量
print(p1.country) # China 调用类变量被改变
print(Person.country) # China 如果实例中没有country,则调用的就是类变量

p1.country = "英国" # 在实例中新增定义一个country(新增在p1的__init__中)
print(p1.country) # 英国 有实例变量就是调用实例变量了
print(Person.country) # China 不会影响类变量

1.3 继承关系中的读写

image-20220604124051706

class Base:
    country = "中国"

class Person(Base):
    age = 18
    def __init__(self,name,age):
        self.name = name # 实例变量 -> 需要实例化后可用
        self.age = age

p1 = Person('kinght',24)

# 读 -> 他们读取的都是父类Base中的变量country
print(Base.country,id(Base.country)) # 中国 140455094750640
print(Person.country,id(Person.country)) # 中国 140455094750640
print(p1.country,id(p1.country)) # 中国 140452947004848

# 写 -> 写是写在各自的空间

# 修改父类变量,其他类中没有country,都会读取父类的country,所以都会修改
Base.country = 'china.base'
print(Base.country,id(Base.country)) # china.base 140349062925168
print(Person.country,id(Person.country)) # china.base 140349062925168
print(p1.country,id(p1.country)) # china.base 140349062925168

# 修改类变量,而实例变量中没有自己的country,则会读取类变量,父类中不会改变
Person.country = 'china.person'
print(Base.country,id(Base.country)) # china.base 140349062925168
print(Person.country,id(Person.country)) # china.person 140349062925360
print(p1.country,id(p1.country)) # china.person 140349062925360

# 修改实例变量的,则不会影响类变量和父类变量
p1.country = 'china.p1'
print(Base.country,id(Base.country)) # china.base 140658973002544
print(Person.country,id(Person.country)) # china.person 140349062925360
print(p1.country,id(p1.country)) # china.p1 140658973002672

2 方法

在最初学习类的时候,我们了解到了绑定方法,但是方法其实也有三种,并且依照归属关系,他们都属于类的成员

2.1 绑定方法

默认有一个self参数,可以调用实例变量,也可以向上查找调用类变量

由对象进行调用(此时self就等于调用方法的这个对象)【对象&类均可调用(类调用需要手动传self)】

class Foo(object):
	def f1(self):
		print("绑定方法", self.name)
      
# 绑定方法(对象)
obj = Foo("kinght",20)
obj.f1() # Foo.f1(obj)

2.2 类方法

默认有一个cls参数,可以调用类变量,不能调用实例变量

用类或对象都可以调用(此时cls就等于调用方法的这个类)【对象&类均可调用】

class Foo(object):
	@classmethod
	def f2(cls):
		print("类方法", cls)
        
# 类方法
Foo.f2()  # cls就是当前调用这个方法的类(类)
obj.f2()  # cls就是当前调用这个方法的对象的类。

2.3 静态方法

无默认参数,也不能使用类变量和实例变量

用类和对象都可以调用。【对象&类均可调用】

class Foo(object):
	@staticmethod
	def f3():
		print("静态方法")
        
# 静态方法
Foo.f3()  # 类执行执行方法(类)
obj.f3()  # 对象执行执行方法

在Python中比较灵活,方法都可以通过对象和类进行调用;而在java、c#等语言中,绑定方法只能由对象调用;类方法或静态方法只能由类调用

在函数的定义过程中,我们通常是遵循需要什么,参数传递什么,如果函数不需要使用self空间,我们就不传递给他,就可以使用类方法或者静态方法,如果函数连类中函数都不需要使用,那么直接使用静态方法

class Person:
    country = 'Person.country'
    def __init__(self):
        self.country = 'self.country'

    # 绑定方法,是需要使用self空间的方法
    def F1(self):
        print(self.country)
    # 类方法,是需要使用类中函数的方法
    @classmethod
    def F2(cls):
        print(cls.country)
    # 静态方法,是既不需要使用类也不需要使用self空间的方法
    @staticmethod
    def F3():
        print("f3.country")
obj = Person()
obj.F1() # self.country
Person.F2() # Person.country
Person.F3() # f3.country

3 属性

属性的作用是读写删除的控制,实现过程其实就是在绑定方法上面加上装饰器,@property代表可读,@函数名.setter代表可写,@函数名.deleter代表可删除

3.1 @property

不过由于可读可写可执行,是不添加属性就默认可以的,所以最直观的感受其实是,执行方法不用加括号了

class Foo:
    def __init__(self,name):
        self.name = name
    @property
    def f1(self):
        print(self.name)
v1 = Foo('kinght')
v1.f1 # kinght 这里调用绑定函数f1不需要加括号

此属性在函数库中被大量使用,例如requests

import requests

res = requests.get(
    url="https://skystarry-1251157247.cos.ap-chengdu.myqcloud.com/img/1.png",
    headers={
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS"
    }
)
print(type(res)) # <class 'requests.models.Response'>

在requests.get运行后会将所有的数据存放于requests.models.Response类中,我们前往此类查看代码

20220606001

在类中,有很多绑定的属性,其中就由我们比较熟悉的

image-20220606141554071

我们通常在调用它的时候是不会加括号的

import requests
res = requests.get(
    url="https://skystarry-1251157247.cos.ap-chengdu.myqcloud.com/img/1.png",
    headers={
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS"
    }
)
with open("aaa.png",mode='wb') as file:
    file.write(res.content) # 在这里使用就不需要加括号

3.2 @函数名.setter

此属性是可写,如果一个函数添加了@property,他就默认变为只可读取的函数,不可写入,但是如果函数连读取都不能,就不存在被写入了,所以需要提前写明函数可读取@property

class C(object):
    @property
    def x(self,a):
        print(a)
obj = C()
obj.x(1)

# TypeError: x() missing 1 required positional argument: 'a'

这个时候,我们就需要在他的下方重新定义一次,并且添加@函数名.setter装饰器(由于python是解释型语言,执行过程从上而下,所以并不能使用装饰器叠加的方式,否则python解释器找不到x在哪)

class C(object):
    @property
    def x(self):
        print(1)
    @x.setter
    def x(self,a):
        print(a)
obj = C()
obj.x = 123 # 得到执行的其实是@x.setter下方的函数

3.3 @函数名.deleter

此属性是函数可删除,同样,删除函数的前提是函数可读取

class C(object):
    @property
    def x(self):
        pass
    @x.deleter
    def x(self):
        a = 2
        b = 1
        return a,b
obj = C()
del obj.x

3.4 基于定义变量定义属性

其实这种属性最大的作用可能就是在不同的方式下调用不同的函数了,而使用property可以快速定义

class C(object):
    def getx(self): 
		pass
    def setx(self, value): 
		pass
    def delx(self): 
		pass
    # 依次定义调用 (顺序为依次,如果没有可以使用None代替)
    # obj.x 使用属性@property 调用getx
    # obj.x = 123 使用属性@setx.setter 调用setx
    # del obj.x 使用属性@delx.deleter 调用delx
    # "I'm the 'x' property." 则是注释作用
    x = property(getx, setx, delx, "I'm the 'x' property.")
    
obj = C()

obj.x
obj.x = 123
del obj.x

3.5 属性名称不要实例变量重名

其实这一点很好理解,在实例化完成后,调用self中变量的方式和调用属性proerty定义过的函数方式是相同的,如果重名了,解释器就不知道到底调用的是哪一个了

class C(object):
    def __init__(self):
        self.name = 123
    @property
    def name(self):
        return 456
A = C()
print(A.name) # ?到底选择的是哪一个呢?

如果想要在函数和实例变量中,名称含义上有所联系,通常的做法是

class C(object):
    def __init__(self):
        self._name = 123 # 通常的做法是在实例变量的名字前加一个单下划线_
    @property
    def name(self):
        return 456
A = C()
print(A.name)

4 成员修饰符

Python中成员的修饰符就是标注该成员是公有还是私有

  • 公有,在任何地方都可以调用这个成员
  • 私有,只有在类的内部才可以调用改成员(成员是以两个下划线开头,则表示该成员为私有)

对于成员修饰符而言,变量名开头是双下划线的__都是私有变量,而规则对于变量、方法、属性都是一样的

4.1 对于变量而言

class Foo(object):
    def __init__(self,name,age):
        self.name = name
        self.__age = age # 设置为私有
    def get_data(self):
        return self.name
    def get_age(self):
        return self.__age # 只有内部可以调用
obj = Foo('kinght','24')
print(obj.name) # kinght 公有成员可访问
print(obj.get_data()) # kinght 公有成员外部内部都可调用
# print(obj.__age) # 私有成员外部不可访问
print(obj.get_age()) # 24 但是可以调用类中的方法进行获取变量

4.2 对于方法而言

class Foo(object):
    def __init__(self,name,age):
        self.name = name
        self.__age = age # 设置为私有
    def __get_data(self): # 设置为私有
        return self.name
    def get_age(self):
        return self.__get_data() # 私有方法只有内部能够调用,此处返回了self.__get_data()的返回值
obj = Foo('kinght',22)
# obj.__get_data() # 外部无法调用私有方法
asd = obj.get_age()
print(asd)

4.3 对于属性而言

class Foo(object):
    @property
    def __name(self):
        print("私有属性")
    @property
    def proxy(self):
        print("公有苏醒,调用私有属性")
        self.__name
obj = Foo()
# obj.__name # 私有属性无法调用
obj.proxy # 公有属性可以调用

4.4 父类中的私有成员,子类无法继承

class Base(object):
    def __data(self):
        print('Base.__data')
    def num(self):
        self.__data()
class Foo(Base):
    def func(self):
        # self.__data() # 报错 因为__data是Base中的私有变量
        self.num() # 成功运行因为是通过Base.num运行的__data
obj = Foo()
obj.func()

4.5 强行访问私有成员的方法

按理说私有成员是无法被外部调用,但如果用一些特殊的语法也可以(Flask源码中有这种写法,大家写代码不推荐这样写)

# 特殊语法(以私有变量为例,实际私有属性和私有方法也可通过此方法访问)
'''
对象名._类名__私有变量名
'''
class Foo(object):
    def __init__(self):
        self.__num = 123
        self.age = 23
obj = Foo()
print(obj.age) # 公有成员可访问
# print(obj.__num) # 私有成员访问报错
print(obj._Foo__num) # 强行访问私有成员

4.6 什么时候使用私有?

成员是否可以作为独立的功能暴露给外部,让外部调用并使用

  • 可以,公有。
  • 不可以,内部其他放的一个辅助,私有。

5 面向对象的嵌套

在基于面向对象进行编程时,对象之间可以存在各种各样的关系,例如:组合、关联、依赖等(主要在Java中和设计模式中的称呼),对于python而言他们就是各种嵌套

5.1 从函数内部添加

以班级和学生的关系为例,我们在班级中创建一个列表,将学生对象加入班级对象中

class Student(object):
    '''学生类'''
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def message(self):
        data = "我是一名学生,名叫{},今年{}岁".format(self.name,self.age)
        print(data)
        
        
class Classes(object):
    '''班级类'''
    def __init__(self,title):
        self.title = title
        self.student_list = []
        
    # 在类中定义将另一个类的对象加入self
    def add_student(self,stu_object):
        '''单个添加学生对象'''
        self.student_list.append(stu_object)
    def add_students(self,stu_object_list):
        '''批量添加学生对象'''
        for stu in stu_object_list:
            self.student_list.append(stu)
            
    def show_member(self):
        for item in self.student_list:
            item.message()
            
            
# 定义学生
s1 = Student('kinght',24)
s2 = Student('AYM',24)
s3 = Student('xin',19)
s4 = Student('yi',24)

# 定义班级
c1 = Classes('starry')

# 学生加入班级
c1.add_student(s1)
c1.add_student(s2)
c1.add_students([s3,s4])

# 显示班级学生信息
c1.show_member()

5.2 通过参数进行添加

而这里,我们将在生成学生对象时就为他绑定班级

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.name,self.age,self.class_object.title)
        print(data)

class Classes(object):
    def __init__(self,title):
        self.title = title


# 定义班级
c1 = Classes("信息安全")
c2 = Classes("软件开发")

user_object_list = [
    Student('kinght','24',c1), # 在生成学生对象时,传入班级对象的地址
    Student('AYM','24',c1),
    Student('xu','23',c2),
    Student('yi','25',c2)
]

for obj in user_object_list:
    obj.message()

5.3 多层函数的嵌套

除了班级和学生的关系,在此我们将加入学校和班级的关系

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.name,self.age,self.class_object.school.name,self.class_object.title)
        # 多层调用 self.class_object.school.name # 通过self.class_object找到Classes.school在找到School.name
        print(data)

class Classes(object):
    def __init__(self,title,school_object):
        self.title = title
        self.school = school_object

class School(object):
    def __init__(self,name):
        self.name = name

# 定义学校
s1 = School("四川托普学院")
s2 = School("西南交大希望学院")

# 定义班级
c1 = Classes("信息安全",s1)
c2 = Classes("软件开发",s2)

user_object_list = [
    Student('kinght','24',c1),
    Student('AYM','24',c1),
    Student('xu','23',c2),
    Student('yi','25',c2)
]

for obj in user_object_list:
    obj.message()

6 特殊成员

在Python的类中存在一些特殊的方法,这些方法都是 __方法__ 格式,这种方法在内部均有特殊的含义,接下来我们来讲一些常见的特殊成员

6.1 __init__初始化方法

此方法在实例化对象的时候会在内存中开辟一块空间存放数据,这块空间专属于独立的对象,在全局中调用这块空间中的变量对象名.变量名,而如果要在方法中使用这块空间中的变量,这种方式就会被局限,所以在方法中这块儿空间的名称被指为self,而调用变量则是self.变量名

class Foo(object):
    def __init__(self, name):
        self.name = name
obj = Foo("kinght")

6.2 __new__构造方法

构造方法的目的是帮助我们实例化对象,在实例化对象的过程中,第一步是创建一个空的对象,这是__new__方法的功能,然后才是执行__init__进行初始化给空间写入数据

class Foo(object):
    def __init__(self):
        print("执行对象空间初始化")
    def __new__(cls, *args, **kwargs):
        print("创建对象空间")
        return object.__new__(cls)
obj = Foo()

平时写代码的时候不会去书写__new__方法,是因为默认父类object中包含了__new__方法,会自动进行调用

image-20220615231249950

6.3 __call__

在实例化对象的时候,会默认运行__new__方法和__init__方法,而在运行对象的时候,会默认执行__call__方法

class Foo(object):
    def __call__(self, *args, **kwargs):
        print("执行call方法")

obj = Foo()
obj()

6.4 __str__

它的功能是将对象转换成字符串,主要用于需要展示对象中的值,在拥有__str__的类中,可以执行str(对象名)将对象转为字符串,转换结果是__str__返回的值

class Student(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __str__(self):
        return "{}-{}".format(self.name,self.age)
s1 = Student('kinght',23)
print(str(s1)) # 结果 kinght-23
print(s1) # 直接输出,在有__str__的情况下默认执行 str(s1) 结果 kinght-23

6.5 __dict__

它的功能是将__init__的值,按照字典的格式进行生成出来,主要用于前后端分离的项目

class Foo(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
s1 = Foo('kinght',23)
print(s1.__dict__) # {'name': 'kinght', 'age': 23}

6.6 __getitem____setitem____delitem__

这个和属性有一点相似,只不过属性是通过括号调用,而它们三个使用中括号调用

通过不同的调用方式,指向不同的方法

'''
s1['x1'] 指向 __getitem__ 
s1['a1'] = 'a2' 指向 __setitem__
del s1['ss'] 指向 __delitem__
'''
class Foo(object):
    def __getitem__(self, item):
        print("这里是__getitem__的值:{}".format(item))
    def __setitem__(self, key, value):
        print("这里是__setitem__的值:key:{} value:{}".format(key,value))
    def __delitem__(self, key):
        print("这里是__delitem__的值:{}".format(key))
s1 = Foo()
s1['x1'] # 这里是__getitem__的值:x1
s1['a1'] = 'a2' # 这里是__setitem__的值:key:a1 value:a2
del s1['ss'] # 这里是__delitem__的值:ss

6.7 __enter____exit__上下文管理

前文提到过,python进行文件操作,需要文件句柄 = open('文件名',mode='读取模式')打开一个句柄,操作完成后使用句柄.close()关闭

而它还有一个简写方法,其实对应的就是 __enter____exit__

with open('文件名',mode='rt') as file:
    pass

我们也可以让自己的函数能够使用with...as....

class Foo(object):
    def __init__(self,name,list_name):
        self.name = name
        self.list_name = list_name
    def __enter__(self):
        print("{}文件开始".format(self.name))
        return self.list_name
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("{}文件执行完成".format(self.name))

obj = Foo('123.txt',1)
with obj as data: # 打开obj对象直接执行__enter__方法,并将返回值交给data
    print("这是{}号文件".format(data)) # data输出的是__enter__方法返回值
    # 执行完成with缩进内部语句后自动执行__exit__方法
    
'''执行结果
123.txt文件开始
这是1号文件
123.txt文件执行完成
'''

并且,此操作其实可以将实例化的步骤直接简化在with ... as ... 中进行

with Foo('123.txt',2) as data:
    print("这是{}号文件".format(data))

如果with...as...中对象需要调用类中的方法,需要将self__enter__中返回

class Context:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass
    def do_something(self):
        print("11")
with Context() as ctx:
    ctx.do_something()

这个方法成员作用还比较大,如果我们要做一个固定的操作,就可以将代码写入到其中

'''例如操作数据库
在对数据库进行操作的时候,我们每次都需要经过三个步骤
1.连接数据库
2.操作数据库
3.关闭数据库链接
'''
# 伪代码案例
class SqlHelper(object):
    def __enter__(self):
        self.连接 = 连接数据库
        return 连接
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.连接.关闭
with SqlHelper() as 连接:
	连接.操作

with ... as ...的语法一般被称为上下文管理

6.8 __add__

注意:之所以是__add__等,是因为除了相加还有减乘除平方等

对象不是一个值,它默认是不能进行加减乘除操作的

class Foo(object):
    def __init__(self,name):
        self.name = name
class Boo(object):
    def __init__(self,name):
        self.name = name
s1 = Foo('kinght')
s2 = Boo('aym')
s3 = s1+s2
print(s3)

image-20220617015543884

可以在类中添加一个__add__让其变得可加

class Foo(object):
    def __init__(self,name):
        self.name = name
    def __add__(self, other):
        return "{}-{}".format(self.name,other.name)
class Boo(object):
    def __init__(self,name):
        self.name = name
s1 = Foo('kinght')
s2 = Boo('aym')
s3 = s1+s2
print(s3)

在系统发现有相加操作的时候,会在加号前面的那个对象中运行__add__,并且将加号后面的对象/值,传入到other这个形参中

在这里传入的就是s2的内存地址,所以执行self.name + other.name实际执行的就是self.name + s2.name

image-20220617020207358

注意:这里的顺序非常重要,必须是加号前面的那个对象有__add__方法

7 迭代器、生成器、可迭代对象

__iter__生成器与推导式章节中讲到过,不过那个时候没有接触到面向对象的概念,所以我们需要对他进行详细梳理

7.1 迭代器

迭代器其实是指定义了__iter____next__方法的类,通过迭代器实例化的对象,叫做迭代器对象

'''
__iter__ 方法需要返回对象本身,即:self
__next__ 方法,返回下一个数据,如果没有数据了,则需要抛出一个StopIteration的异常
官方文档:https://docs.python.org/3/library/stdtypes.html#iterator-types
'''
class Foo(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

# 实例化迭代器对象
obj = Foo()

他们的取值方式分为两种,都是取__next__返回的值

  • 使用__next__()next()Foo.__next__()返回的值
print(obj.__next__())
print(next(obj))
# print(next(obj)) # 取值运行到__next__执行raise StopIteration()代码则会报错StopIteration
  • 使用for循环取值,for首先会去寻找__iter__()获取它的返回值,随后反复执行__iter__()返回值即self.__next__()将其return交给item,如果遇到raise StopIteration()代码,则终止运行
# for循环内部在循环时,先执行__iter__方法,获取一个迭代器对象,然后不断执行的next取值(有异常StopIteration则终止循环)
for item in obj:
    print(item)

7.2 生成器

生成器是一个函数的概念,主要是将函数输出做成信息流,而不会大量占用内存

def func():
    a = 1
    while True:
        yield a
        a += 1
v1 = func() # 实例化生成器对象
print(v1.__next__())
print(next(v1))
for item in v1:
    print(item)

但是,它同样也有实例化的一个过程,是因为生成器对象是根据内置生成器函数generator创建的对象,generator的内部也声明了:__iter____next__ 方法,所以它也支持next()\__next__\for ... in ... 语法

如果按照迭代器的规定来看,其实生成器类也是一种特殊的迭代器类(生成器也是一种特殊的迭代器

7.3 可迭代对象

如果一个类中有__iter__方法且返回一个迭代器对象 ;则我们称以这个类创建的对象为可迭代对象

class IT(object):
    def __init__(self):
        self.name = 1
    def __iter__(self):
        return self.name
    def __next__(self):
        self.name += 1
        if self.name == 3:
            raise StopIteration()
        return self.name
class Foo(object):
    def __iter__(self):
        return IT() # 这里需要返回迭代器对象(生成器对象)

obj1 = IT() # 迭代器对象
obj2 = Foo() # 可迭代对象

可迭代对象是可以使用for来进行循环,在循环的内部其实是先执行__iter__方法,获取其迭代器对象,然后再在内部执行这个迭代器对象的__next__功能,逐步取值

我们之前完成一个简易的range函数,他其实就是定义的一个可迭代对象

image-20220618010843212

我们生成一个range(),使用dir()查看其生成对象v1的属性列表,可以发现它支持__iter__方法,而不支持__next__方法,我们使用v1.__iter__即可获取内部的迭代器对象v2,即可认为他是一个可迭代对象

根据此发现,我们复写一个xrange,只能生成从0开始递加整数的函数

class IterRange(object):
    def __init__(self,max_num):
        self.max_num = max_num
        self.counter = -1 # num初始值为-1
    def __iter__(self):
        return self
    def __next__(self):
        self.counter += 1
        if self.counter == self.max_num:
            raise StopIteration()
        return self.counter

class Xrange(object):
    def __init__(self,max_num):
        self.max_num = max_num
    def __iter__(self):
        return IterRange(self.max_num) # 引入迭代器对象

v1 = Xrange(10)
for item in v1:
    print(item)

由于生成器也是一种特殊的迭代器,所以也可以用于可迭代对象返回值

class Xrang(object):
    def __init__(selmf,max_num):
        self.max_num = max_nu
    def __iter__(self):
        counter = 0
        while self.max_num > counter:
            yield counter
            counter += 1
v1 = Xrang(10)
for item in v1:
    print(item)

7.4 内置函数判断可迭代对象、迭代器对象

还记得我们之前说到的字符串、列表、元组、字典、集合、文件对象都是可迭代对象吗?我们现在可以进行一波验证

我们推断的方法

image-20220618013752752

这样的办法实在太慢,Python其实内置了一个函数库可以快速的判断

from collections.abc import Iterator,Iterable

v1 = (1,2,3,4,5)
print(isinstance(v1,Iterable)) # True 判断是否是可迭代对象
print(isinstance(v1,Iterator)) # False 判断是否是迭代器对象

v2 = v1.__iter__()
print(isinstance(v2,Iterable)) # True 判断是否是可迭代对象(由于迭代器对象中也有__iter__方法,所以返回也是True)
print(isinstance(v2,Iterator)) # True 判断是否是迭代器对象

他们的判断依据其实和我们推断的方法是一样的,就是判断是否存在__iter____next__方法


Python开发-029_面向对象的成员
http://localhost:8080/archives/9DP7up57
作者
kinght
发布于
2024年11月11日
更新于
2024年11月11日
许可协议