python魔术方法(进阶)斐波那契数列

特殊属性
属性含义
_name_类、函数、方法等的名字,要调用实例显示的话要在前加上__class__
_class_对象 或 类所属的类名
_module_对象或类所属的类 (父)
_doc_类、函数的文档字符串,如果没有定义则为None (类和实例)
_dict_类或实例的属性,可写的字典(类和实例)

mro() 调用方法,类的名字加上.mro() # 可以得到查找循序
例子:

class Myname:
    def __init__(self, nam):
        self.nam = nam

    # __name__和__class__的运用
    def show(self):
        # print('11', self.__class__.__name__)  # __name__ 不能直接通过实例调用
        print('44', __class__.__name__)  # 少了self实例得到的是类的名字

    def show2(self):
        print('22', self.__module__)  # 类所定义的模块名
        # print('55', self.__mro__)


class Car(Myname):
    def carshow(self):
        print('33', self.__module__)  # 类所定义的模块名


b = Car('tom')
print(b.show())
print(b.show2())
print(b.carshow())
print(Car.mro() # 查找循序

查看属性
方法意义
_dir_返回类或者对象的所有成员名称列表。dir()函数就是调用 _dir_ ()。 使用实例调用时,如果提供 _dir_ (),则返回其返回值,要求是可迭代对象。 如果没有提供 _dir_() ,则会从实例和类及祖先类中收集信息

用法:
dir() 括号里加上 你想查询的对象
如何用dir()函数查看模块属性和方法,例子:

class Animal:
    x = 123

    def __init__(self, name, age=20, color=10):
        self.name = name
        self.age = age
        self.color = color

    def show(self):
        pass


print('the dir is {}'.format(dir()))  # 在模块中返回模块的属性
print('the dir is {}'.format(dir(Animal)))  # 在返回模块的变量名
print(dir([]))

class Person:
    def show(self):
        a = 100
        t = int(a)
        print(dir())
def test(a=50, b=100):
    print(dir())
Person().show() # 实例的输出:a t self
test() # 只输出变量

实例化
方法意义
_new_实例化一个对象 该方法需要返回一个值,如果该值不是cls的实例,则不会调用 _init_ 该方法永远都是静态方法。等于创建一个类的实例的方法

_new_ 方法很少使用,即使创建了该方法,也会使用 return super().__new__(cls) 基类object的 _new_ 方 法来创建实例并返回。

class Persron(object):
    def __new__(cls, *args, **kwargs):
        print('new')
        return super().__new__(cls)  # 

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return '<Preson {} {} >'.format(self.name, self.age)

if __name__ == '__main__':
    pi = Persron('tom', 18)
    print(pi)  # 缺少str 或者repe 看到的只是一个类对象的实例 <__main__.Persron object at 0x10214fcf8>

可视化
方法意义
__str__str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没有定义,就 去调用 __repr__ 方法返回字符串表达,如果 __repr__ 没有定义,就直接返回对象的内存地 址信息
__repr__内建函数repr()对一个对象获取字符串表达。调用 __repr__ 方法返回字符串表达,如果 __repr__ 也没有定义,就直接返回object的定义 就是显示内存地址信息
__bytes__bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象

例子:

class C:
    def __init__(self, name):
        self.name = name

    def __bytes__(self):  # 二进制转换
        # return "{} is {}".format(self.name, self.age).encode()
        import json
        return json.dumps(self.__dict__).encode()

    def __repr__(self): # 装换成字符串输出,没有定义的话,会返回对象的内存地址信息
        return '<re:{}>'.format(self.name)


print(C('tom'))
print(bytes(C('123'))) # 二进制装换,成json


hash
方法意义
_hash_内建函数 hash() 调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
_eq_对应==操作符,判断2个对象是否相等,返回bool值
class A:
    def __init__(self, name, age=18):
        self.name = name

    def __hash__(self):
#         return 1
        return hash(self.name) # 可以调用hash()
    def __eq__(self,other):
        return self.name == other.name

    def __repr__(self):
        return self.name

a = A('kenny')
b = A('kenny')
c = A('tom')
print(id(a),id(b),id(c)) # ip地址都不一样
# 内容对比 用到eq
print('1.',a == b) 
print('2.',a==c )
print(hash(a),hash(b),hash(c))
print('3.',a is b) # hash地址一样,但是ip地址不一样,所以是False

print({a,b})
# 总结: == 比较的是内容, is 比较的是ip地址, set集合要去重,必须内容和哈希地址一样

set() 集合的内容和****hash值地址一样就去重。有hash冲突并不去重,也不看id地址


练习: 设置一个二维坐标Point 它是可hash类型,并比较2个实例坐标是否相等


from collections import Hashable # 导入hashable判断是否hash类型
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        # return hash((self.x, self.y))
        return 1

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

a = Point(4, 5)
b = Point(5, 6)
c = Point(5, 6)
print(a.__hash__(), b.__hash__())
print(a == b)
print(b == c)
print(isinstance(a,Hashable)) #True

bool
方法意义
__ bool__内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。 没有定义 __bool__ (),就找__ len__ ()返回长度,非0为真。如果 __len__ ()也没有定义,那么所有实例都返回真

例子:(注意不能通过是否带引号来判断输出值的类型,判断类型还是要选择type或者isinstance)

class B:
    def __bool__(self):
        return False


print(bool(B))  # 判断有没有存在
print(bool(B()))  # 有定义的实例调用为return


class A: pass


class C:
    def __len__(self):
        return 0


print(bool(A()))
if A():
    print('Real A')
print(bool(A)) 
print(bool(A())) # 没有定义,全部实例返回为真  
print(bool(C()))  # __bool__没找到默认找__len() 返回长度,非0为真

运算符重载应用场景

在这里插入图片描述

往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中 的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point。 提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。

例子:实现A类的2个实例相减

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

    def __sub__(self, other):  # 定义的是-
        return self.age - other.age

    def __isub__(self, other):  # 如果没有定义__isub__,则会调用__sub__
        return A(self.name, self - other)


a = A('kek', 18)
b = A('yuyu', 20)
print(a - b)

例子二:我们定义一个“人”的类People,当中有属性姓名name、年龄age。让你需要利用sorted函数对一个People的数组进行排序,排序规则是按照name和age同时排序,即name不同时比较name,相同时比较age。由于People类本身不具有比较功能,所以需要自定义,你可以这么定义People类

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

    def __gt__(self, other):
        return self.name > other.name if self.name != other.name else self.age > other.age

    def __repr__(self):
        return '<{}: {} {}>'.format(__class__.__name__, self.name, self.age)


tom = People('tom', 18)
jenny = People('jenny', 20)
s1 = [1, 4, 2, 5, 123, 3]
for item in sorted([tom, jenny, People('jenny', 28)]):
    print(item)

__eq__ 等于可以推断不等于
__gt__ 大于可以推断小于
__ge__ 大于等于可以推断小于等于
也就是用3个方法,就可以把所有比较解决了


容器相关方法
方法意义
__len__内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list 或者dict。bool()函数调用的时候,如果没有 __bool__() 方法,则会看 __len__() 方法 是否存在,存在返回非0为真
__iter__迭代容器时,调用,返回一个新的迭代器对象
__getitem__实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为 hashable。key不存在引发KeyError异常
__setitem__和 __getitem__ 的访问类似,是设置值的方法

例子:构建一个购物车

class Cart:
    # 初始化列表
    def __init__(self):
        self.car = []

    # 调用函数,增加
    def additem(self, name):
        return self.car.append(name)

    # 不要返回对象内存地址
    def __repr__(self):
        return '<{} {}>'.format(__class__.__name__, self.car)

    # 判断列表长度
    def __len__(self):
        return len(self.car)

    # 可以迭代
    def __iter__(self):
        return iter(self.car)

    # 返回一个对象可以链式编程
    def __add__(self, other):
        self.car.append(other)
        return self

    # 通过索引取到列表的值
    def __getitem__(self, index):
        return self.car[index]

    # 通过索引修改列表的值
    def __setitem__(self, index, value):
        self.car[index] = value
        # return self
    # def __contains__(self, index):
    #     self.car = index


# def
# def __add__(self, other):


cart = Cart()
cart.additem(1)
cart.additem('abc')
cart.additem(3)
print(cart.car)
# 长度、bool
print(len(cart))
print(bool(cart))
# # 迭代
for x in cart:
    print(x, end=' ')
# # in
print()
print(3 in cart)
print(2 in cart)
# # 链式编程实现加法
print(cart + 4 + 5 + 6)
print(cart.__add__(17).__add__(18))
# 索引操作
print(cart[1])
cart[1] = 'xyz'

可调用对象

python 中一切皆对象
__call__,类中定义一个该方法,实例可以像函数一样使用
例子:

def foo():
    print(foo.__module__, foo.__name__)  # 类的名字,函数的名字
foo()
foo.__call__() # 函数即对象,对象foo加上() ,就是调用函数对象call__() 方法

练习:
定义一个类,并实例化得到其实例,像实例函数一样去调用。

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

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

    def __repr__(self):
        return '<{},{},{}>'.format(self.__class__.__name__, self.name, self.age)


c1 = Person('tom', 12)
print(c1())


class add:
    ## 不初始化对象
    # def __call__(self, *args, **kwargs):
    #     ret = 0
    #     for x in args:
    #         print(x)
    #         ret += x
    #     self.ret = ret
    #     return ret

    # 初始化对象
    def __init__(self, *args):
        self.args = args

    def __call__(self, *args, **kwargs):
        ret = 0
        for i in args:
            ret += i
        return ret


adder = add()
print(adder(4, 6))
# print(adder.ret)
## 2. 定义一个斐波那契数列,计算第n项[0、1、1、2、3、5、8、13、21]
	
# [0、1、1、2、3、5、8、13、21]
# 普通方法
a = 1
b = 0
for i in range(6):
    a, b = b, a + b
print(b)
# 函数,生成器 做成生成器函数

def fib():
    a = 1
    b = 0
    while True:
        a, b = b, a + b
        yield a
foo = fib()
for i in range(10):
    print(next(foo))		

# 递归
def fib(n):return n if n <= 1 else fib(n - 2) + fib(n - 1)

#做成一个类:	
class Fib:
    def __init__(self):
        self.x = 1
        self.y = 1

    def __call__(self, index):
        if index <= 1:
            return index
        else:
            for i in range(index - 2):
                self.x, self.y = self.y, self.x + self.y
        return self.y
fib = Fib()
print(fib(5))

# 如何提高效率,一是使用cache缓存,二是将类做成一个列表,
# 一 缓存
import functools
@functools.lru_cache()
def fib(n):
    return n if n <= 1 else fib(n - 2) + fib(n - 1)
print(fib(135))

# 二 类的列表 实现,长度,可索引,可迭代,可实例调用
class Fib:
    def __init__(self):
        self.lb = [0, 1, 1]

    def __call__(self, index):
        if index < len(self.lb):
            return self.lb[index]
        for i in range(3, index+1):
            self.lb.append(self.lb[i - 2] + self.lb[i - 1])
        return self.lb
fib = Fib()
print(fib(3))

class Fib:
    def __init__(self):
        self.arrlis = [0, 1, 1]

    def __call__(self, index):
        if index < len(self.arrlis):
            return self.arrlis[index]
        for i in range(3, index + 1):
            self.arrlis.append(self.arrlis[i - 2] + self.arrlis[i - 1])
        return self.arrlis[index]

    # 可迭代
    def __iter__(self):
        return iter(self.arrlis)
	#返回长度
    def __len__(self):
        return len(self.arrlis)
	#通过索引取值
    def __getitem__(self, item):
        return self.arrlis[item]


fib = Fib()
print(fib(5))
for i in fib:
    print(i)
print(len(fib))
print(fib[4])

上下文管理

使用with…as语法,可以用文件IO操作对文件对象使用上下文管理当一个对象同时实现__enter__ 和__exit__的方法,它就属于上下文管理。enter 和__exit__必须成对出现

方法意义
__enter__进入对象相关的上下文,with语法会把其返回值绑定到 as 字句的指定变量上
__exit__退出上下文管理,返回值可以压制主程序的异常

例子:

class Point:
    def __init__(self):
        print('1')

    def __enter__(self):
        print('2')
        return '==='

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('3')


if __name__ == '__main__':
    with Point() as f:  # 实例化对象时,会执行init属性 但是不会进入enter里,with语法会调用到enter方法。
        print('---')
        print(f)  # ==enter里的返回值

注意:with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。with并不开启一个新的作用域。


上下文管理的安全性

异常并不会影响上下文的进入和退出。with会调用enter方法。

即以上例得到,如果调用上下文管理,先会初始化实例,然后通过with 执行enter方法,里面执行完之后再执行主语句体的内容。如果主语句体报错,也不会影响exit的执行

所以:上下文管理是安全的


方法的参数

__enter__ 方法 没有其他参数。
__exit__ 方法有3个参数:
__exit__(self, exc_type, exc_value, traceback) return True 可以压制异常
这三个参数都与异常有关。 如果该上下文退出时没有异常,这3个参数都为None
exc_type ,异常类型
exc_value ,异常的值
traceback ,异常的追踪信息

练习:为加法函数计时
1.用装饰器显示函数的执行时长
2 . 用上下文管理方法来显示函数的执行时长

##1
import time
import datetime
from functools import wraps
def logger(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        print('begin')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        end = ((datetime.datetime.now()) - start).total_seconds()
        print('{},run is {}'.format(fn.__name__, end))
        return ret
    return inner
@logger  # add = logger(add)
def add(x, y):
    time.sleep(2)
    return x + y
if __name__ == '__main__':
    print(add(4, 5))

## 2 
import time
import datetime
from functools import wraps


class Timeni:
    def __init__(self, fn):
        self.fn = fn

    def __enter__(self):
        print('begin')
        self.start = datetime.datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        end = (datetime.datetime.now() - self.start).total_seconds()
        print('{},run time is {}'.format(self.fn.__name__, end))
        print('byebye')


def add(x, y):
    time.sleep(2)
    return x + y


if __name__ == '__main__':
    with Timeni(add) as f:
        print(add(4, 5))
        print(add(7, 8))
# 或者可以将Timeni当成可调用对象实现,就不用写add
   def __call__(self, x, y):
        return self.fn(x, y)
        
 if __name__ == '__main__':
    with Timeni(add) as f:
        print(f(4, 5))

修改成类属性装饰器:

import time
import datetime
from functools import wraps


class Timeni:
    """this is timeni class"""

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

    def __call__(self, *args, **kwargs):
        self.start = datetime.datetime.now()
        ret = self.fn(*args, **kwargs)
        duration = (datetime.datetime.now() - self.start).total_seconds()
        print('{} run is {}'.format(self.fn.__name__, duration))
        return ret


@Timeni  # add = Timeni(add)
def add(x, y):
    """this is add function"""
    time.sleep(2)
    return x + y


if __name__ == '__main__':
    print(add(4, 5))
    print(add.__doc__) # doc的名字变了
    print(add.__class__.__name__) # add 的名字是类的名字

# 修改:
## 在类的init方法下直接修改字符串self.__doc__ = self.fn.__doc__
##方法二:
from functools import wraps, update_wrapper
在__init__下,调用update_wrapper(self,fn)
## 方法三:
 wrap(fn)(self)

总结上下文应用场景

  1. 可以增强功能
  2. 打开的资源可以自动关闭,例如文件对象,网络连接,数据库连接。
  3. 权限验证(在执行代码前,做权限验证。在__enter__中处理。)

contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 __enter__ 和 __exit__ 方法。 对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。 也就是这个装饰器接收一个生成器对象作为参数

contextlib.contextmanager装饰器,yield前代表__extend__ 。yield后代表__exit__ yield代表__extend的返回值

import contextlib
@contextlib.contextmanager
def add():
    """this is add function"""
    print('begin')
    try:
        yield 2
    finally:
        print('end')


if __name__ == '__main__':
    with add() as f:
        print(f)
 # 如果不加 try..finally, 程序出现异常时,无法执行后继的end,

练习:依然是给add函数通过上下文管理的方式计算时间

import contextlib
import time
import datetime


@contextlib.contextmanager
def add(x, y):
    """this is add function"""
    print('begin')
    start = datetime.datetime.now()
    try:
        yield x + y
    finally:
        duration = (datetime.datetime.now() - start).total_seconds()
        print('the time is {}'.format(duration))


if __name__ == '__main__':
    with add(4, 5) as f:
        time.sleep(2)
        print(f)

总结 如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter__ 和 __exit__ 方法方便。

反射

反射,reflection,指的是运行时获取类型定义信息
简单说,在Python中,在运行的时候,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或者自省。
具有反射能力的函数有type(),isinstance(),callable(),dir(), getattr()

内建函数意义
getattr(object, name[, default])通过name返回object的属性值。当属性不存在,将使用default返回,如果没有 default,则抛出AttributeError。name必须为字符串
setattr(object, name, value)object的属性存在,则覆盖,不存在,新增
hasattr(object, name)判断对象是否有这个名字的属性,name必须为字符串

如果为实例增加方法,是没有绑定的
例子:有一个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(4, 5)
print(p)
# p.show()
setattr(p, 'z', 21)
print(getattr(p, 'z', 'NONe'))
print(getattr(p, 'k', 'NONe'))
print(getattr(p, '__dict__'))
print(dir(p))
print(p.__dir__())
# 动态调用方法
if hasattr(p, 'show'):
    getattr(p, 'show')()
# 为类创建相等
setattr(Point, 'xyz', 100)
print(Point.__dict__)
# 为类增加方法
if not hasattr(Point, 'show'):
    setattr(Point, 'show', lambda self: print(self.x, self.y))
p2 = Point(4, 5)
p2.show()
# 实现两个实例相加
if not hasattr(Point, 'add'):
    setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))

