面向对象
可以把数据以及功能打包为一个整体
类:
- 名称
- 属性(数据)
- 方法
class Person:def __init__(self, name, age):self.age = ageself.name = namedef print_info:print(self.name, self.age)
定义
#经典类
class Dog1:pass# 新式类
class Dog2(object):pass
在python3里面这都一样, 用于继承
self
self指向当前的这一个对象自己, python会自动传递当前的对象
self的名字可以不叫self, 但是推荐使用self, 会使用类的方法的第一个参数
属性
class Dog1:def set_name(self, name):self.name = namedef print_name(self):print(self.name)class Dog2:passdog1 = Dog1()
# dog1.set_name('dog1')
dog1.print_name()
这一个属性会在调用
dog1.set_name('dog1')
的时候才会创建, 不调用的时候使用这一个变量会出问题也可以使用
dog1.name = "dog1"
来进行指定, 但是一般不使用这一个方式
私有属性
普通的属性可以在外部进行修改, 这种方式一般是不希望出现的
可以在变量名前面加两个__
进行设置
class Dog1:def __init__(self, name):self.__name = namedef print_name(self):print(self.__name)dog = Dog1("dog1")
print(dog.__name)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" Traceback (most recent call last): File "e:\JHY\python\2024-4-22\main.py", line 9, in <module>print(dog.__name) AttributeError: 'Dog1' object has no attribute '__name'
dog = Dog1("dog1")
dog.__name = "dog2"
dog.print_name()
这样实际也不能修改这一个变量, 打印的还是
dog1
实际使用的时候可以通过_ClassName__name进行调用, 但是不建议使用
dog = Dog1("dog1")
dog._Dog1__name = "dog2"
dog.print_name()
python实际是对这一个变量进行了一个重命名
私有方法
外部不能直接调用的方法
在函数名的前面加一个__
继承
一个新的类里面有之前的一个类里面的数据, 这时候可以使用继承的方式
class NewClass(OldClass1, OldClass2, ...):pass
实际继承的时候可以有多个类
继承获取的方法可以重写进行覆盖(方法的名字需要一样)
class Parent(object):x = 1class son1(Parent):passclass son2(Parent):passprint(Parent.x, son1.x, son2.x)
son1.x = 2 # 这一个实际是一个定义
print(Parent.x, son1.x, son2.x)
Parent.x = 3
print(Parent.x, son1.x, son2.x)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" 1 1 1 1 2 1 3 2 3
在类里面的变量是使用字典的方式进行记录的, 在当前的类里面没有找到的话会从祖先的类里面找, 子类里面重写会创建一个新的变量在他的里面记录这一个新的值, 不会影响父类里面的值
super
有可能存在父类的方法不可以满足子类的需求, 但是不是需要全部重写, 而是加了一部分新的数据, 全部重写会出现代码的冗余
可以重写的时候调用一下父类的方法
class Chile(Father):def Father_func():super.Father_func()子类的新的处理
也可以使用
Father.Father_func()
, 但是使用这一个方法的时候会导致多继承的时候可能有的函数会被多次调用, 如果没有相同的祖先, 这一个就不会出现问题
class Father(object):def __init__(self, name):print("Father")self.name = nameclass Son1(Father):def __init__(self, name):print("Son1 Begin")super().__init__(name)print("Son1 End")class Son2(Father):def __init__(self, name):print("Son2 Begin")super().__init__(name)print("Son2 End")class GrandSon(Son1, Son2):def __init__(self, name):print("GrandSon")super().__init__(name)print("GrandSon End")grandson = GrandSon("Shi")
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" GrandSon Son1 Begin Son2 Begin Father Son2 End Son1 End GrandSon End
这一个调用的时候需要计算出来实际调用的是哪一个父类, python里面实际使用了一个C3算法
可以使用
print(GrandSon.__mro__)
查看实际调用的顺序, 每一次遇到一个super会进入下一层的函数里
(<class '__main__.GrandSon'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Father'>, <class 'object'>)
使用这一个的时候会出现一个问题: 多进程的时候传入的参数的个数的问题, 这一个的顺序是不一定的, 所以传递的时候需要把所有的参数都传递
多态
在python里面不是很明显
多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承)
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
实际使用的时候可以传递一系列类过去, 这些类里面有一个相同名字的函数, 但是这一个函数的实际执行的任务不同, 传不同的类会使用不同的行为, 但是实际调用的时候看起来调用的都一样
特殊属性方法
静态方法
在class里面有一个函数, 实际不需要class这一个参数, 这一个方法就是一个静态方法, 实际使用的时候需要使用参数@staticmethod
进行修饰
class Class(Object):@staticmethoddef func():pass
这一个静态方法可以直接使用类名进行调用
Class.func()
类属性
通过一个类创建的对象之间的数据是相互隔离的, 但是有的数据需要是共享的
在Class里面, 但是位于def外面的属性是一个类属性
class Class(object):类属性 = value
实际调用的时候需要使用
类名.类属性
来调用这一个属性
class Tools(object):num = 0def __init__(self):Tools.num += 1
类里面的属性实际存储的时候使用的是字典里面的键值对, 所以实际去调用一个参数的时候传给属性拦截器的是一个字符串
类方法
专门用于对类属性操作的方法, 使用@classmethod
, 这个时候传递的参数是cls
class Tools(object):num = 0@classmethoddef add_1(cls):cls.num += 1 def __init__(self):self.add1()
实际调用的时候可以使用
实例对象.类方法
或者类名.类方法
记录的时候实际也是一个属性, 指向一个代码段
可以直接使用实例或者类.名字 = 方法
这种方式进行动态添加一个一个方法, 但是这一种方式不会自动传入self参数, 可以使用types进行动态添加方法
- 实例方法
import typesclass Person (object):def __init__(self, name=None, age=None):self.name = nameself.age = agep = Person("jiao", 21)def show_info(self):print("----info----")p.show_info = types.MethodType(show_info, p) # 自动把这一个参数传进去
p.show_info()
- 类方法/静态方法
这两个可以直接在加了修饰器以后添加
类对象
在python里面定义class的时候这一个定义也是一个对象, 这一个对象里面有类属性等信息, 还有各种方法的代码
创建的实例里面有一个参数__class__
, 这一个参数指向这个类对象
实例调用方法的时候会使用这一个类对象, 用于节省空间, 避免代码重复
可以使用
dir(实例对象)
查看有哪一些方法, 属性dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
变量特殊命名方法
xx
: 共有变量
_xx
:私有化属性和方法, 使用from module import *
禁止导入, 类对象和子类可以使用
__xx
: 无法在外部直接访问, 私有属性
__xx__
用户名名空间的魔法对象和属性
xx_
: 用于避免和python关键字冲突
property动态数据
使用实例对象.属性 = xxx
的时候会自动调用一个方法, 一个属性使用的时候是动态生成的, 需要在使用的时候通过一个函数进行生成
在使用=进行赋值的时候也可以使用这一个函数进行数据的检查
装饰器使用
class Foo(object):def func(self):print("fun running...")@propertydef prop(self):print("prop running...")foo_boj = Foo()
foo_boj.func()
foo_boj.prop #这一个调用的时候没有()但是会执行这一个函数
这一个函数的返回值会作为最后的结果, 可以使用这一个进行产生动态的数据
新式类
class Foo(object):def __init__(self) -> None:self.aaa = 1@propertydef prop(self):print("prop running...")@prop.setterdef prop(self, value):print("prop setting...")self._prop = value@prop.getterdef prop(self):print("prop getting...")return self._prop@prop.deleterdef prop(self):print("prop deleting...")del self._propfoo_boj = Foo()foo_boj.prop = 2
print(foo_boj.prop)
del foo_boj.prop
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" prop setting... prop getting... 2 prop deleting...
@prop.getter
没有定义的时候会使用@property
的返回值, 后面两个必须在@property
后面使用, 这一个属性是必须有的
另一种调用方式
class Foo(object):def __init__(self):self.bar = "jiao"def get_bar(self):return self.bardef set_bar(self, value):self.bar = valueBAR = property(get_bar, set_bar, None, "This is a bar")obj = Foo()
result = obj.BAR
print(result)
obj.BAR = "haoyang"
print(obj.BAR)
print(Foo.BAR.__doc__)
第四个参数是一个字符串, 使用类名.属性.__doc__
可以获取
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" jiao haoyang This is a bar
这一个使用
+=
进行赋值的时候相当于obj.BAR = obj.BAR + value
, 所以实际调用的时候会使用一次获取以及一次写入
- 实际使用实例
Django框架里面的property里面属性使用的就是这一个
不足
如果有多个属性需要设置的时候, 这里面需要实现很多的函数
内建函数集合
__init__
引入
创建的时候会调用的函数
class Person:def __init__(self, name, age):self.age = ageself.name = namedef print_info:print(self.name, self.age)
__str__
打印
这一个函数的返回值是使用print函数打印这一个类的时候结果
__call__
如果使用一个类创建了一个对象, 直接使用这一个对象当函数调用会执行这一个函数
__slots__
静态
添加这一个属性可以使得这一个类不能在运行的时候进行添加属性, 只可以使用声明了的属性
class Person(object):__slots__ = ("name", "age")p = Person()
p.name = "John"
p.age = 20
print(p.name, p.age)
p.address = "henan"
print(p.address)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" John 20 Traceback (most recent call last): File "e:\JHY\python\2024-4-22\main.py", line 8, in <module> p.address = "henan" AttributeError: 'Person' object has no attribute 'address'
这一个效果只在定义他的类里面有效, 不会进行继承
import mathclass Person:# 初始化def __init__(self, name, age):self.age = ageself.name = namedef print_info(self):print(self.name, self.age)# print直接打印的时候def __str__(self):return f'{self.name} is {self.age} years old'def __repr__(self):return f'Person({self.name}, {self.age})'def __eq__(self, other):return self.age == other.agedef __lt__(self, other):return self.age < other.agedef __gt__(self, other):return self.age > other.agedef __le__(self, other):return self.age <= other.agedef __ge__(self, other):return self.age >= other.agedef __ne__(self, other):return self.age != other.agedef __add__(self, other):return self.age + other.agedef __sub__(self, other):return self.age - other.agedef __mul__(self, other):return self.age * other.agedef __truediv__(self, other):return self.age / other.agedef __floordiv__(self, other):return self.age // other.agedef __mod__(self, other):return self.age % other.agedef __pow__(self, other):return self.age ** other.agedef __and__(self, other):return self.age & other.agedef __or__(self, other): return self.age | other.agedef __xor__(self, other):return self.age ^ other.agedef __lshift__(self, other):return self.age << other.agedef __rshift__(self, other):return self.age >> other.agedef __neg__(self):return -self.agedef __pos__(self):return +self.agedef __abs__(self):return abs(self.age)def __invert__(self):return ~self.agedef __round__(self, n=0):return round(self.age, n)def __floor__(self):return math.floor(self.age)def __ceil__(self):return math.ceil(self.age)def __trunc__(self):return math.trunc(self.age)def __index__(self):return self.agedef __len__(self):return len(self.name)def __contains__(self, item):return item in self.namedef __getitem__(self, key):return self.name[key]def __setitem__(self, key, value):self.name[key] = valuedef __delitem__(self, key):del self.name[key]def __iter__(self):return iter(self.name)def __reversed__(self):return reversed(self.name)def __next__(self):return next(self.name)def __hash__(self):return hash(self.name)def __call__(self):return self.print_info()def __enter__(self):print('Entering')def __exit__(self, exc_type, exc_value, traceback):print('Exiting')def __del__(self):print('Deleting')p1 = Person('John', 30)
p2 = Person('Jane', 25)p1.print_info()print(p1 == p2)
print(p1 < p2)
print(p1 > p2)
print(p1 <= p2)
print(p1 >= p2)
print(p1 != p2)
print(p1 + p2)
print(p1 - p2)
print(p1 * p2)
print(p1 / p2)
print(p1 // p2)
print(p1 % p2)
print(p1 ** p2)
print(p1 & p2)
print(p1 | p2)
print(p1 ^ p2)
print(p1 << p2)
print(p1 >> p2)
print(-p1)
print(+p1)
print(abs(p1))
print(~p1)
print(round(p1, 1))
print(math.floor(p1))
print(math.ceil(p1))
print(math.trunc(p1))
print(hash(p1))
p1()
with p1:pass
del p1
John 30
False
False
True
False
True
True
55
5
750
1.2
1
5
8472886094430000000000000000000000000
24
31
7
1006632960
0
-30
30
30
-31
30
30
30
30
2252686827583093840
John 30
Entering
Exiting
Deleting
Deleting
常用的属性 | 说明 | 触发 |
---|---|---|
__init__ | 构建初始化的时候 | 初见实际赋值的时候调用, 初始化new创建的实例 |
__new__ | 生成的实例时候 | 创建实际时, 最先调用, 返回实例 |
__class__ | 实例所在的类 | 实例.__class__ |
__str__ | 实例的字符串显示, 可读性 | print打印的时候显示 |
__repr__ | 实例的字符串显示, 准确性 | print(repr(实例类))或print直接调用, 优先级比较高 |
__del__ | 析构(删除的时候执行) | del实例 |
__dict__ | 实例的自定义属性 | vars(实例.__dict__) , 类.__dict__ 的时候只打印实例属性 |
__doc__ | 类文档, 子类不会继承 | help(类或实例), 函数会打印他的描述 |
__getattribute__ | 属性访问拦截器 | 访问实例属性的时候, 传入的参数是这一个参数的字符串 |
__bases__ | 类的所有的父类构成的元素 | print(类名.__bases__) |
常用属性
__module__
: 查看他所在的模块(文件)__class__
: 这一个实例使用的类
类字典实现
__getitem__, __setitem__, __delitem__
: 使用实例[索引]
和的时候会调用这三个
class Foo(object):def __getitem__(self, key):print("__getitem__", key)def __setitem__(self, key, value):print("__setitem__", key, value)def __delitem__(self, key):print("__delitem__", key)foo = Foo()
foo["bar"] = "baz"
del foo["bar"]
foo["bar"]
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" __setitem__ bar baz __delitem__ bar __getitem__ bar
传递的时候是一个字符串
__getslice__, __setslice__, __delslice__
: 在python2里面的实现切片
元类
一个特殊的类, 用于定义其他的类
元类(type)–>类(类对象)–>实例对象
类的创建也可以使用类似于闭包的方式
def choice_class(name):if name == "foo":class Foo(object):passreturn Fooelse:class Bar(object):passreturn Bar
使用type创建类
type实际有两个功能, 一个是测试类型, 另一个是创建一个类
type(类名, 父类的名称组成的元组(可以为空), 包含属性的字典(名称和值))
c_j = type("JIAO", (object,), {"name":"jiao", "age":18})
print(type(c_j))obj_j = c_j()
print(obj_j.name)
print(obj_j.age)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" <class 'type'> jiao 18
元类的定义
函数type实际上就是一个元类, 是python里面用于创建所有类的元类, Python里面的所有的类都是一个对象
JIAO = type("JIAO", (), {})
print(dir(JIAO))
print(JIAO.__dict__)
print(JIAO.__bases__) # 查看这一个对象继承的类
print(JIAO.__class__) # 查看它构建这一个实例使用的类
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'JIAO' objects>, '__weakref__': <attribute '__weakref__' of 'JIAO' objects>, '__doc__': None} (<class 'object'>,) <class 'type'>
str, int这一些数据实际使用也是一个type类生成的对象, type这一个类的父类是object
自己创建元类
class Foo(object, metaclass=xxxxx):
在定义一个类的时候没有指定这一个metaclass, 会默认使用type进行创建, 否则使用这一个参数指定的那一个函数进行创建
class UperAttrMetaClass(type):#def __new__(cls, clsname, bases, dct):new_attr = {}for name, value in dct.items():if not name.startswith('__'):new_attr[name.upper()] = valueelse:new_attr[name] = valuereturn type(clsname, bases, new_attr)class Foo(metaclass=UperAttrMetaClass):bar = 'bip'print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))f = Foo()
print(f.BAR)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" False True bip
创建这一个类里面的属性的时候都使用大写
class UperAttrMetaClass(type):# 这一个函数会在类被创建的时候被调用# clsname: 类的名字# bases: 类的基类# dct: 类的属性# 默认的实现是返回一个新的类# 这个新的类会将属性名转换为大写def __new__(cls, clsname, bases, dct):new_attr = {}for name, value in dct.items():if not name.startswith('__'):new_attr[name.upper()] = valueelse:new_attr[name] = valuereturn type(clsname, bases, new_attr)class Foo(metaclass=UperAttrMetaClass):bar = 'bip'print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))f = Foo()
print(f.BAR)
类定义的过程
python解释器遇到一个类的时候实际会进行一次调用, 为了知道实际有的属性以及方法, 之后把这些方法属性传递到元类type里面, 创建一个对象, 这个就是类对象
如果类里面有放在外面代码的话, 这些代码会执行
描述符对象
property的实现, @classmethod
等的实现都可以使用描述符
在大型的项目里面对MySQL数据库的操作的传媒也可以使用这一个进行实现
__getattr__
这一个是在调用一个不存在的属性的时候调用, 访问的属性存在的时候不会调用这一个方法
class A:def __init__(self, name):self.name = name# def __getattribute__(self, item):# if item == "name":# return "haha"# else:# return super().__getattribute__(item)def __getattr__(self, item):print("error item %s" % item)a = A("jiao")
print(a.age) # 默认访问这一个的时候会报错, 现在不会
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" error item age None
默认这一个会产生一个异常
__getattribute__
访问一个对象的时候会调用这一个函数, 实际使用的时候可以使用这一个函数进行拦截
class A:def __init__(self, name):self.name = namedef __getattribute__(self, item):if item == "name":return "haha"else:return super().__getattribute__(item)a = A("jiao")
print(a.name)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" haha
**注: **这两个同时出现的时候会调用
__getattribute__
描述符
之前使用的@property
这一个属性会使得一个函数的方法属于的类从function
变为property
这个property实际就是一个描述符, 严格来说, 如果一个类里面有__get__, __set__, __delete__
这三个属性里面的任意一个, 那么这一个类实际就是一个描述符对象
如果有一个类, 这一个类里面有类属性对应的是上面的描述符对象创建的实例对象, 这一个类属性就是一个描述符
class A:def __get__(self, instance, owner):print("---get---")def __set__(self, instance, value):print("---set---")def __delete__(self, instance):print("---delete---")class B:a = A() # 这一个是一个描述符b = B()
print(b.a) # 调用__get__方法, b为instance, B是owner
b.a = 100 # 调用__set__方法, b为instance, 100是value
del b.a # 调用__delete__方法
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" ---get--- None ---set--- ---delete---
实际property就是这一个方式实现的
如果直接使用B.a进行调用的时候, instance这一个是None, owner还是B
使用描述符的原因
python里面没有属性, 所以可以使用这一个进行类型检测, 使用的时候比
实际使用
实现数据监测
class NonNegative(object):def __init__(self, default):self.default = default # 没有某一个参数的时候返回值self.data = dict()def __get__(self, instance, owner):return self.data.get(instance, self.default)def __set__(self, instance, value):if value < 0:raise ValueError('value must be non-negative')self.data[instance] = valueclass Order(object):price = NonNegative(0)quantity = NonNegative(1)def __init__(self, name, price, quantity):self.name = nameself.price = priceself.quantity = quantitydef total(self):return self.price * self.quantityorder = Order('apple', 1, 10)
print(order.total())
order.price = 10
print(order.total())
order.price = -10 # ValueError: value must be non-negative
实现classmethed
class classmethed_new(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):def new_func(*args, **kwargs):return self.func(owner, *args, **kwargs)return new_func # 这是一个闭包, 返回实际调用的函数class A(object):M = 100def a(self):print('A.a')@classmethed_newdef b(cls): # 这里实际为b = classmethed_new(b)print('A.b')print(cls.M)obj = A()
obj.a()
print('-'*20)
obj.b() # 从b获取一个函数, 之后执行这一个函数, 这时候的参数会给new_func, 之后new_func调用self.func
实现惰性计算
惰性计算就是需要一个值, 这一个值不是提前准备的, 是啥时候需要啥时候计算的
class LazyPropety(object):def __init__(self, fun) -> None:self.fun = fundef __get__(self, instance, owner):print("Calling __get__")if instance is None:return selfvalue = self.fun(instance)# setattr(instance, area, value) 把这一个函数的名字指向的对象改为这一个计算值setattr(instance, self.fun.__name__, value)return valueclass ReadOnlyNumber(object):def __init__(self, value) -> None:self.value = valuedef __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):raise AttributeError("Can't set attribute")class Circle(object):pi = ReadOnlyNumber(3.14)def __init__(self, radius):self.radius = radius# 初始的时候area这一个类保存的是一个描述符@LazyPropetydef area(self):print("Calculating area")return self.pi * self.radius * self.radiusprint("1.----")
a = Circle(4)
print("2.----")
print(a.area)
print("3.----")
print(a.area)
print("4.----")
__dict__
作用
使用这一个可是查看一个类里面的已经定义的属性
在python里面所有的属性以及方式使用的都是字典的方式进行存储的, 实力对象里面只有这一个对象的私有属性, 方法以及类的文档在类属性里面
这一个实际调用print(对象.__dict__)
, 也可以使用vars(对象)
这一个函数进行替代
数据在调用的时候找到的是一个普通的属性的时候会直接调用, 如果这一个属性是一个描述符则会调用他的__get__
方法
在获取数据的时候会首先从自己的字典里面获取数据, 没有这一个数据的话查找父类的字典
class M:def __init__(self) -> None:self.x = 1def __get__(self, instance, owner):print('get')return self.xdef __set__(self, instance, value):print('set')self.x = valueclass AA:m = M()n = 2def __init__(self, score) -> None:self.score = scorea = AA(3)
print(a.m)
print("-" * 20)
print(type(a).__dict__["m"].__get__(a, AA)) # 这个和上面的那一个等价
数据描述符和非数据描述符
同时有__get__
和__set__
的时候这是一个数据描述符
只有一个__get__
的时候是一个非数据的描述符
这两个区别是:
属性名和描述符的名字一样的时候, 访问这一个属性的时候, 如果这一个描述符是数据描述符, 就会优先访问这一个描述符, 不是的话访问这一个属性
class M:def __init__(self) -> None:self.x = 1def __get__(self, instance, owner):return self.xdef __set__(self, instance, value):self.x = valueclass N:def __init__(self) -> None:self.x = 1def __get__(self, instance, owner):return self.xclass AA(object):m = M() # 资料描述符n = N() # 非资料描述符def __init__(self, m, n) -> None:self.m = m # 属性m和描述符m同名, 调用的时候会发生冲突# 数据描述符优先级高, 不会创建一个新的m, 会给m描述符赋值self.n = n # 属性n和描述符n同名, 这一个会创建一个新的属性aa = AA(2, 3)
print('-'*20)
print(aa.__dict__)
print(AA.__dict__)
print('-'*20)
print(aa.n) # 这俩是不一样的, 这个调用属性
print(AA.n) # 描述符
print('-'*20)
print(aa.m) # 这俩是一样的, 都是用的描述符
print(AA.m)
PS E:\JHY\python\2024-4-22> python -u "e:\JHY\python\2024-4-22\main.py" -------------------- {'n': 3} {'__module__': '__main__', 'm': <__main__.M object at 0x0000015CB8287FD0>, 'n': <__main__.N object at 0x0000015CB8287CD0>, '__init__': <function AA.__init__ at 0x0000015CB82AB0A0>, '__dict__': <attribute '__dict__' of 'AA' objects>, '__weakref__': <attribute '__weakref__' of 'AA' objects>, '__doc__': None} -------------------- 3 1 -------------------- 2 2
只读描述符
同时定义两个属性, 但是在调用__set__
的时候会触发一个AttributeError, 引发一个异常
实际调用的细节
使用默认的__getattribute__
的时候访问一个属性的时候会使用hasattr
进行判断是不是有一个属性__get__
如果有的话会使用这一个函数的返回值
如果重新这一个函数会使得描述符失效
没有这一个属性的时候调用__getattr__
注意
- 使用描述符的时候这一个必须是一个类属性, 不能是实例属性, 否则的话这一个属性调用的时候不会调用这一个属性的方法
- 由于使用的是类属性, 所以这一个描述符在实现的时候需要区分不同的实例, 这个时候可以使用instance做一个字典的key