14、高级编程技巧

一、深入类和对象

鸭子类型和多态
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”

动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

所谓多态:定义时的类型和运行时的类型是不一样,此时就称为多态。

抽象基类(abc模块)
抽象基类(abstract base class,ABC):抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。

抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。

应用场景:
(1)判断某个对象的类型;

class Demo(object):
    def __init__(self,names):
        self.names = names

    # def __len__(self):
    #     return len(self.names)

    def __iter__(self):
        pass

    def test(self):
        pass


d = Demo(['juran', 'python'])

print(len(d))

print(hasattr(d, "test"))

from collections.abc import Sized,Iterable

print(isinstance('demo', int))

print(isinstance(d, Iterable))

(2)我们需要强制某个子类必须实现某些方法

import abc

class CacheBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get(self, key):
        # raise ValueError
        pass
        # self.key = key

    @abc.abstractmethod
    def set(self, key, value):
        # raise NotImplementedError
        pass


class RedisBase(CacheBase):
    # 重写父类中的方法
    # def get(self, key):
    #     pass

    def set(self, key, value):
        pass

r = RedisBase()
# r.get('juran')

使用isinstance和type的区别:

i=1
s='a'
isinstance(i,int)
isinstance(s,str)
isinstance(s,int)

print(type(i))


if isinstance(i, int):
# if type(i) == 'int':
    print(123)


class A:
    pass


class B(A):
    pass


b = B()
print(isinstance(b, B))
print(isinstance(b, A))     # True  考虑类的继承关系


print(type(b) is B)
print(type(b) is A)         # False  没有考虑类的继承关系
# == value
# 和 is  内存地址
# 区别

类变量和实例变量:

class A:
    # 类属性
    aa = 1

    # 实例方法
    def __init__(self, x, y):
        # 实例属性
        self.x = x
        self.y = y
        # self.aa = 22


a = A(1, 2)
# print(a.x, a.y, a.aa)       # 向上查找

# print(A.x)                  # 向下查找

A.aa = 11
a.aa = 22
print(a.aa)     # 22
print(A.aa)     # 11


b = A(1, 2)
print(b.aa)     # 11

类属性和实例属性以及查找顺序
MRO算
在这里插入图片描述

Python2.2之前的算法:金典类

DFS(deep first search):A->B->D->C->E
在这里插入图片描述
Python2.2版本之后,引入了BFS(广度优先搜索)

BFS:A->B->C->D

在Python2.3之后,Python采用了C3算法

Python新式类继承的C3算法:链接: link

**'''
class D(object):
    pass


class B(D):
    pass


class C(D):
    pass


class A(B, C):
    pass

print(A.__mro__)
'''

class D:
    pass


class B(D):
    pass


class E:
    pass


class C(E):
    pass


class A(B, C):
    pass


# A,B,D,C,E
print(A.__mro__)**

Python对象的自省机制
自省是通过一定的机制查询到对象的内部结构

Python中比较常见的自省(introspection)机制(函数用法)有: dir(),type(), hasattr(), isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。

class Person(object):

    name = 'jingang'


class Student(Person):

    def __init__(self, school_name):
        self.school_name = school_name


user = Student('教育')
# print(user.__dict__)
# print(dir(user))
# print(user.name)


# list()
a = [1, 2]
print(a.__dict__)
# print(dir(a))
# print(list.__dict__)

super函数
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super 来实现。

# class A:
#     def __init__(self):
#         print("A")
#
#
# class B(A):
#     def __init__(self):
#         print("B")
#         # python2的写法
#         # super(B, self).__init__()
#         super().__init__()

# 重写了B的构造函数  为什么还要去调用super
# b = B()

class People(object):
    def __init__(self, name, age, weight):
        self.name = name
        self.age = age
        self.weight = weight

    def speak(self):
        print("%s 说:我%d岁了"%(self.name, self.age))