print(p2.add(p))
# 实现两个实例相减
if not hasattr(Point, 'sub'):
    setattr(Point, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(p2.sub(p))

练习:建立一个命令分发器,通过名称找对应的函数执行

class Fenfa:
    def __init__(self):
        pass

    def reg(self, name, fn):
        setattr(self, name, fn)

    def run(self):
        while True:
            cmd = input('>>>')
            if cmd == 'quit':
                print('bye')
                break
            getattr(self, cmd, lambda : print('Not emted{}'.format(cmd)))()


fenfa = Fenfa()
fenfa.reg('ls', lambda :print('so'))
fenfa.run()
反射相关的魔术方法
方法意义
__getattr__()实例属性会按照继承关系找,如果找不到,就会执行 __getattr__() 方法,如果没有这个方法,就会抛出 AttributeError异常表示找不到属性。查找属性顺序为:instance.__dict__ --> instance.__class__.__dict__ --> 继承的祖先类(直到object)的__dict__ —找不到–> 调 用__getattr__()
__setattr__()通过 . 访问实例属性,进行增加、修改都要调用它管
__delattr__()防止实例删除属性

__getattr__的使用例子:

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的话只能返回None
        # return "missing{}".format(item)
        value = 1000
        self.__dict__[item] = value  # 所有缺失属性的一个缺省值,可以加到实例字典看到,相当于后悔药,如果直接return 1000 加不进实例字典里
        return value



P1 = Point(4, 5)
print(P1.x)
print(P1.z)
print(P1.n)
print(P1.t)  # 跟getattr 相关,没有getattr的话会报错,表示找不到属性
print(P1.l)
print(P1.__dict__)

__setter__使用方法:实例通过.点号设置属性,例如 self.x = x 属性赋值,就会调用 __setattr__() ,属性要加到实例的 __dict__ 中,就需要自己完成。

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的话只能返回None
        # return "missing{}".format(item)
        value = 1000
        self.__dict__[item] = value
        return value

    def __setattr__(self, key, value):
        self.__dict__[key] = value
        # print('{} = {}'.format(key, value))


P1 = Point(4, 5)
print(P1.x)
print(P1.y)
print(P1.__dict__)
P1.z = 250 # 实例通过.点号设置属性,例如 self.x = x 属性赋值,就会调用 __setattr__() ,属性要加到实例的 __dict__ 中,就需要自己完成。
print(P1.z)
print(P1.__dict__)

__delattr__() 使用方法:防止实例删除属性

class Base:
    n = 0
class Point(Base):
    z = 6

    # d = {'tttt': '20000'}

    def __init__(self, x, y):
        self.x = x
        self.y = y

  #  def __delattr__(self, item):
    #    print('is not del {}'.format(item))
        # return super().__delattr__(self, *args)

P1 = Point(4, 5)
del P1.x # 可以删除
print(P1.__dict__)
print(Point.__dict__)
# P1.z = 15

__getattribute__和__getattr配合使用。__getattribute__实例所有的属性调用都从这个方法开始

class Point(Base):
    z = 6
    d = {'tttt': '20000'}

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return __class__.d[item]

    # def __setattr__(self, key, value):
    #     self.__dict__[key] = value
    #     # print('{} = {}'.format(key, value))

    def __getattribute__(self, item):
        # return super.__getattribute__(item)
        raise AttributeError('aaa')


P1 = Point(4, 5)
print(P1.tttt)
描叙器

用到3个魔术方法: __get__() 、 __set__() 、 __delete__()
方法签名如下

  1. object.__get__(self, instance, owner)
  2. object.__set__(self, instance, value)
  3. object.__delete__(self, instance)
    self 指代当前实例,调用者 instance 是owner的实例 owner 是属性的所属的类
class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A init')
            #  A的实例 #属组的实例 # 属组的类
    def __get__(self, instance, owner):  # 需要返回值,instance属组的属性即B实例, owner
        print(self, instance, owner)
        print(self.__dict__)
        return self


class B:  # 属组
    x = A()  # 访问A实例里面的get

    def __init__(self):
        print('b init')


# print(B.x)  # 对A实例的魔术方法的get
# print(B.x.a1) # A的实例访问a1
b = B()
print(b.x)
#因为定义了 __get__ 方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问, 就会调用 __get__ 方法
class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A init')
        #  A的实例 #属组的实例 # 属组的类
# 描述器
    def __get__(self, instance, owner):  # 需要返回值,instance属组的属性即B实例, owner
        print(self, instance, owner)
        print(self.__dict__)
        return self


class B:  # 属组
    x = A()  # 类属性可以,描述器和属主类的类属性有关

    def __init__(self):
        self.y = A()  # 实例属性没用
        print('b init')


b = B()
print(b.y) # b.x才会触发描述器的方法

描述器定义

Python中,一个类实现了 __get__ 、 __set__ 、 __delete__ 三个方法中的任何一个方法,就是描述器。
如果仅实现了 __get__ ,就是非数据描述符 non-data descriptor;
同时实现了 __get__ 、 __set__ 就是==数据描述符 data descriptor ==赋值将会跑到set方法当中。优先级更高
如果一个类的类属性设置为描述器实例,那么它被称为owner属主。
当该类的属性被查找、设置、删除的时候,就会调用描述器相应的方法。

练习:实现staticmethod装饰器、实现classMethod装饰器

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')


A.stmtd()
A().stmtd()

from functools import partial  # 偏函数


class ClassMethod:
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, instance, cls):
        ret = partial(self.fn, cls)  # 将例注入
        return ret


