上周习题:

    1、shape基类,要求所有子类都必须提供面积的计算,子类有三角形,矩形,圆,圆类的数据可序列化

    2、用面向对象实现LinkedList链表,单向链表实现append,iternodes

    3、双向链表实现append,pop,insert,remove,iternodes

答案:

    1、shape类

        import math

        import json

        import msgpack

        class Shape:

            @property

            def area(self):

                raise NotImplementedError # 不可实例化的class

        class Triangle(Shape):

            def __init__(self,a,h):

                self.a = a

                self.h = h

            @property

            def area(self):

                return int(0.5*self.a*self.h)

        class Rectangle(Shape):

            def __init__(self,width,height):

                self.width = width

                self.height = height

            @property

            def area(self):

                return self.width*self.height

        class Circle(Shape):

            def __init__(self,radius):

                    self.d = radius ** 2

    

            @property

            def area(self):

                return '{:.3}'.format(math.pi * self.d)

        class SerializableMixin:

            def dumps(self,t='json'):

                if t == 'json':

                    return json.dumps(self.__dict__)

                elif t == 'msgpack':

                    return msgpack.packb(self.__dict__)

                else:

                    raise NotImplementedError

        class SerializableCircleMixin(SerializableMixin,Circle):

            pass

        scm = SerializableCircleMixin(4)

        print(scm.area)

        print(scm.dumps())

        # s = scm.dumps('msgpack')

        # print(s)

        t = Triangle(3,4)

        r = Rectangle(3,4)

        c = Circle(3)

        print(t.area,r.area,c.area)

    2、单链表

        class SingleNode:

            def __init__(self,item,next=None):

                self.item = item # 定义链表单个元素

                self.next = next

            def __repr__(self):

                return repr(self.item) # 返回单个元素

        class LinkedList: # 定义链表

            def __init__(self):

                self.head = None # 定义链表元素开头

                self.tail = None # 定义链表元素结尾

            def add(self,item): # 实现append

                node = SingleNode(item) # 调用singlenode获取一个node

                if self.head is None: # 初始headNone

                    self.head = node # 复制headnode

                else:

                    self.tail.next = node # 后续定义尾部追加next元素

                self.tail = node # 更新tail值为node

            def iternodes(self): # 迭代

                current = self.head # 现在的值为head,第一个

                while current:

                    yield current

                    current = current.next # 更新current,完成迭代

        ll = LinkedList()

        ll.add('abc')

        ll.add(1)

        ll.add(2)

        ll.add('def')

        print(ll.head,ll.tail)

        for x in ll.iternodes():

        print(x)

    3、双向链表;

        class SingleNode:

            def __init__(self,item,prev=None,next=None):

                self.item = item

                self.prev = prev

                self.next = next

            def __repr__(self):

                return repr(self.item)

        class LinkedList:

            def __init__(self):

                self.head = None

                self.tail = None

                

            def append(self,item):

                node = SingleNode(item)

                if self.head is None:

                    self.head = node

                else:

                    self.tail.next = node

                    node.prev = self.tail

                self.tail = node

            def insert(self,index,item):

                if index < 0:

                    raise ValueError('Wrong index {}'.format(index))

                current = None

                for i ,node in enumerate(self.iternodes()):

                    if index == i:

                        current = node

                        break

                if current is None:

                    self.append(item)

                    return

                node = SingleNode(item)

                prev = current.prev

    

                if prev is None:

                    self.head = node

                else:

                    prev.next = node

                    node.prev = prev

                node.next = current

                current.prev = node

            def pop(self):

                if self.tail is None:

                    raise Exception('Empty')

                node = self.tail

                item = node.item

                prev = node.prev

                if prev is None:

                    self.head = None

                    self.tail = None

                else:

                    prev.next = None

                    self.tail = prev

                return item

            def remove(self,index):

                    if self.tail is None:

                        raise Exception('Empty')

                    if index < 0:

                        raise ValueError('Wrong index {}'.format(index))

                    current = None

                    for i, node in enumerate(self.iternodes()):

                        if i == index:

                            current = node

                            break

                    if current is None:

                        raise ValueError('Wrong index {} out of boundary'.format(index))

                    prev = current.prev

                    next = current.next

                    if prev is None and next is None:

                        self.head = None

                        self.tail = None

                    elif prev is None:

                        self.head = next

                        next.prev = None

                    elif next is None:

                        self.tail = prev

                        prev.next = None

                    else:

                        prev.next = next

                        next.prev = prev

                    del current

            def iternodes(self,reverse=False):

                current = self.tail if reverse else self.head

                while current:

                    yield current

                    current = current.prev if reverse else current.next

        ll = LinkedList()

        ll.append(1)

        ll.append(2)

        ll.append(3)

        ll.append('abcd')

        ll.append(4)

        ll.append(5)

        ll.append('def')

        ll.append(8)

        print(ll.head,ll.tail)

        for x in ll.iternodes():

            print(x)

        print('~~~~~~~~~~~~~~~~~~')

        ll.remove(6)

        ll.remove(5)

        ll.remove(0)

        ll.remove(1)

        for x in ll.iternodes():

            print(x)

        print('~~~~~~~~~~~~~~~~~~')

        ll.insert(3,5)

        ll.insert(20,'def')

        ll.insert(1,2)

        ll.insert(0,'abc')

        for x in ll.iternodes():

            print(x)

