一、property属性
1.目的:设置property是为了在给对象的属性进行操作的时候加以管理,调用方式如下:
class Foo:
def func(self):
print("func is working")
@property # 将方法变成了一个属性
def prop(self):
print("prop is working")
return "ok" # 返回值作为调用 foo.prop 的结果
foo = Foo()
foo.func()
x = foo.prop # 使用【对象名.property属性名】进行调用
print(x)
运行结果:
func is working
prop is working
ok
进程已结束,退出代码0
(1).demo案例
"""模拟京东网商品翻页"""
class Pager:
def __init__(self, current_page):
self.current_page = current_page
self.per_items = 10 # 每个页面能容纳10个商品
@property
def start(self): # 一个页面开始的单号
value = (self.current_page - 1) * self.per_items
return value + 1
@property
def end(self): # 一个页面结束的单号
value = self.current_page * self.per_items
return value
p = Pager(2)
print(f"{p.start}-{p.end}")
运行结果:
11-20
进程已结束,退出代码0
(2) 总结
我们在对属性进行操作时,如果不使用property方法,不仅需要单独设置函数来完成这个操作,而且还需要在调用的时候在进行调用,而使用property后就可以直接进行设置,不需要单独进行调用,而且普通属性调用时,首先要进行函数的运算,在返回给属性,我们在获取时还要返回给我们,而property不需要这么繁琐,调用时就会直接进行运算,运算完成后直接将返回值交给我们就好了。
2.property的2种应用方式
(1)装饰器方式(新式类)
class Goods:
def __init__(self,):
self.original_price = 100
self.discount = 0.8
@property
def price(self): # 相当于getter
new_price = self.discount * self.original_price
return new_price
@price.setter # 注意保持一致
def price(self, value): # 注意保持一致
self.original_price = value
@price.deleter
def price(self):
del self.original_price
goods1 = Goods()
print(goods1.price)
goods1.original_price = 1000
print(goods1.price)
运行结果:
80.0
800.0
进程已结束,退出代码0
(2) 类属性方式
class Goods:
def __init__(self,):
self.original_price = 100
self.discount = 0.8
def get_price(self):
new_price = self.discount * self.original_price
return new_price
def set_price(self, value):
self.original_price = value
def del_price(self):
del self.original_price
BAR = property(get_price, set_price, del_price) # 将【方法名】作为参数传入,不要括号
goods1 = Goods()
print(goods1.BAR)
goods1.original_price = 1000
print(goods1.BAR)
运行结果:
80.0
800.0
进程已结束,退出代码0
类属性和装饰器属性时完全一致的,只是相比较而言,类属性方式更加简洁,而装饰器方式可读性高。
二、元类
1.元类就是一个专门用来创建其他类的特殊类,最顶层的元类就是type
。类也是一个对象,也就是说可以将类作为一个实参传递。
def choose_class(name):
if name == "foo":
class Foo():
print("ok")
return Foo
else:
class Bar():
pass
return Bar
A = choose_class("foo") # <__main__.choose_class.<locals>.Foo object at 0x000001A9F8505810> A是一个类
a = A() # <class '__main__.choose_class.<locals>.Foo'> a是类的实例对象
print(a, A)
总结:
(1).类的定义可以放在函数中
(2).给函数返回的参数不同,得到类的对象不同,从而创建的类的对象不同
(3).可以动态的创建类
2.由type创建类
type语法:type(类名, 由父类名称组成的元组(可以为空), 包含属性的字典(名称和值) )
b = type("B", (), {}) # 这样就使用type创建了一个类,b仅为一个变量名,B为类名,一般将b写成B,提高可读性
print(help(b))
运行结果:
Help on class B in module __main__:
class B(builtins.object) # 这就是上面创建的类
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
None
进程已结束,退出代码0
3.使用type创建带有属性的类
B = type("B", (), {"bar": True})
b = B()
print(b.bar)
运行结果:
True
进程已结束,退出代码0
4.使用type创建带有方法的类
class A(object):
num = 100
def print_b(self): # 实例方法
print(self.num)
@staticmethod # 静态方法
def print_static():
print("print_static is working")
@classmethod # 类方法
def print_class(cls):
print(cls.num)
B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class})
b = B()
b.print_b() # 调用print_b方法
b.print_static() # 调用print_static方法
b.print_class() # 调用print_class方法
运行结果:
100
print_static is working
100
进程已结束,退出代码0
5.自定义元类
(1)使用函数自定义
def upper_attr(class_name, class_parents, class_attr):
# class_name就是下面的类名Foo,class_parents是object,class_attr是类里面的属性以字典的形式 {"bar": bip}
new_attr = {}
for name, value in class_attr.items(): # name就是”bar“, value就是bip
if not name.startswith("__"): # name是否是以下划线开始的
new_attr[name.upper()] = value # 创建一个新字典
return type(class_name, class_parents, new_attr) # 返回一个class
# 这里太容易混淆了,class是以【return type(class_name, class_parents, new_attr)】这个语句创建的类,不是以下面的形式创建的类
class Foo(object, metaclass=upper_attr):
bar = "bip"
print(hasattr(Foo, "bar")) # 这里返回为False是因为Foo不是以上述形式所创建的,故没有bar
print(hasattr(Foo, "BAR"))
# print(Foo.__dict__)
f = Foo()
print(f.BAR)
运行结果:
False
True
bip
进程已结束,退出代码0
(2)使用其他类自定义
class UpperAttrMetaClass(type):
# __new__创建一个对象,__init__只是初始化一个对象
def __new__(cls, class_name, class_parents, class_attr):
new_attr = {}
for name, value in class_attr.items(): # name就是”bar“, value就是bip
if not name.startswith("__"): # name是否是以下划线开始的
new_attr[name.upper()] = value # 创建一个新字典
return type(class_name, class_parents, new_attr) # 返回一个class
class Foo(object, metaclass=UpperAttrMetaClass):
bar = "bip"
print(hasattr(Foo, "bar"))
print(hasattr(Foo, "BAR"))
f = Foo()
print(f.BAR)
运行结果:
False
True
bip
进程已结束,退出代码0
总结:
元类只有在极少数情况下才会使用,所以不需要理解的太透彻。
三、描述符(Descriptor)
- 定义:如果一个类有__get__, __set __, __ delete __三个中的一个或多个,那么用这个类所创建的对象,可以称为描述符。
- 目的:描述符是具有"绑定行为"的对象属性。可以用来自定义类属性
没啥可说的,直接上代码。
class Desc:
def __set_name__(self, owner, name):
self.__name = name # Desc.__name 这是定义了一个临时属性(这个临时属性是instance这个实例对象的)用来存储描述符名即 [Desc.__name = Username]
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError(f"{value} is not a string")
if len(value) == 0:
raise ValueError(f"{value} is empty")
instance.__dict__[self.__name] = value # instance实例对象的属性中的[临时属性:self.__name] = value
def __get__(self, instance, owner):
return instance.__dict__[self.__name]
class Email:
username = Desc()
password = Desc()
def __init__(self, username, password, iphone):
self.username = username
self.password = password
self.iphone = iphone
mail = Email("路西恩", "1314521", 1234567890)
代码流程图如下:
3.描述符的应用
(1).使用描述符实现staticmethod (真提莫的难,我测)
class ClassMethodNew:
def __init__(self, func):
self.func = func # self.func = b
def __get__(self, instance, owner): # self是b函数,instance是A的实例对象,owner是A类
print(self.func, instance, owner)
def call(*args):
self.func(owner, *args) # b(owner, *args),owner是A,所以这里等价于b(A)
return call
class A:
M = 100
def a(self):
print("a是实例方法")
@ClassMethodNew # 等价于 b = ClassMethodNew(b)
def b(cls):
print("b是类方法1")
print(cls.M)
print("b是类方法2")
@ClassMethodNew
def c(cls, num1, num2):
print("c是类方法1")
print(cls.M + num1 + num2)
print("c是类方法2")
obj = A()
obj.b()
A.b()
obj.c(11, 22)
运行结果:
<function A.b at 0x0000015BD0A7A200> <__main__.A object at 0x0000015BD0A86850> <class '__main__.A'>
b是类方法1
100
b是类方法2
<function A.c at 0x0000015BD0A7A2A0> <__main__.A object at 0x0000015BD0A86850> <class '__main__.A'>
c是类方法1
133
c是类方法2
进程已结束,退出代码0
代码流程图如下
4. 描述符的调用机制
(1)_dict _访问顺序
Python中所有的对象都有一个字典,这个字典存储着它所拥有的所有属性,属性名为key,属性值为value。
instance.m的访问顺序:
- 程序会先查找
instance.__dict__["m"]
是否存在 - 不存在再找
type(instance).__dict__["m"]
中查找,其中type(instance)
指的是instance的父类。 - 然后找
type(instance)
的父类 - 期间找到的是普通纸就输出,如果找到的是一个描述符,则调用
__get__
方法。
- 数据描述符 / 数据资料描述符( 非数据描述符 / 非数据资料描述符)
5.1 深入理解描述符
class M:
def __init__(self):
self.x = 1
def __get__(self, instance, owner):
return self.x
def __set__(self, instance, value):
instance.__dict__[self.x] = value
class AA:
m = M() # 描述符会调用M中的方法
n = 2
def __init__(self, score):
self.score = score
aa = AA(3)
print(aa.__dict__) # aa中的属性只有score,m和n是它的父类的属性,通俗来讲就是,aa能用m和n,但不是aa的
print(aa.score)
print(aa.__dict__["score"])
print(type(aa).__dict__) # type(aa)是aa的父类
print(aa.n)
print(type(aa).__dict__["n"])
print(aa.m) # 在aa中找不到m,所以去父类里面找,在父类里m指向描述符,所以调用描述符方法,返回self.x = 1
print(type(aa).__dict__["m"].__get__(aa, AA)) # type(aa).__dict__["m"]这是M的实例对象,既然是实例对象,就可以调用M中的函数。所以
# type(aa).__dict__["m"].__get__(aa, AA)就等价于m.__get__[aa,AA],其中aa就是instance, AA就是owner
print("_" * 20)
print(AA.m)
print(AA.__dict__["m"].__get__(None, AA))
5.2数据描述符,非数据描述符
- 同时定义了
__set__
和__get__
的方法的描述符称为数据描述符 - 只定义了
__get__
的描述符称为非数据描述符 - 二者的区别:当属性名与描述符名相同时,在访问这个同名属性时,如果是数据描述符就会优先访问描述符,如果是非数据描述符就会先访问属性,因为非数据描述符没有
__set__
方法,所以会使用init赋值。
class M:
def __init__(self):
self.x = 1
def __get__(self, instance, owner):
# print("get m here")
return self.x
def __set__(self, instance, value):
# print("set m here")
self.x = value + 1
class N:
def __init__(self):
self.x = 1
def __get__(self, instance, owner):
# print("get n here")
return self.x
class AA:
m = M()
n = N()
def __init__(self, m, n):
self.m = m # 当属性名m与描述符名m相同时,会发生冲突,但此时描述符m是数据描述符,所以执行描述符,不执行__init__函数,此时运行结果就是aa.m = 6,即数据描述符优先级比属性高
self.n = n # 与上面同理,但此时描述符是数据描述符,所以现在执行__init__函数,而不执行描述符n,所以结果是aa.n = 5,非数据描述符优先级要比属性低。
aa = AA(5, 5)
# print(aa.__dict__)
# print(AA.__dict__)
print(aa.m)
print(aa.n)
# print(AA.n)
# aa.m = 6
# print(aa.m)
# print("_" * 20)
# print(AA.__dict__["n"].__get__(None, AA))
# print(AA.__dict__["n"].__get__(aa, AA))
运行结果:
6
5
进程已结束,退出代码0
5.3 只读描述符
要想制作一个只读描述符,需要同时定义__set__
和__get__
,并在__set__
中引发一个AttributeError异常。定义一个引发异常__set__
方法就足够让一个描述符成为数据描述符,切是只读的
class OnlyRead:
def __set_name__(self, owner, name):
self._property_name = 3.14
def __set__(self, instance, value):
raise AttributeError("This val is only read")
def __get__(self, instance, owner):
return self._property_name
class Math:
pi = OnlyRead()
math = Math()
print(math.pi)
math.pi = "3"
运行结果:
Traceback (most recent call last):
File "H:\PycharmProjects\pythonProject1\text1.py", line 18, in <module>
math.pi = "3"
^^^^^^^
File "H:\PycharmProjects\pythonProject1\text1.py", line 6, in __set__
raise AttributeError("This is val is only read")
AttributeError: This val is only read
3.14
进程已结束,退出代码1
- 描述符的注意点
- 不可以用 实例属性 定义描述符
class NameDes:
def __init__(self):
self.__name = None
def __set__(self, instance, value):
print("set is working")
if isinstance(value, str):
self.__name = value
else:
raise TypeError("必须是字符串")
def __get__(self, instance, owner):
print("get is working")
return self.__name
class Person():
# name = NameDes() # 类属性时,描述符才有作用
def __init__(self):
self.name = NameDes() # 实例对象不会起到描述符的作用
person = Person()
print(person.name)
# 如果将描述符对象赋值给实例属性,此时相当于name指向了一个普通的对象,只不过这个对象中有__get__,__set__方法而已,不会自动调用其__get__方法
- 注意事项
class MaxValues:
def __init__(self, inti_val, max_val):
self.value = inti_val
self.max_val = max_val
def __get__(self, instance, type_):
return self.value
def __set__(self, instance, value):
self.value = min(self.max_val, value)
class Widget:
volume = MaxValues(0, 10)
a = Widget()
print("a默认的volume值:", a.volume)
a.volume = 12
print("a设置后的volume值:", a.volume)
b = Widget() # 两个实例对象共用一个描述符对象和类对象,两者不独立
print("b默认的volume值:", b.volume)
运行结果:
a默认的volume值: 0
a设置后的volume值: 10
b默认的volume值: 10
进程已结束,退出代码0
上述代码是有问题的,在使用a设置value的之后,另一个对象访问数时就会访问被设置好的值,所以就会出错。其流程图如下:
改进方法如下:
class MaxValues:
def __init__(self, inti_val, max_val):
self.value = inti_val
self.max_val = max_val
self.data = {}
def __get__(self, instance, owner): # instance就是a,b
if not instance:
return self.value
return self.data.get(instance, self.value) # get可以取出字典中的value,但是a.get(instance, self.value)就相当于instance or self.value
def __set__(self, instance, value):
self.data[instance] = min(self.max_val, value) # self.data[instance]就是字典将{“instance”: value}
class Widget:
volume = MaxValues(0, 10)
a = Widget()
print("a默认的volume值:", a.volume)
a.volume = 12
print("a设置后的volume值:", a.volume)
b = Widget() # 两个实例对象共用一个描述符对象和类对象,两者不独立
print("b默认的volume值:", b.volume)
运行结果:
a默认的volume值: 0
a设置后的volume值: 10
b默认的volume值: 0
进程已结束,退出代码0
其流程图如下:
定义一个空字典就可以避免这种情况。