Python元类及迭代器生成器

getattr__和__getattribute

  • __getattr__是当类调用一个不存在的属性时才会调用getattr魔法函数,他传入的值item就是你这个调用的不存在的值。
  • __getattribute__则是无条件的优先执行,所以如果不是特殊情况最好不要用.
class A(object):
    def __init__(self):
        pass

    def __getattr__(self, item):
        return "Timi"

a=A()
print(a.asd)  #Timi
print(a.haha) #Timi

属性描述符

什么是属性描述符?
描述符是对多个属性运用相同存取逻辑的一种方式,是实现了特性协议的类,这个协议包括了__get__、__set__和__delete__方法来实现属性对象的查找、设置、删除行为。一个类只要是内部定义了方法 get, set, delete 中的一个或多个,就可以称为描述符,描述符的本质是一个类。

属性描述符可分为两类:

  • 非数据描述符:一个类,如果只定义了 get() 或者是__delete__()方法,而没有定义 set()方法,则认为是非数据描述符(即没有定义__set__)
  • 数据描述符:一个类,不仅定义了 get() 方法,还定义 set(), delete() 方法,则认为是数据描述符(即定义了__get__和__set__)。

属性描述符的作用:
描述符的作用是用来代理另外一个类的属性的。

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

    def __get__(self, instance, owner):
        print('__get__:查询属性',self.name)
        return self.value

    def __set__(self, instance, value):
        print('__set__:设置属性',self.name)
        if isinstance(value,(int,str)):
            self.value = value
        else:
            raise TypeError

    def __delete__(self, instance):
        print('刪除屬性',self.name,self.value)

class User():
    weight = Person('weight')
    sex = Person('sex')

    def __init__(self,weight,sex):
        self.weight = weight
        self.sex = sex

if __name__ == '__main__':
    user=User(6,'male')
    print(user.weight)
    print(user.sex)
    del user.sex
__set__:设置属性 weight
__set__:设置属性 sex
__get__:查询属性 weight
6
__get__:查询属性 sex
male
刪除屬性 sex male

上例中Person类就是作为User类的属性存在,这里我们在对修改属性的时候,还对属性的值进行判断,属性类型不为整型或字符串就抛出异常。

元类

元类实际上就是创建类的类

创建元类的两种方法

  • 1、使用函数创建
def create_class(name):
    if name == 'user':
        class User:
            def __init__(self):
                pass
        return User


User=create_class('user')
print(type(User))
user=User()
print(type(user))
  • 2、使用type()创建
    语法:
    type(name, bases, dict)
    第一个参数:name表示类名称,字符串类型
    第二个参数:bases表示继承对象(父类),元组类型,单元素使用逗号
    第三个参数:attr属性,这里可以填写类属性、类方式、静态方法,采用字典格式,key为属性名,value为属性值
class Base_class():
    def __init__(self):
        self.info = 'python'

def speak(self):
    msg='%s speak haha'%(self.name)
    return msg

def __str__(self):
    return 'Hello'

User=type('User',(Base_class,),{'name':'gg','speak':speak,'__str__':__str__})

user=User()
print(type(user)) 
print(user.name)
print(user.info)
print(user.speak())
print(user.__str__())

执行结果:

<class '__main__.User'>
gg
python
gg speak haha
Hello

Metaclass属性

如果在创建类时,想用 MetaClass 元类动态地修改内部的属性或者方法,则类的创建过程将变得复杂:先创建 MetaClass 元类,然后用元类去创建类,最后使用该类的实例化对象实现功能。

如果想把一个类设计成 MetaClass 元类,其必须符合以下条件:

  1.  必须显式继承自 type 类;
    
  2.  类中需要定义并实现 __new__() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 __new__() 方法会自动被执行,用来修改新建的类。
    
#定义一个Metaclass
class ListMeta(type):
    def __new__(cls, name,bases,attrs):
        attrs['name'] = 'list'  #动态添加一个name属性
        return super().__new__(cls,name,bases,attrs)

class MyList(object,metaclass=ListMeta):
    pass

li=MyList()
print(li.name)

利用Metaclass属性功能拓展

class ListMeta(type):
    def __new__(cls, name,bases,attrs):
        def add(a,value): #定义一个add方法
            return a.append(value)
        attrs['add'] = add   #新增一个add方法
        # attrs['add'] = lambda self,value:self.append(value)
        return super().__new__(cls,name,bases,attrs)

class MyList(list,metaclass=ListMeta):
    pass

li=MyList()
print(li.name)
print(li)
li.add(1)
print(li)

使用Metaclass控制实例的创建

  •   只要定义类型的时候,实现__call__函数,这个类型就成为可调用的。换句话说,我们可以把这个类型的对象当作函数来使用,相当于 重载了括号运算符。
    