一、魔术方法:

    1、特殊属性

属性含义
__name__类,函数,方法的名字
__moudle__类定义所在的模块名
__class__对象或类所属的类
__bases__类的基类的元组,顺序为它们在基类列表中表现的顺序
__doc__类,函数的文档字符串,如果没有定义则为None
__mro__类的mro,class.mro( )返回的结果保存在__mro__中
__dict__类或实例的属性,可写的字典

    2、查看属性

        __dir__返回类或对象的所有成员名称列表。dir( )函数就是调用__dir__( )。如果提供__dir__( ),则返回属性的列表,否则会尽量在__dict__属性中收集信息

        dir()得到的属性多,是个列表

        class Person:

            pass

        p = Person()

        print(dir(p))

        print(p.__dict__)

        import test  # test是上面的例子的文档名

        from test import Animal

        class Cat(Animal):

            X = 'cat'

            y = 'abcd'

        class Dog(Animal):

            def __dir__(self):

                return ['dog'] # 必须是列表

        print(dir())

        print(dir(test))

        print(sorted(object.__dict__.keys()))

        print(dir(Cat))

    3、魔术方法:

        1、分类:

            1、创建与销毁:__init__与__del__

            2、hash

            3、bool

            4、可视化

            5、运算符重载

            6、容器和大小

            7、可调用对象

            8、上下文管理

            9、反射

            10、描述器

            11、其他杂项

        2、hash

            方法:__hash__内建函数hash()调用的返回值,返回一个整数,如果定义这个方法,这个类的实例就可hash

            hash取mo法,%x得到的mo,就是hash值,如果x越大,越不容易冲突

            hash散列,散开的,很大的一个空间存放,缓存时使用,缓存的目的为了再看,hash算法的时间复杂度时O1,字典和集合的key

            class A:

                def __init__(self):

                    self.a = 'a'

                    self.b = 'b'

                def __hash__(self):     hash指向引用,虽然都是1,但内存地址不一样,不是一个

                    return 1

                def __eq__(self, other):        eq方法指向内存地址,返回相等,则一模一样

                    return self.a == other.a  

            print(hash(A()))

            print((A(),A()))

            print({A(),A()})

            s = {A(),A()}

            print(s)

    3、eq

        方法:__eq__对应==操作符,判断两个对象是否相等,返回bool值

        __hash__方法只是返回一个hash值作为set的key,但是去重,还需要判断2个对象是否相等

        hash值相等,只是hash冲突,不能说明两个对象是相等的

        因此,一般来说,提供__hash__方法是为了作为set或者dict的key的,所以去重要同时进行的话,需要提供eq方法

        可hash对象必须提供__hash__方法,没有提供的话,isinstance(p1,collections.hashable)一定为False

        去重要提供eq方法

        

        设计二位坐标类Point,比较2个坐标是否相等?

        from collections import Hashable

        class Point:

            def __init__(self, x, y):

                self.x = x

                self.y = y

            def __hash__(self):

                return 1

            def __eq__(self, other):

                return self.x == other.x and self.y == other.y

        p1 = Point(1,2)

        p2 = Point(1,2)

        print(p1 is p2) # 内存地址不等       False

        print(p1 == p2) # 内容相等         True

        print({p1,p2})                    因为eq结果==为True所以去重,因为集合内部实现的是,先看是不是==,是就去重

        print(isinstance(p1,Hashable))

    4、bool

        方法:__bool__内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值

                没有定义__bool__( ),就找__len__( )返回长度,非0为真,如果len也没有定义,那么所有实例都返回为真

        class A:

            pass

        print(bool(A()))

        class B:

            def __bool__(self):

                return False

        print(bool(B))

        print(bool(B()))

        class C:

            def __len__(self):

                return 0

        print(bool(C()))

    5、可视化  

