文章目录
特殊属性
属性 | 含义 |
---|---|
_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)
总结上下文应用场景
- 可以增强功能
- 打开的资源可以自动关闭,例如文件对象,网络连接,数据库连接。
- 权限验证(在执行代码前,做权限验证。在__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__()
方法签名如下
- object.__get__(self, instance, owner)
- object.__set__(self, instance, value)
- 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)
- 实现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)
- 对实例的数据进行校验,判断,函数先写
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)