class Student(People):
    def __init__(self,name, age, weight, grade):
        # self.name = name
        # self.age = age
        # self.weight = weight
        # super().__init__(name ,age, weight)
        People.__init__(self, name, age, weight)
        self.grade = grade

    def speak(self):
        print("%s 说:我%d岁了 我在读%d年级"%(self.name, self.age, self.grade))


s = Student('lg', 18, 30, 3)

# s.speak()


# super 执行顺序到底是什么样的?

class A:
    def __init__(self):
        print("A")


class B(A):
    def __init__(self):
        print("B")
        super().__init__()


class C(A):
    def __init__(self):
        print("C")
        super().__init__()


class E:
    pass


class D(B, C):
    def __init__(self):
        print("D")
        super().__init__()


d = D()
print(D.__mro__)        # D B C A
# super 调用父类中的方法
# 而是按照 mro 算法来调用的

二、类与对象深度问题与解决技巧

1、如何派生内置不可变类型并修改其实例化行为
我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素,例如:

IntTuple([2,-2,'jr',['x','y'],4]) => (2,4)
# self对象到底是谁创建的呢?
#self是通过__new__创建的,当__new__没有返回值时,__init__方法(是在产生类对象之后的实例化)不能执行
class A(object):
    def __new__(cls, *args, **kwargs):
        print("A.__new___", cls, args)
        return object.__new__(cls) #建议返回值写object
        # return super().__new__(cls)

    def __init__(self, *args):
        print("A.__init__")


a = A(1, 2)#等同于下面两行代码
a = A.__new__(A, 1, 2)
A.__init__(a, 1, 2)

如何继承内置tuple 实现IntTuple

class IntTuple(tuple):
    def __new__(cls, iterable):
        # for i in iterable:
        #     if isinstance(i, int) and i > 0:
        #         super().__init__(i)
        # 生成器
        # f = (i for i in iterable if isinstance(i, int) and i > 0)
        f = [i for i in iterable if isinstance(i, int) and i > 0]
        print(f)
        # 硬编码,此时cls就是IntTuple,但是return中不能用IntTuple,目的是为了避免硬编码
        return super().__new__(cls, f)


int_t = IntTuple([2, -2, 'jr', ['x', 'y'], 4])

print(int_t)        # (2, 4)

2、如何为创建大量实例节省内存
在游戏中,定义了玩家类player,每有一个在线玩家,在服务器内则有一个player的实例,当在线人数很多时,将产生大量实例(百万级)

如何降低这些大量实例的内存开销?

解决方案:

定义类的__slots__属性,声明实例有哪些属性(关闭动态绑定)

import tracemalloc  # 跟踪内存的使用


class Player1(object):
    def __init__(self, uid, name, status=0, level=1):
        self.uid = uid
        self.name = name
        self.status = status
        self.level = level


class Player2(object):
    __slots__ = ('uid', 'name', 'status', 'level')#关闭__dict__动态绑定属性功能

    def __init__(self, uid, name, status=0, level=1):
        self.uid = uid
        self.name = name
        self.status = status
        self.level = level


# p1 = Player1('0001', 'juran')
# p2 = Player2('0002', 'juran')

# print(dir(p1))
# print(len(dir(p1)))  # 30
# print(dir(p2))
# print(len(dir(p2)))  # 29

# __weakref__ 弱引用
# __dict__ 动态绑定属性,及其浪费内存
# print(set(dir(p1)) - set(dir(p2)))

# p1.x = 6
# p1.__dict__['y'] = 7
# print(p1.__dict__)


# 有了__slots__之后就可以把动态绑定属性关掉,也就是执行p2.x = 6时,会报错。
# print(p2.name)

# # __dict__ 动态绑定属性会占用多少属性呢?
# print(sys.getsizeof(p1.__dict__))#112,getsizeof查看对象占用的内存,内存分析的化要用tracemalloc
# print(sys.getsizeof(p1.name))#54
# print(sys.getsizeof(p1.uid))#53