方法意义
__repr__内建函数repr( )对一个对象获取字符串表达,如果一个类定义了__repr__但没有定义__str__,那么在请求该类的实例的“非正式”的字符串表示时,也将调用__repr__( )
__str__str()函数、内建函数format、print()函数调用,需要返回对象的字符串表达
__bytes__bytes的时候,返回一个对象的bytes表达,即返回bytes对象

        class A:

            def __init__(self):

                self.a = 'a'

                self.b = 'b'

            def __repr__(self):

                return 'repr:{},{}'.format(self.a,self.b)

            def __str__(self):                    # 在print,format、str时使用,这三个优先与repr

                return 'str:{},{}'.format(self.a,self.b)

        print(A()) # print函数使用str

        print([A()]) # []使用str,但其内部使用repr

        print([str(A())]) # []使用strstr也使用str

        print('str:a,b')

        s = 'b'

        print(['a'],(s,))

    6、运算符重载

        去int里找所有的运算符(在pycharm里)

         运算符      特殊方法        含义

<

<=

==

>

>=

!=

__lt__

__le__

__eq__

__gt__

__ge__

__ne__

比较运算符

+

-

*

/

%

//

**

divmod

__add__

__sub__

__mul__

__truediv__

__mod__

__floordiv__

__pow__

__divmod__

算数运算符,移位,位运算也有对应的方法

+=

-=

*=

/=

%=

//=

**=

__iadd__

__isub__

__imul__

__itruediv__

__imod__

__ifloordiv

__ipow__


        class A:

            def __init__(self,x):

                self.x = x

            def __sub__(self, other):

                return self.x - other.x

            def __isub__(self, other):

                tmp = self.x - other.x

                return A(tmp)

        a1 = A(1)

        a2 = A(2)

        print(a1 - a2)

        print(a2.__isub__(a1))

        a1 -= a2

        print(a1)

        练习:完成Point类设计,实现判断点相等的方法,并完成向量的加法        

        class Point:

            def __init__(self, x, y):

                self.x = x

                self.y = y

            def __eq__(self, other):

                return self.x == other.x and self.y == other.y

            def add(self, other):

                return Point(other.x + self.x, other.y + self.y)

            def __add__(self, other):

                self.x = self.x + other.x

                self.y = self.y + other.y

                return Point(self.x, self.y)

            def __str__(self):

                return 'Point:{}:{}'.format(self.x, self.y)

        p1 = Point(1, 2)

        p2 = Point(1, 2)

        print(p1 == p2)

        print(p1 + p2)

        print(Point(*(p1, p2)))

        print(p1.add(p2))

        print(p1)

        运算符重载,往往是用面向对象实现的类,需要做大量的运算,而运算符可以实现实例的运算,加方法也可以

    7、容器相关方法:

        

            方法                                                意义
__len__

内建函数len( ),返回对象的长度(>=0的整数),其实即便把对象当作容器

类型看,就如同list或者dict,bool( )函数调用的时候,如果没有__bool__

方法,则会看__len__方法是否存在,存在则返回非0为真。

__iter__迭代容器时调用,返回一个新的迭代器对象,必须时迭代器
__contains__in成员运算符,没有实现,就调用__iter__方法遍历
__getitem__

实现self[key]访问。

序列对象,key接受整数为索引,或者切片。对于set和dict,key为hashable。key不存在引发keyerror异常