class B:
    @ClassMethod  # stmtd = StaticMethod(stmtd)
    def stmt(cls, x, y):
        print(cls.__name__, x, y)


print(B.__dict__)
print(B.stmt)
B.stmt(4, 5)
B().stmt(4, 5)
        
  1. 实现property装饰器:
#!/usr/bin/env python
# -*- coding: utf-8 -*-


class Property:
    def __init__(self, fget, fset=None):  # 等于一个函数,实例调用
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget(instance)

    def __set__(self, instance, value):  # 先实现只读属性
        if self.fset is None:
            raise AttributeError(123)

        self.fset(instance, value)  #  返回

    def setter(self, fn):
        self.fset = fn
        return self


class A:
    def __init__(self, x):
        self._x = x

    @Property  # self.fn = Property(self.fn)
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value


a = A(10)
print(a.x)
a.x = 1000
print(a.x)

  1. 对实例的数据进行校验,判断,函数先写
class Person:
    def __init__(self, name: str, age: int):
        parmr = ((name, str), (age, int))
        if not self.chank(parmr):
            raise TypeError('int or str')
        self.name = name
        self.age = age

    def chank(self, parmr):
        for i,v in parmr:
            if not isinstance(i,v):
                return False
        return True
Person('tom','12')

对实例的数据进行校验,装饰器