class ListMeta(type):
	    def __call__(self, *args, **kwargs):
        if len(args)== 0 or args[0] != 'a':
            raise TypeError('parameter mistake')
        else:
            print('')

class MyList:
    pass

class YouList:
    pass

class Use_c1:
    lst = {"1":MyList,"2":YouList}

    def __new__(cls, name):
        if name in cls.lst:
            return cls.lst[name]()
        else:
            raise NameError('Not have this name')


li=Use_c1('2')

Python迭代器

迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来.可用for遍历任何可迭代对象。
迭代提供了一种通用的不依赖索引的迭代取值方式,可以作用于next()函数。

可迭代对象

可以用for循环遍历的对象都是可迭代对象。

  • str,list,tuple,dict,set等都是可迭代对象。
  • generator,包括生成器和带yield的生成器函数。

判断对象是否可迭代

  • 看内置是否含有__iter__方法。
  • 使用 isinstance() 判断一个对象是否是 Iterable 对象。

迭代器对象

• 有内置的__next__()方法的对象,执行该方法可以不依赖索引取值
• 有内置的__iter__()方法的对象,执行迭代器的__iter__()方法得到的依然是迭代器本身
可迭代对象不一定是迭代器

iter()

可以被next()函数调用并不断返回下一个值的对象称为迭代器。可以通过iter()方法将可迭代的对象,转为迭代器。
next()只能顺延调用,不能往前。

可迭代对象与迭代器区别

  • 可用于for循环的都是可迭代类型
  • 作用于next()都是迭代器类型
  • list、dict、str等都是可迭代的但不是迭代器,因为next()函数无法调用它们。可以通过iter()函数将它们转为迭代器
  • python的for循环本质就是通过不断调用next()函数实现的

for循环迭代器的原理

li = [1, 2, 3, 4]
lis = iter(li)
while True:
	try:
		print(next(lis))
	except StopIteration:
		break

生成器

生成器的定义:
在Python中,一边循环一边计算的机制,称为生成器:generator。

为什么要有生成器

  • 对比迭代器和生成器占用内存大小
import os
import psutil

def show_memory_info(args):
    '''显示当前程序占用内存的大小'''
    pid = os.getpid()  #获取pid
    p = psutil.Process(pid)

    info = p.memory_full_info()
    memory = info.uss / 1024 /1024
    print('{} memory used :{} MB'.format(args,memory))

def test_iterator():
    show_memory_info("initing iterator")
    list_1 =[i for i in range(1000000)]
    show_memory_info('after iterator inited')
    print(sum(list_1))
    show_memory_info('after sum called')

def test_generator():
    show_memory_info("initing generator")
    list_2 =(i for i in range(1000000))
    show_memory_info('after generator inited')
    print(sum(list_2))
    show_memory_info('after sum called')

test_iterator()
print('~~~~~~~~~~~~~~~~~~')
test_generator()

执行结果:

initing iterator memory used :6.484375 MB
after iterator inited memory used :45.17578125 MB
499999500000
after sum called memory used :45.203125 MB
~~~~~~~~~~~~~~~~~~
initing generator memory used :6.79296875 MB
after generator inited memory used :6.79296875 MB
499999500000
after sum called memory used :6.79296875 MB

结果发现当我们使用迭代器生成元素后,元素都会保存在内存中,如果增加元素的个数至十亿,内存可能会出现OOM错误。(OOM,Out of memory,翻译过来大意是内存用完了)。而使用生成器则不会产生这样的现在。如果我们既要大量的数据,又想要使其占用的内存少,就可以使用生成器。

如何创建生成器

方法一:
把一个列表生成式的[]改成(),就创建了一个generator:

gen =(i for i in range(1000))
print(type(gen))
#<class 'generator'>

方法二:
如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象。
其工作原理就是通过重复调用next()或者__next__()方法,直到捕获一个异常。
yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行。

使用生成器实现实现斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,12,21,34…

def number():
    a,b =0,1

    for i in range(10):
        yield b
        a,b = b,a+b

if __name__ == '__main__':
    goal=number()
    for i in goal:
        print(i)	

读取大文件

文件300G,文件比较特殊,一行 分隔符 {|}

def readlines(f,newline):
    buf = ""
    while True:
        while newline in buf:
            pos = buf.index(newline) #获取分隔符的位置
            yield buf[:pos] #截取到分隔符的内容
            buf = buf[pos + len(newline):]
        chunk = f.read(4096*10) #每次读取文件大小
        if not chunk: #当最后一次读取文件内容不到4096*10时
            yield buf
            break
        buf += chunk
        
if __name__ == '__main__':
    with open('demo.txt') as f:
        for line in readlines(f,"{|}"):
            print(line)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值