__setitem__和__getitem__的访问类似,是设置值的方法
__missing__字典使用__getitem__调用时,key不存在执行该方法

        

        练习:将购物车类改造成方便操作的容器类

        class Item:

            def __init__(self, **kwargs):

                self.kwargs = kwargs

            def __repr__(self):

                return str(sorted(self.kwargs.items()))

        class Cart:

            def __init__(self):

                self.items = []

    

            def additem(self, item):

                self.items.append(item)

            def __len__(self):

                return len(self.items)

            def __iter__(self):

                return iter(self.items)

            def __getitem__(self, index):

                return self.items[index]

            def __setitem__(self, key, value):

                self.items[key] = value

        cart = Cart()

        cart.additem(1)

        cart.additem(2)

        cart.additem(3)

        cart.additem(Item(car='audi', price=270000))

        print(len(cart))

        for x in cart:

        print(x)

        print(2 in cart)

        cart[2] = 'a'

        print(cart[2])

   

    8、可调用方法:

        方法:__call__ 类的第一个该方法,实例就可以像函数一样调用

        可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。

        class Point:

            def __init__(self, x, y):

                self.x = x

                self.y = y

            def __call__(self, *args, **kwargs):

                return 'Point({},{})'.format(self.x,self.y)

        p = Point(4,5)

        print(p)

        print(p())

        class Adder:

            def __call__(self, *args, **kwargs):

                ret = 0

                for x in args:

                    ret += x

                self.ret = ret

                return ret

        adder = Adder()

        print(adder(4, 5))

        print(adder.ret)

    练习:定义一个斐波那契数列的类,方便调用,计算第n项

        class Fib:

            def __init__(self):

                self.items = [0, 1, 1]

            def __call__(self, index):

                if index < 0:

                    return None

                if index < len(self.items):

                    return self.items[index]

                for i in range(len(self.items), index+1):

                    self.items.append(self.items[i-1] + self.items[i-2])

                return self.items[index]

            def __len__(self):

                return len(self.items)

            def __iter__(self):

                return iter(self.items)

            def __str__(self):

                return str(self.items)

            __repr__ = __str__

        fib = Fib()

        print(fib(30),len(fib))

        for x in fib:

        print(x)

        增加迭代的方法,返回容器的长度,使用容器减少计算,每次计算后保留容器,下次不用再计算

    9、上下文管理:

        1、文件IO操作可以对文件对象使用上下文管理,使用with...as语法

        2、with open('test') as f:pass

        3、仿照上例,可以写一个类,实现上下文管理

        方法                                                意义
__enter__

进入与此对象相关的上下文,如果存在该方法,with语法会把该

方法的返回值作为绑定到as语句的变量f的值

__exit__退出与此对象相关的上下文

        class Point:

            def __init__(self):

                print('init')

            def __enter__(self):

                print('enter')

            def __exit__(self, exc_type, exc_val, exc_tb):

                print('exit')

        with Point() as f:

            print('do sth')

        实例化对象的时候,不会调用enter,进入with语句后,先enter,再with语句块,离开时exit

        with可以开启一个上下文运行环境,在执行前做准备工作,执行后做收尾工作

        4、上下文管理的安全性

        异常对上下文的影响

        class Point:

            def __init__(self):

                print('init')

            def __enter__(self):

                print('enter')

            def __exit__(self, exc_type, exc_val, exc_tb):

                print('exit')

        with Point() as f:

            raise Exception(''error)

            print('do sth')  

        enter和exit照样执行,上下文管理很安全

        极端例子,import sys后,在with语句中加入sys.exit( )后,依然执行,init,enter,exit函数,哪怕是退出python环境 

        __enter__方法的return值是with语句的变量,如果不返回就是None,一般返回self

        class Point:

            def __init__(self):

                print('init')

            def __enter__(self):

                print('enter')

                return self

            def __exit__(self, exc_type, exc_val, exc_tb):

                print('exit')

        p = Point()

        with p as f:

            print(p == f)

            print('do sth')

        5、__enter__和__exit__的参数:

            enter没有参数,exit有三个参数,都与异常有关

            如果上下文退出时都没有异常,这三个参数都是None,如果有异常,参数意义如下。

            如果有异常,参数意义如下:

            exc_type,异常类型

            exc_value,异常的值

            trackback,异常的追踪信息

            如果exit方法返回一个等效的True的值,则压制异常,否则,继续抛出异常,默认False