import inspect


def check(cls):
    def wrapper(*args, **kwargs):
        sig = inspect.signature(cls)  # 拿到签名
        params = sig.parameters  # 拿到有序字典
        # print(params.items())
        for x, (k, v) in zip(args, params.items()):
            if v.annotation != inspect._empty and not isinstance(x, v.annotation):
                raise TypeError('1!!!!!!!!!!!!!')
        re = cls(args, kwargs)
        print('end')
        return re

    return wrapper


@check  # Person = check(Pecson)
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


a = Person('tom', '12') # 报错,成功


对实例的数据进行校验,描述器

class TypeCheck:  # 描述器
    def __init__(self, name, typ):
        self.name = name
        self.type = typ

    def __get__(self, instance, owner):
        # print('get~~~~~~~~~~~~~~')
        if instance:
            return instance.__dict__[self.name]
        else:
            raise Exception  # 或者return self,总之不正常

    def __set__(self, instance, value):
        # print('set~~~~~~~~~~~~~')
        if instance:
            if not isinstance(value, self.type):
                raise TypeError(self.name, '+++++++++++')
            else:
                instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样

    # def __set_name__(self, owner, name):  # python3.6新增的方法
    #     print(name)
    #     self.name = name


class TypeInject:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwarg):
        sig = inspect.signature(self.cls)
        params = sig.parameters
        # print(params)  # OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
        for name, param in params.items():
            # print(name, param.annotation)
            if param.annotation != param.empty:  # inspect._empty
                setattr(self.cls, name, TypeCheck(name, param.annotation))

        return self.cls(*args, **kwarg)


@TypeInject  # Person = TypeInject(Person)
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


p5 = Person('Green', 28)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值