tracemalloc.start()
p1 = [Player1(1, 2, 3) for _ in range(100000)] #这个地方的下划线(_)表示循环的值不需要,只需要循环的次数 # 16.8 MiB
p2 = [Player2(1, 2, 3) for _ in range(100000)]  # 7837 KiB
end = tracemalloc.take_snapshot()
top = end.statistics('lineno')
# top = end.statistics('filename')

for stat in top[:10]:
    print(stat)

3、Python中的with语句:自动地释放资源
上下文管理器协议

contextlib简化上下文管理器

# 上下文管理器
class Sample(object):

    # 获取资源
    def __enter__(self):
        print('start')
        return self

    def demo(self):
        print('this is demo')

    # 释放资源  Traceback
    def __exit__(self, exc_type, exc_val, exc_tb):
        # <class 'AttributeError'> 异常类
        print(exc_type, '-')
        # 'Sample' object has no attribute 'dems'  异常值
        print(exc_val, '-')
        # <traceback object at 0x000002D58D8D0A48>  追踪信息
        print(exc_tb, '-')
        print('end')


# 要想类能用with语句,类中需要有__enter__和__exit__两种方法,这样写也是比较麻烦的
# with Sample() as sample:
#     sample.dems()


import contextlib  # 简化上下文管理器


@contextlib.contextmanager
def file_open(filename):
    # xxx  __enter__ 函数
    print('file open')
    yield {} #生成器
    #yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。
    # 重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。
    # return 的作用:如果没有 return,则默认执行至函数完毕,遇到return程序结束。

    # __exit__ 函数
    print('file close')


with file_open('demo.txt') as f:
    print('file operation')

4、如何创建可管理的对象属性
在面向对象编程中,我们把方法看做对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如访问属性简洁。

'''
麻烦
A.get_key()     # 访问器
A.set_key()     # 设置器

A.key
A.key = 'jr'
形式上  属性访问
实际上  调用方法
'''

class A:
    def __init__(self, age):
        self.age = age

    def get_age(self):
        return self.age

    def set_age(self, age):
        if not isinstance(age, int):
            raise TypeError('Type Error')
        self.age = age
    #实现方式一:直接调用Property函数
    # property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
    R = property(get_age, set_age)
    #实现方式二:装饰器方式
    @property       # @property相当于get方法
    def S(self):
        return self.age

    @S.setter       #  @S.setter相当于set方法
    def S(self, age):
        if not isinstance(age, int):
            raise TypeError('Type Error')
        self.age = age


a = A(18)

# 文件读取的  str
# a.age = '20'
# print(type(a.age))
# a.set_age('20')
# print(a.get_age())
# a.R = 20 #相当于调用set_age方法
# print(a.R)#相当于调用get_age方法

print(a.S)
a.S = 22

5、如何让类支持比较操作
有时我们希望自定义类的实例间可以使用,<,<=,>,>=,==,!=符号进行比较,我们自定义比较的行业,例如,有一个矩形的类,比较两个矩形的实例时,比较的是他们的面积

from functools import total_ordering
import math
import abc  #抽象基类


@total_ordering
class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod #抽象基类中的第二种应用场景:我们需要强制某个子类必须实现某些方法
    def area(self):
        pass

    def __lt__(self, other):
        return self.area() < other.area()

    def __eq__(self, other):
        return self.area() == other.area()