练习:为加法函数计时

          1、使用装饰器

          2、使用上下文管理

          1、装饰器

            import datetime

            import time

            def logger(fn):

                def wrapper(*args):

                    start = datetime.datetime.now()

                    ret = fn(*args)

                    delta = datetime.datetime.now()

                    print('{} took {}s'.format(fn.__name__, (delta-start).total_seconds()))

                    return ret

                return wrapper

            @logger

            def add(x, y):

                time.sleep(2)

                return x + y

            print(add(1, 2))

          2、上下文

            import datetime

            import time

            from functools import wraps

            class TimeIt:

                """This is A Class"""

                def __init__(self, fn=None):

                    if fn is not None:

                        self.fn = fn

                        wraps(fn)(self)

                    # self.__doc__ = fn.__doc__

                # def __enter__(self):

                    # self.strat = datetime.datetime.now()

                    # return self

                # 此上下为上下文管理

                # def __exit__(self, exc_type, exc_val, exc_tb):

                    # delta = (datetime.datetime.now() - self.start).total_seconds()

                    # print(delta)

                def __call__(self, *args, **kwargs):

                    self.start = datetime.datetime.now()

                    ret = self.fn(*args,**kwargs)

                    self.delta = (datetime.datetime.now() - self.start).total_seconds()

                    print(self.delta)

                    return ret

            @TimeIt # add = TimeIt(add)

            def add(x, y):

                """This is add function"""

                time.sleep(2)

                return x + y

            print(add(2, 3))

            print(add.__doc__)

            print(TimeIt().__doc__)

        上面代码,既可以用在上下文管理,又可以用作装饰器

上下文管理应用场景:

    1、增强功能

            在代码执行的前后增强代码,以增强其功能。类似于装饰器的功能

    2、资源管理

            打开了资源需要关闭,例如文件对象、网络连接、数据库连接等

    3、权限验证

            在执行代码之前,做权限的验证,在__enter__中处理

二、反射

程序运行时,区别于编译时,指的时程序被加载到内存执行的时候

反射,reflection,指的是运行时,获取类型定义信息。

一个对象,能够在运行时,像照镜子一样,反射出其类型信息

具有反射能力的函数有,type(),isinstance( ),callable(),dir(),getattr()

    1、反射相关的函数和方法:

        有一个point类,查看它实例的属性,并修改它

        class Point:

            def __init__(self, x, y):

                self.x = x

                self.y = y

            def __str__(self):

                return 'Point({},{})'.format(self.x, self.y)

            def show(self):

                print(self.x, self.y)

        p = Point(3, 4)

        print(p)

        print(p.__dict__)

        p.__dict__['y'] = 16

        print(p.__dict__)

        p.z = 10

        print(p.__dict__)

        print(dir(p))

        print(p.__dir__())

上例通过属性字典__dict__来访问对象的属性,本质也是反射,但是不好看

内建函数                                              意义

getattr(object,

name[,default])

通过name返回object的属性值。当属性不存在,将使用default返回,

如果没有default,则抛出AttrbuteError。name必须是字符串

setattr(object,

name,value)

object的属性存在,则覆盖,不存在,新增

hasattr(object,

name)

判断对象是否有这个名字的属性,name必须是字符串

用上面的方法来修改上例的代码

class Point:

def __init__(self, x, y):

self.x = x

self.y = y

def __str__(self):

return 'Point({}, {})'.format(self.x, self.y)

def show(self):

print(self)

p1 = Point(1, 2)

p2 = Point(2, 3)

if not hasattr(Point, 'add'):

setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))

print(Point.add)

print(p1.add)

print(p1.add(p2))

这种动态增加属性的方法,和装饰器修饰及Mixin的区别在于运行时动态改变类或者实例,更灵活

练习:命令分发器,通过名称找到对应的函数执行。

class Dispatcher:

    def cmd1(self):

        print('cmd1')

    def reg(self, cmd, fn):   # 注册,实现写入属性

        if isinstance(cmd, str):

            setattr(self.__class__,cmd,fn)

    def run(self):     # 运行时,实现获取函数

        while True:

            cmd = input('plz input command:')

            if cmd.strip() == 'quit':

                return

        getattr(self, cmd.strip(), self.defaultfn)() # 缺省为函数,需要调用

    def defaultfn(self):

        # print('defalut')

        print('default')

dis = Dispatcher()

dis.reg('cmd2', lambda self: print(1))

dis.reg('cmd3', lambda self: print(2))

dis.run()

    2、反射相关的魔术方法:

        __getattr__( ),__steattr__( ),__delattr__( )

class Base:

    n = 0

class Point(Base):

    z = 6

    def __init__(self, x, y):

        self.x = x

        self.y = y

    def show(self):

        print(self.x, self.y)

    def __getattr__(self, item):

        return 'missing {}'.format(item)

p1 = Point(4, 5)

print(p1.x)

print(p1.z)

print(p1.n)

print(p1.t)

一个类的属性会按照继承关系找,如果找不到,就找getattr方法,如果没有这个方法,报attr错误

   

class Base:

    n = 0

class Point(Base):

    z = 6

    def __init__(self, x, y):

        self.x = x

        self.y = y

    def show(self):

        print(self.x, self.y)

    def __getattr__(self, item):

        return 'missing {}'.format(item)

    def __setattr__(self, key, value):

        print('setattr {}={}'.format(key, value))

        self.__dict__[key] = value

p = Point(1, 2)

print(p.x)

p.x = 50

print(p.x)

实例通过. 设置属性,如同self.x = x,就会调用__setattr__,属性要加到dict中,需要自己设置

如果不设置,setattr会拦截对实例属性的增加,修改操作

class Point:

    Z = 5

    def __init__(self, x, y):

        self.x = x

        self.y = y

    def __delattr__(self, item):

        print('can not del {}'.format(item))

p = Point(1, 2)

del p.x

p.z = 15

del p.z

del p.Z

del Point.Z

print(p.Z)

delattr会拦截实例删除属性的操作,但是通过类删除依然可以

class Base:

    n = 0

class Point(Base):

    z = 6

    def __init__(self, x, y):

        self.x = x

        self.y = y

    def __getattr__(self, item):

        return 'missing {}'.format(item)

    def __getattribute__(self, item):

        preturn item

p = Point(1, 2)

print(p.__dict__)

print(p.x)

print(p.z)

print(p.m)

print(Point.__dict__)

实例的所有的属性访问,第一个都会调用getattribute方法,它阻止了属性的查找,返回计算后的值,或者抛出一个AttributeError

它的ruturn值将作为属性查找的结果,如果抛出AttributeError,会调用getattr方法,因为表示属性未找到

class Base:

    n = 0

class Point(Base):

    z = 6

    def __init__(self, x, y):

        self.x = x

        self.y = y

    def __getattr__(self, item):

        return 'missing {}'.format(item)

    def __getattribute__(self, item):

        return object.__getattribute__(self, item)

p = Point(1, 2)

print(p.__dict__)

print(p.x)

print(p.z)

print(p.m)

print(Point.__dict__)

__getattribute__方法中为了避免该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.getattribute(self,name)

属性查找顺序:实例调用__getattribute__( )-->instance.__dict__-->instance.__class__.__dict__-->继承类的祖先直到object的__dict__-->调用__getattribute__

二、描述器Descriptors

描述器的表现

用到三个魔术方法:__get__( )、__set__( )、__delete__( )

方法如下:

object.__get__(self,instance,owner)

object.__set__(self,instance,owner)

object__delete__(self,instance)

self指代当前实例,调用者

instance时owner的实例

owner是属性的所属的类

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A.init')

    def __get__(self, instance, owner):

        print('A.__get__ {} {} {}'.format(self,instance,owner))

        # return self   # 返回自身

class B:

    x = A()

    def __init__(self):

        print('B.init')

        self.b = A()

print('-'*20)

print(B.x)

# print(B.x.a1)    # 实例返回get的return,如果不写,None没有a1属性

print('='*20)

b = B()

print(b.b)

   

B类实例的属性不会触发get方法