class Rect(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

    # def __lt__(self, other):
    #     return self.area() < other.area()
    #
    # def __eq__(self, other):
    #     return self.area() == other.area()

# rect1 = Rect(1, 2)
# rect2 = Rect(1, 2)
# print(Rect(1, 2) > Rect(1, 2))    # rect2 < rect1

class Cirle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2*math.pi

    # def __lt__(self, other):
    #     return self.area() < other.area()
    #
    # def __eq__(self, other):
    #     return self.area() == other.area()


c = Cirle(8)
rect = Rect(1, 2)

print(c == rect)

6、如何在环状数据结构中管理内存
双向循环链表
弱引用:不占用引用计数。

import weakref
class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

    def add_right(self, node):
        self.right = node
        # node.left = self
        node.left = weakref.ref(self)

    def __str__(self):
        return 'Node:<%s>' % self.data

    def __del__(self):
        print('in __del__: delete %s' % self)


def create_linklist(n):
    head = current = Node(1)
    for i in range(2, n + 1):
        node = Node(i)
        current.add_right(node)
        current = node
    return head


head = create_linklist(1000)
head = None

import time
for _ in range(1000):
    time.sleep(1)
    print('run...')
input('wait...')

7、通过实例方法名字的字符串调用方法
我们有三个图形类

Circle,Triangle,Rectangle
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口

class Triangle:
    def __init__(self,a,b,c):
        self.a,self.b,self.c = a,b,c
    
    def get_area(self):
        a,b,c = self.a,self.b,self.c
        p = (a+b+c)/2
        return (p * (p-a)*(p-b)*(p-c)) ** 0.5

class Rectangle:
    def __init__(self,a,b):
        self.a,self.b = a,b
    
    def getArea(self):
        return self.a * self.b
        
class Circle:
    def __init__(self,r):
        self.r = r
    
    def area(self):
        return self.r ** 2 * 3.14159



shape1 = Triangle(3, 4, 5)
shape2 = Rectangle(4, 6)
shape3 = Circle(1)


def get_area(shape):
    method_name = ['get_area', 'getArea', 'area']
    for name in method_name:
        f = getattr(shape, name, None) #getattr(x, 'y') is equivalent to x.y,当x中不存在‘y’时,返回None,而不是报错。
        if f:
            return f()

# map 函数
# print(get_area(shape1))
# print(get_area(shape2))
# print(get_area(shape3))

shape_list = [shape1, shape2, shape3]

area_list = list(map(get_area, shape_list))
print(area_list)

三、Python 垃圾回收机制

1、Python 垃圾回收机制
计数引用我们反复提过好几次, Python 中一切皆对象。因此,你所看到的一切变量,本质上都是对象的一个指针。

那么,怎么知道一个对象,是否永远都不能被调用了呢?

就是当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。

import os
import psutil


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

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


def func():
    show_memory_info('initial')  # 8.4921875M
    # 局部变量
    a = [i for i in range(10000000)]
    show_memory_info('after a created')  # 396.41796875 MB


func()
show_memory_info('finished')  # 9.2734375 MB

2、循环引用
如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?

'''
引用计数
'''
import sys

import gc
# 清除没有引用的对象
# gc.collect()#循环引用

a = []


# 2 查看变量的引用次数  本身也要算一次
# print(sys.getrefcount(a))       # 2,引用计数


def func(a):
    # 1 a = []
    # 2 函数调用
    # 3 函数的参数
    # 4 getrefcount
    print(sys.getrefcount(a))   # 4

func(a)

# b = a
# print(sys.getrefcount(a))   # 3次


# 引用次数为0 是垃圾回收启动的充分必要条件嘛?充分不必要,例子如下:
import os
import psutil


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

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

def func():
    show_memory_info('initial')# 8.546875 MB
    a = [i for i in range(10000000)]
    b = [i for i in range(10000000)]
    show_memory_info('after a, b created')#783.8046875 MB
    a.append(b)
    b.append(a)

func()
show_memory_info('finished') #783.8046875 MB

3、调试内存泄漏
虽然有了自动回收机制,但这也不是万能的,难免还是会有漏网之鱼。内存泄漏是我们不想见到的,而且还会严重影响性能。有没有什么好的调试手段呢?

它就是 objgraph,一个非常好用的可视化引用关系的包。在这个包中,我主要推荐两个函数,第一个是 show_refs(),它可以生成清晰的引用关系图。

import objgraph

a = [1, 2, 3]
b = [4, 5, 6]

a.append(b)
b.append(a)

objgraph.show_refs([a])

在这里插入图片描述
dot转图片链接: link.

4、总结
垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;

引用计数是其中最简单的实现,不过切记,这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收;

Python 的自动回收算法包括标记清除和分代收集,主要针对的是循环引用的垃圾收集;

调试内存泄漏方面, objgraph 是很好的可视化分析工具。

四、代码调试和性能分析

1、调试和性能分析
用 pdb 进行代码调试
首先,我们来看代码的调试。也许不少人会有疑问:代码调试?说白了不就是在程序中使用 print() 语句吗?

没错,在程序中相应的地方打印,的确是调试程序的一个常用手段,但这只适用于小型程序。因为你每次都得重新运行整个程序,或是一个完整的功能模块,才能看到打印出来的变量值。如果程序不大,每次运行都非常快,那么使用 print(),的确是很方便的。

可能又有人会说,现在很多的 IDE 不都有内置的 debug 工具吗?但是在大型项目中,可能有混合代码,不同的语言之间的调试,这时就可以借用pdb进行调试。

如何使用 pdb
首先,要启动 pdb 调试,我们只需要在程序中,加入import pdb和pdb.set_trace()这两行代码就行了

a = 1
b = 2
import pdb
pdb.set_trace()
c = 3
print(a + b + c)

这时,我们就可以执行,在 IDE 断点调试器中可以执行的一切操作,比如打印,语法是"p ":

(pdb) p a
1
(pdb) p b
2

除了打印,常见的操作还有“n”,表示继续执行代码到下一行

(pdb) n
-> print(a + b + c)

而命令l,则表示列举出当前代码行上下的 11 行源代码,方便开发者熟悉当前断点周围的代码状态

(pdb) l
  1    a = 1
  2    b = 2
  3    import pdb
  4    pdb.set_trace()
  5  ->  c = 3
  6    print(a + b + c)

命令“s“,就是 step into 的意思,即进入相对应的代码内部。

import pdb

a = 1
b = 2

def func():
    print('enter func()')

pdb.set_trace() # 可以在调试中 用p a 来打印a,用n继续往后执行,用l显示未执行的代码,用s进入函数体内部。
c = 3
func()
print(a + b + c)

当然,除了这些常用命令,还有许多其他的命令可以使用

参考对应的官方文档链接: link

2、用 cProfile 进行性能分析
事实上,除了要对程序进行调试,性能分析也是每个开发者的必备技能。

日常工作中,我们常常会遇到这样的问题:在线上,我发现产品的某个功能模块效率低下,延迟高,占用的资源多,但却不知道是哪里出了问题。

这时,对代码进行 profile 就显得异常重要了。

这里所谓的 profile,是指对代码的每个部分进行动态的分析,比如准确计算出每个模块消耗的时间等。

计算斐波拉契数列,运用递归思想

import cProfile

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res

# print(fib_seq(30))

cProfile.run('fib_seq(30)')

在这里插入图片描述
参数介绍:

(1)ncalls,是指相应代码 / 函数被调用的次数
(2)tottime,是指对应代码 / 函数总共执行所需要的时间(注意,并不包括它调用的其他代码 / 函数的执行时间)
(3)tottime percall,就是上述两者相除的结果,也就是tottime / ncalls
(4)cumtime,则是指对应代码 / 函数总共执行所需要的时间,这里包括了它调用的其他代码 / 函数的执行时间
(5)cumtime percall,则是 cumtime 和 ncalls 相除的平均结果。

五、经典的参数错误

def add(a, b):
    a += b
    # a = a + b
    return a


a = 1
b = 2
c = add(a, b)

print(c)        # 3
print(a, b)     # 1 2

a = [1, 2]
b = [3, 4]

c = add(a, b)
print(c)        # [1 2 3 4]
print(a, b)     # a = [1 2 3 4]   b = [3 4]

a = (1, 2)
b = (3, 4)
c = add(a, b)
print(c)        # (1 2 3 4)
print(a, b)     # (1 2)   (3 4)

# 可变  不可变:对于不可变类型的数据重新赋值的话,相当于重新创建了一个数据,id发生了改变。
# 数据结构  列表:列表的创建是在__init__方法中的,  元组:元组的创建在__new__方法中

不可变类型
以int类型为例:实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int对象,i引用自这个新的对象。

可变类型
以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改。

六、元类编程

1、__getattr__和__getattribute__魔法函数

from datetime import date


class User:
    def __init__(self, name, birthday, info={}):
        self.name = name
        self.birthday = birthday
        self.info = info

    # 在查找不到属性的时候调用
    def __getattr__(self, item):
        # print(item, 'attribute not find')
        # print(item)
        # return 'attribute not find'
        # pass
        # return self.info[item]
        return self.info.get(item)

    # __getattribute__ 在__getattr__之前执行
    # def __getattribute__(self, item):
    #     return "juran"


# def demo(): pass
# print(demo())

if __name__ == '__main__':
    # 1990-1-1
    user = User('juran', date(year=1990, month=1, day=1), info={"age": 18})
    # print(user.name)
    print(user.birthday)

2、属性描述符

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

    def get_age(self):
        return self.age

    def set_age(self, age):
        if not isinstance(age, int):
            raise TypeError('type error')
        self.age = age

如果User类中有多个属性都需要判断,那么就需要写多个方法,这些方法怎么复用呢?这个时候就要用到属性描述符

属性描述符,只要实现了__get__,set,__delete__任何一个方法,就被称为属性描述符

# 属性描述符  __get__ __set__ __delete__
# Django

class IntField(object):
    """
    数据描述符
    """
    def __get__(self, instance, owner):
        print("__get__")
        # print(instance)
        return self.values

    def __set__(self, instance, value):
        print("__set__")
        # print(instance)
        # print(value)
        if not isinstance(value, int):
            raise ValueError('Value Error')
        self.values = value

    def __delete__(self, instance):
        pass


class NoneDataIntField:
	"""
	非数据描述符
	"""
    def __get__(self, instance, owner):
        pass


class User:
    age = IntField()
    # age = 19


user = User()
user.age = 30
user.__dict__['age'] = 18


print(user.age)

属性查找顺序:属性描述符的优先级是最高的

user = User(), 那么user.age 顺序如下:

1 如果"age"是出现在User或其基类的__dict__中, 且age是data descriptor,那么调用其__get__方法, 否则

2 如果"age"出现在user的__dict__中, 那么直接返回 obj.__dict__['age'],否则

3 如果"age"出现在User或其基类的__dict__中

3.1 如果age是non-data descriptor,那么调用其__get__方法, 否则

3.2 返回 __dict__['age']

4 如果User有__getattr__方法,调用__getattr__方法,否则

5 抛出AttributeError

3、自定义元类
动态创建类

def create_class(name):
    if name == 'user':
        class User(object):
            def __str__(self):#打印的时候被调用
                return "user"
        return User
    elif name == 'student':
        class Student:
            def __str__(self):
                return "student"
        return Student



if __name__ == '__main__':
    myclass = create_class('user')
    obj = myclass()
    print(obj)
    # 查看数据类型  创建类
    print(type(obj))

4、使用type创建类
type还可以动态的创建类,type(类名,由父类组成的元组,包含属性的字典)

'''
type(name, bases, dict) -> a new type
'''

# User = type("User", (), {})
# obj = User()
# print(obj)

# 添加属性
# User = type("User", (), {'name': 'juran'})
# obj = User()
# print(obj)
# print(obj.name)

# 添加一个方法
def demo(self):
    return self.name


def get_age(self):
    self.age = 18
    return self.age


# def __init__(self):#尽量不用调用魔法方法
#     self.sex = 1


User = type("User", (), {'name': 'juran', "info": demo, "age": get_age})
obj = User()
# print(obj.name)
# print(obj)
class BaseClass(object):
    def test(self):
        return "base class"

    def __str__(self):
        return "this is test"


class BaseClass1(object):
    def test1(self):
        return "base class1"


# b = BaseClass1()
# print(b)


User = type("User", (BaseClass, BaseClass1), {'name': 'juran'})
user = User()
print(user)
print(user.test1())

5、metaclass属性
如果一个类中定义了metalass = xxx,Python就会用元类的方式来创建类

'''
def upper_attr(class_name, class_parents, class_attr):
    # print(class_name)
    # print(class_parents)
    # print(class_attr)
    newattr = {}
    for name, value in class_attr.items():
        # print(name)
        # print(value)
        if not name.startswith("_"):
            newattr[name.upper()] = value

    return type(class_name, class_parents, newattr)


class Foo(object, metaclass=upper_attr):
    # __metaclass__ = upper_attr
    name = 'juran'


f = Foo()
print(hasattr(Foo, 'name'))
print(hasattr(Foo, 'NAME'))

# print(f.NAME)
# print(f.name)
'''


# 类的实例化 首先寻找metaclass
class Demo(object):
    def __new__(cls, *args, **kwargs):
        pass


class MetaClass(type): #metaclass是继承type
    def __new__(cls, *args, **kwargs):
        pass


class User(Demo, metaclass=MetaClass):#metaclass是优先执行的,在此处Demo并未被执行
    pass


obj = User()

七、迭代器和生成器

1、迭代器
在介绍迭代器之前,先说明下迭代的概念:

迭代:通过for循环遍历对象的每一个元素的过程。

Python的for语法功能非常强大,可以遍历任何可迭代的对象。

在Python中,list/tuple/string/dict/set/bytes都是可以迭代的数据类型。

迭代器是什么?
迭代器是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。 迭代器只能往后遍历不能回溯,不像列表,你随时可以取后面的数据,也可以返回头取前面的数据。

from collections.abc import Iterator, Iterable

# print(isinstance(list(), Iterable))  # True
# print(isinstance(list(), Iterator))  # False

# 迭代器的实现通常使用两个方法:iter  next(取迭代器了里面的元素)

l = [1, 2, 3, 4]
it = iter(l)

# print(it)

# print(next(it))
# print(next(it))
# print(next(it))
print(next(it), '-')


for i in it:
    print(i)

2、生成器
有时候,序列或集合内的元素的个数非常巨大,如果全制造出来并放入内存,对计算机的压力是非常大的
生成器生成的两种方式:
(1)(x for x in range(10))
(2)yield关键字

# g = (x for x in range(10))

# print(g)

# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# for i in g:
#     print(i)

# 0 1 2 3 5 8  a, b = b, a+b
def fibonacci():
    print("--func start--")
    a, b = 0, 1

    for i in range(5):
        # print(b)
        print("--1--")
        # 当函数中出现yield关键字的时候,这个函数就变成了一个生成器了
        yield b #yield是返回值,每次运行到yield会停在此处,并返回值,后面再运行时,从yield位置开始运行
        print("--2--")
        a, b = b, a+b
        print("--3--")

    print("--func end--")

g = fibonacci()
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

生成器如何读取大文件
文件300G,文件比较特殊,一行 分隔符 {|}

def readlines(f, newline):
    buf = ""
    while True:
        while newline in buf:
            # index 2
            pos = buf.index(newline)
            yield buf[:pos]
            buf = buf[pos + len(newline):]

        chunk = f.read(1024)
        # 读到文件末尾
        if not chunk:
            yield buf
            break
        buf += chunk

# 2+3
'abc{|}123{|}456'
with open('demo.txt') as f:
    for line in readlines(f, "{|}"):
        print(line)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值