Python中,一个类实现了__get__、__set__、__delete__中的任何一个,就是描述器

如果仅实现了__get__,就是非数据描述器

同时实现了__get__、set就是数据描述器

如果一个类的类属性设置为描述器,那么它被成为owner属主

属性的访问顺序

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A.init')

    def __get__(self, instance, owner):

        print('A.__get__ {} {} {}'.format(self, instance, owner))

        return self

class B:

    x = A()

    def __init__(self):

        print('B.init')

        self.x = 'b.x' # 增加实例属性x

print('-'*20)

print(B.x)

print(B.x.a1)

print('='*20)

b = B()

print(b.x)

print(b.x.a1) # AttributeError

b是实例,b.x访问实例的属性,而不是描述器

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A.init')

    def __get__(self, instance, owner):

        print('A.__get__ {} {} {}'.format(self, instance, owner))

        return self

    def __set__(self, instance, value):

        print('A.__set__ {} {} {}'.format(self, instance, value))

        self.data = value

class B:

    x = A()

    def __init__(self):

        print('B.init')

        self.x = 'b.x' # 增加实例属性x

print(B.x)

print(B.x.a1)

b = B()

print(b.x)

print(b.x.a1)

print(B.x.__dict__)

这次B.x.a1返回a1,访问到了描述器的数据

实例的__dict__优先于非数据描述器

数据描述器优先于实例的__dict__

__delete__方法有同样的效果,有了这个方法,就是数据描述器

本质(进阶)

不是数据描述器优先级高,而是数据描述器把实例的属性从__dict__中拿走了,访问顺序没变过

Python中的描述器

练习:

实现staticmethod非数据描述器

class StaticMethod:

def __init__(self, fn):

self._fn = fn

def __get__(self, instance, owner):

return self._fn

class A:

@StaticMethod # stmtd = StaticMethod(stmtd) 属性等于类的实例

def stmtd():

print('static method')

A.stmtd()

A().stmtd()

实现classmethod非数据描述器

from functools import partial

class ClassMethod:

    def __init__(self, fn):

        self._fn = fn

    def __get__(self, instance, owner):

        ret = partial(self._fn, owner)

        return ret

class A:

    @ClassMethod # clsmtd = ClassMethod(clsmtd)

    def clsmtd(cls):

        print(cls.__name__)

print(A.__dict__)

A.clsmtd

A.clsmtd()

对实例的数据进行校验

1、

class Person:

    def __init__(self, name: str, age: int):

        self.params = ((name, str), (age, int))

        if not self.checkdata(self.params):

            raise TypeError()

        self.name = name

        self.age = age

    def checkdata(self, params):

        for p, t in params:

            if not isinstance(p, t):

                return False

            return True

p = Person('tom', 18)

print(p.checkdata(p.params))

这种代码太过耦合,可以使用装饰器

使用描述器

class Typed:

def __init__(self, name, type):

self.name = name

self.type = type

def __get__(self, instance, owner):

if instance is not None:

return instance.__dict__[self.name]

return self

def __set__(self, instance, value):

if not isinstance(value, self.type):

raise TypeError(value)

instance.__dict__[self.name] = value

class Person:

name = Typed('tom', str)

age = Typed(18, int)

def __init__(self, name: str, age: int):

self.name = name

self.age = age

p = Person('tom', 18)

硬编码不好

使用inspect模块

import inspect

class Typed:

    def __init__(self, item, type):

        self.item = item

        self.type = type

    def __get__(self, instance, owner):

        if instance is not None:

            return instance.__dict__[self.item]

    return self

    def __set__(self, instance, value):

        if isinstance(value, self.type):

            instance.__dict__[self.item] = value

        else:

            raise TypeError(value)

def typeassert(cls):

    params = inspect.signature(cls).parameters

    print(params)

    for name, param in params.items():

        print(param.name, param.annotation)

        if param.annotation != param.empty:

            setattr(cls, name, Typed(name, param.annotation))

    return cls

@typeassert

class Person:

    def __init__(self, name: str, age: int):

        self.name = name

        self.age = age

    def __repr__(self):

        return '{} is {}'.format(self.name, self.age)

    __str__ = __repr__

p = Person('tom', 18)

print(p)