python中一切皆对象
python是基于协议来进行编程的
python中一切都是对象,包括class也是对象,函数也是对象
函数作为返回值,实际上是Python装饰器的原理
type,object和class的关系
解释了python当中一切皆对象是如何做到的
object是最顶层的基类
type是一个类,也是一个对象
以list来举例
list除了是class以外,还是一个type的对象,可以动态的修改list这个类
在java和C++当中,类一旦创建,就会加载到内存当中,实际上是不能修改的
并且实现了一切都继承了object
type实现自己实例化自己的原因可能是,类似于指针,只是内存块而已
python中常见的内置类型
python代码本身也会被python解释器变成一个对象类型
由于set和dict的实现原理大体一致,所以他们的效率会比较高,所以在进行数据处理,判断是否为in 时可以使用set来进行处理
elliosis:省略号类型
对象的三个特征:身份,类型,值
可以利用id()来查看身份
魔法函数
class中以“_ _”开头的函数
魔法函数不可进行自定义
异常之后就将for循环结束
定义了__getitem__之后,实例化的对象就是可以迭代的对象
for循环实际上是需要拿到一个迭代器,但实际上我们定义的类中不存在迭代器,那么python 会对代码进行优化,没有迭代器就去找__getitem__魔法函数,并在遍历后出现越界异常后,自行停止处理异常(def __iter__(self)这个方法才会有迭代器)
python的数据模型以及数据模型对python的影响
魔法函数实际上就是python数据模型的一个概念,并且魔法函数是一种网上的叫法
该案例下魔法函数的调用实际上是隐式的,即不需要直接去显示调用__getitem__这个方法
__getitem__这个方法实际上会将类的实例化对象变成一个列表对象,可以对其进行切片操作
看起来比较神奇并方便了我们的操作,魔法函数会直接影响到python语法本身
subscriptable下标可访问的
内置len()函数会 尝试先去调用__len__魔法函数,然后再尝试调用__getitiem__魔法函数
在print(实例化对象)时,python解释器会隐式调用str(实例化对象),即print(str(实例化对象))
二元魔法函数举例
class Akebi:
def __init__(self,x,y) -> None:
self.x = x
self.y = y
def __add__(self,other_class):
self.re_vector = Akebi(self.x+other_class.x,self.y+other_class.y)
return self.re_vector
def __str__(self):
return f"x:{self.x},y:{self.y}"#注意farmat的写法
akebi1 = Akebi(1,2)
akebi2 = Akebi(2,3)
print(akebi1+akebi2)
len函数的特殊性
在使用len()时,会隐式调用__len__魔法函数
如果对python的内置类型使用len(),实际上对于list,set,dict等原生的数据结构都是利用C语言来实现 的,所以其性能是比较高的。在对内置类型进行计算时,会直接读取C语言当中的数据,比如list 会在实现的过程中,在内部维护一个“长度”数据,使用len(),直接读取数据,而不会进行遍历。
所以在使用python 时,尽量使用原生的数据类型,这样代码的性能比较高
鸭子类型和多态
多态是指同一种操作作用于不同的对象,可以有不同的解释和实现。它可以通过接口或继承实现,可以提高代码的灵活性和可读性。
静态语言和动态语言的区别就是,动态的变量可以指向任何一个类型,而静态语言在定义变量时,需要写明类型,如 int a = 10
动态语言的缺陷,无法做类型检查,无法在代码编译时发现错误,只有在运行时才会知道错误
class Akebi:
def say(self):
print("I am Akebi")
class Komichi:
def say(self):
print("I am Komichi")
class Hobert:
def say(self):
print("I am Hobert")
name_list = [Akebi,Komichi,Hobert]
for i in name_list:
i().say()#实例化对象
#在多个类中定义同一个方法,可以实现多态,即鸭子类型
a = ["AKebi","Komichi"]
b = ["Hobert","Akebi"]
c = ("abc","def")
d = set()
d.add("hig")
a.extend(c)#iterable可迭代的,所以这里可以传任何可以迭代的对象,extend()会隐式调用迭代器
#任何可以迭代的对象,比如可以自行实现一个类(可迭代的,
#可以通过实现__iter__或__getitem__魔法函数来实现类的可迭代)进行传参,
#而这就是鸭子类型的体现
print(a)

一个类有什么特性或属于什么类型,是看里面到底实现了什么魔法函数(这种特性也是一种协议)
不需要去继承指定的类
抽象基类
1.在基类中设定一些方法,让所有的类来继承基类,并覆盖基类的方法
2.不能实例化

isinstance(a1,sized)是python 内置的函数,可以去判断某一个对象是否为指定类型
可以找到继承链,从而判断类型
抽象基类的使用场景
1.在某些情况下,需要判断某个对象的的类型,可以使用抽象基类
2.需要强制某些子类必须实现某些方法
实现了一个抽象基类,指定子类必须实现某些方法
如在实现web框架时,由于不知道后期cache(redis,cache,memorychache)


实际上python内置的抽象类型
为了加深我们对这些接口里面的必须的函数的一些理解
__all__写成这样。能够让我们以一种类似于文档的体验去搞懂
抽象基类的好处
1.作为isinstance的参数
2.用于做一些接口的强制规定
class CacaheBase():#定义抽象基类
def get(self):#定义一些方法,让子类在继承时,必须要进行复写覆盖,不然就会报错
raise NotImplementedError#python内置的异常
def set(self):
raise NotImplementedError#默认让其抛出一个异常
class Cache(CacaheBase):
pass
cache = Cache()
cache.get()
#也可以在不使用抽象基类情况下,对其进行模拟
但抽象基类容易设计过度,不建议使用,如果要继承网线接口的话,比较推荐用mixin,这种多继承的方式去实现,更推荐python的鸭子类型
isintance和 type的区别
"is "和"=="的区别
"is "判断前后的两个是不是一个对象,即判断id是否相同

类变量和对象变量(实例变量)
在实例上使用a.aa = 100时,类属性保持不变,而会新建一个aa属性放在实例化对象的属性值中
class Akebi:
aa = 1#属于类变量
def __init__(self,x,y):#构造函数第一个传递进来的是类的一个实例
self.x = x #属于对象变量,即实例化对象的变量
self.y = y#x和y 已经绑定到一个具体的实例上了
a = Akebi(2,3)
print(a.x,a.y,a.aa)#a作为实例化对象,可以向上查找,先找构造函数,然后再向上找变量
print(Akebi.aa)
print(Akebi.x)#类无法进行向下查找,
类属性和实例属性以及查找顺序
属性是定义在类内部的一些变量或方法
实例对象在查找属性的时候,实际上是由下而上的查找顺序

在这种继承结构之下,应该将B和D,C和E各自看成一个整体,先将一条路查到黑

在python2之后,就改为了广度优先的算法

这样可以实现C中的属性可以覆盖D中的属性,即C的属性比D的先查找到
MRO:方法查询的顺序
总结上面的的缺陷,
最终python3 在方法查询方面,使用的是C3算法
python2.2之前存在经典类,即如果不写object,则不会继承object的一种类
而在python3中经典类已经不存在了,变成了新式类(写不写都继承object)
以上的解析,是为了在多继承的情况下,写代码时,出现重名,出现bug,可以根据这个理论基础来进行排错
属性的查找顺序会放在__mro__属性中
静态方法,类方法以及对象方法(实例方法)以及参数
class Date:
def __init__(self,year,mouth,day) -> None:
self.year = year
self.mouth = mouth
self.day = day
@staticmethod #静态方法不需要加self
def phase_str(date_str):
year , mouth , day = tuple(date_str.split("-"))
return Date(int(year),int(mouth),int(day))#这里的return采用的是硬编码的方式,缺陷是如果类名变了,这里也要变
@classmethod#类方法,第一个参数是类,cls是习惯性用法,可以写成其它的形式
def from_str(cls,date_str):
year , mouth , day = tuple(date_str.split("-"))
return cls(int(year),int(mouth),int(day))#直接将类名传递进来,规避了静态方法的缺陷
#但当不需要返回类对象,只对传入的数值进行检查时,staticmethod就有用了
@staticmethod
def check_date(date_str):
year , mouth , day = tuple(date_str.split("-"))
if int(year) > 0 and int(day)<=31:
return True
else:
return False
def tomarrow(self):
self.day += 1#这种只能修改实例化对象的变量,修改类变量可以是Date.变量名
def __str__(self):
return f"{self.year}/{self.mouth}/{self.day}"
if __name__ == "__main__":
date = Date(2024,5,2)
date.tomarrow()#python会将该行代码转换为tomarrow(date)
print(date)
date_str = "2023-6-9"
year , mouth , day = tuple(date_str.split("-"))#tuple可以实现拆包,直接将多个值赋值给多个变量
print(year,mouth,day)
date = Date(int(year),int(mouth),int(day))
print(date)
#使用staticmethod来完成初始化,可以让代码变得更加精简优雅
date1 = Date.phase_str(date_str)
print(date1)
#使用classmethod来完成初始化,可以让代码变得更加精简优雅
date2 = Date.from_str(date_str)
print(date2)
#使用静态方法对传入的参数进行判断
print(Date.check_date("2018-2-35"))
要完成类方法和静态方法必须要加上装饰器
数据封装和私有属性
对于静态语言中的private,python可以通过"_ _"双下划线来定私有属性
class Date:
def __init__(self,year,mouth,day):
self.year = year
self.mouth = mouth
self.day = day
@staticmethod #静态方法不需要加self
def phase_str(date_str):
year , mouth , day = tuple(date_str.split("-"))
return Date(int(year),int(mouth),int(day))#这里的return采用的是硬编码的方式,缺陷是如果类名变了,这里也要变
@classmethod#类方法,第一个参数是类,cls是习惯性用法,可以写成其它的形式
def from_str(cls,date_str):
year , mouth , day = tuple(date_str.split("-"))
return cls(int(year),int(mouth),int(day))#直接将类名传递进来,规避了静态方法的缺陷
#但当不需要返回类对象,只对传入的数值进行检查时,staticmethod就有用了
@staticmethod
def check_date(date_str):
year , mouth , day = tuple(date_str.split("-"))
if int(year) > 0 and int(day)<=31:
return True
else:
return False
def tomorrow(self):
self.day += 1#这种只能修改实例化对象的变量,修改类变量可以是Date.变量名
return self
def __str__(self):
return f"{self.year}/{self.mouth}/{self.day}"
class User:
def __init__(self,birthday) -> None:
self.__birthday = birthday
def get_age(self):#caonima “self”写成“slef”居然不报错,找了半天
return (2024 - (self.__birthday.year))
if __name__ == "__main__":
user = User(Date.from_str("2004-7-1"))
# print(user.birthday)#python定义了私有属性后,无法通过.来访问私有属性
#然而其只是对其进行了改名,将__birthday变成了_User__birthday
print(user._User__birthday)#依旧可以打印输出
print(user.get_age())
def get_age(self):#caonima “self”写成“slef”居然不报错,找了半天
return (2024 - (self.__birthday.year))
对象的自省机制
自省是通过一定的机制查询到对象的内部结构
__dict__
class Person:
"""
AKEBI
"""
name = "Akebi"#即使被子类继承,但实际上仍属于Person这个类
class Hobert(Person):
def __init__(self,name):
self.name1 = name
if __name__ == "__main__":
hobert = Hobert("Komichi")
hobert.__dict__["age"] = 20#可以直接赋值存储
print(hobert.age)#动态取出
print(hobert.__dict__)
print(hobert.name)
#name并不属于子类,只是在进行查询的时候根据mro向上查找
print(Person.__dict__)
dir()
if __name__ == "__main__":
a = [1,2,3,4,5]
print(dir(a))

super函数
函数的应用
class A:
def __init__(self) -> None:
print("A")
class B(A):
def __init__(self) -> None:
print("B")
super().__init__()
#super函数在多线程当中的使用
from threading import Thread
class MyThread(Thread):
def __init__(self,name,user):
self.user = user
super().__init__(name = name)
#直接进行关键字传参,不需要去关心内部的结构
if __name__ == "__main__":
b = B()
函数的调用顺序
class A:
def __init__(self) -> None:
print("A")
class B(A):
def __init__(self) -> None:
print("B")
super().__init__()
class C(A):
def __init__(self) -> None:
print("C")
super().__init__()
class D(B,C):
def __init__(self) -> None:
print("D")
super().__init__()
if __name__ == "__main__":
d = D()
print(D.__mro__)
mixin继承案例-djangon rest framework
多继承会使得python中的关系变得混乱,所以不推荐使用多继承
那么就用mixin来实现多继承
mixin模式的特点
1.Mixin类功能单一
2.不和基类关联,可以和任意基类组合,基类可以不和基类关联就可以初始化成功
3.在mixin中不要使用super这种用法(如果一个类继承了基类以及mixin,那么在使用super时,会以__mro__算法的顺序进行查询,这样会产生关联,违背了第2点)

mixin的类尽量以-Mixin来结尾,方便阅读和迭代
with语句-上下文管理器
try-except-finally和return的用法
with
简化try-except-finally的用法
上下文管理器协议
只需要将某些魔法函数定义在类中,这个类实际上就满足python中的协议
class Simple:
def __enter__(self):
print("enter")
#获取资源
return self#这里必须要返回一个实例化对象,用于后续的调用
def __exit__(self,exc_type,exc_val,exc_tb):#这里的4个参数都要传递
print("exit")
#释放资源
def my_fuc(self):
print("my_fuc")
with Simple() as simple:
simple.my_fuc()
python解释器通过with语句对上下文管理器进行支持
contextlib简化上下文管理器
import contextlib
@contextlib.contextmanager
def my_file(file_name):
print("open")#获取资源
yield {}#将函数转换为生成器
print("close")#释放资源
with my_file("Akebi.txt") as file_open:
print("code")#对文件进行操作
自定义序列类
序列类型的分类
第一个维度:根据存储数据的类型来区分,分为容器序列(可以放任意类型的数据)和扁平序列
第二个维度:根据序列是否可变来区分
序列的abc继承关系
但如果只定义了getitem的这个方法,是实际上也可以进行if...in...的判断

多个的魔法函数组成类就构成了序列协议
序列的+,+=和extend的区别


实现可切片的对象
[start:end:step]:当step为负整数时,表示为反向切片,这时的start应比end的值要大
切片操作会返回一个新的列表
实现__gettiem__可以让使用者对其操作像序列一样

[start:end:step]在使用切片语法是,python解释器会自动产生一个slice对象传入item

import numbers
class Group:
def __init__(self,company_name,user_name,staffs):
self.company_name = company_name
self.user_name = user_name
self.staffs = staffs
def __getitem__(self,item):#以下实现形式可以将切片和下标索引后,仍然返回group
cls = type(self)
if isinstance(item,slice):
return cls(self.company_name,self.user_name,self.staffs[item])
elif isinstance(item,numbers.Integral):
return cls(self.company_name,self.user_name,[self.staffs[item]])#外面再加[]目的是将下标取得的单个元素转换为列表
def __len__(self):
return len(self.staffs)
def __iter__(self):#转换为可迭代的对象
return iter(self.staffs)
def __contains__(self,item):#可以使用if...in...
if item in self.staffs:
return True
else:
return False
def __reversed__(self):
self.staffs.reverse()#直接利用列表对象的方法来实现元素反转
if __name__ == "__main__":
group = Group("Akebi","Komichi",["江利花酱","明日小路","苗代同学"])
print(group[1:2].__dict__)#切片操作是用冒号作为分隔
print(group[0].__dict__)
print(len(group))
if "江利花酱" in group:
print("好耶")
for index in group:
print(index)
reversed(group)
print(group.__dict__)
pass
bisect维护已排序的序列
维护可修改的序列
import bisect
#bisect用于处理已排序的序列,升序
#二分查找
my_list = []
bisect.insort(my_list,3)
bisect.insort(my_list,1)
bisect.insort(my_list,4)
bisect.insort(my_list,2)
print(my_list)#得到的是一个排好序的序列
#biscet = biscet_right
#得到插入数据的位置信息
print(bisect.bisect(my_list,3))
print(bisect.bisect_left(my_list,3))
什么时候不应该去使用列表
import array
#array只能存放指定的数据类型
my_array = array.array("i")
my_array.append(1)
my_array.append("akebi")
列表推导式,生成器表达式,字典推导式
灵活性高,更加可控
#简单逻辑
my_list = [i for i in range(21) if i%2 == 1]
print(my_list)
#复杂逻辑情况
def squet(item):
return item * item
my_list = [squet(i) for i in range(21) if i%2 == 1]
print(my_list)
#列表生成式性能高于列表操作
#将中括号变成小括号时,就会变成生成器
my_gen = (i for i in range(21) if i%2 == 1)
print(my_gen)
print(type(my_gen))
for i in my_gen:#可以进行遍历来打印生成
print(i,end=" ")#遍历迭代后,生成器内部为空
#生成器可以直接转换成list
my_gen = (i for i in range(21) if i%2 == 1)
my_list = list(my_gen)
print(type(my_list))
print(my_list)
#字典推导式
my_dict = {"akebi":14,"Komichi":14,"hobert":20}
my_dict_reversed = {value:key for key,value in my_dict.items()}#推导式中前后的key和value的书写要一致
print(my_dict_reversed)#如果key相同,会保留后出现的
#集合推导式
my_set = {key for key ,value in my_dict.items()}
#my_set = set(my_dict.keys())#keys()返回可迭代的对象,set()接收一个可迭代的对象的值
print(my_set)
print(type(my_set))
深入python的set和dict
from collections.abc import Mapping,MutableMapping
#dict属于Mapping类型
a = {}
print(isinstance(a,MutableMapping))
#dict只是实现了MutableMapping中的一些魔法函数
dict常用方法
a = {"akebi":{"age":14},
"Komichi":{"age":14}}
print(a)
#clear()
# a.clear()
# print(a)
#copy()浅拷贝
new_dict = a.copy()
new_dict["akebi"]["age"] = 16#修改嵌套的元素会影响被拷贝的数据
print(new_dict)
print(a)
#formkeys
#将可迭代的对象转换成dict
new_list = ["akebi1","akebi2"]
new_dict = dict.fromkeys(new_list,{"Komichi":14})#(序列,默认值)
print(new_dict)
#get()
#防止keyerr问题
value1 = new_dict.get("Hobert",{})#key值不存在就返回默认值,但并不会将key设置进dict
print(value1)
value2 = new_dict.get("akebi1",{})#key存在就返回默认的value
print(value2)
#items()
for key,value in new_dict.items():#也使用到了元组拆包的方法
print(key,value)
#setdefault
#key值不存在就返回默认值,但会将key设置进dict
new_default = new_dict.setdefault("Hobert",20)
print(new_default)
print(new_dict)
#update()
new_dict.update({"江利花酱":14})
print(new_dict)
new_dict.update(苗代同学=15,name="莹酱")#关键字传参的形式
print(new_dict)
new_dict.update([("company","CW")])#使用可迭代的对象即可
print(new_dict)
new_dict.update((("company","CW"),("明日小路","seki")))#使用可迭代的对象即可
print(new_dict)
dict的子类
my_dict = dict(name = "akebi")
print(my_dict)
class Mydict(dict):
def __setitem__(self,key,value):
super().__setitem__(key,value*2)
#直接继承dict,可能会导致部分魔法函数覆盖失效
my_dict1 = Mydict(name="莹酱")#失效invalid
my_dict2 = Mydict()
my_dict2["name"] = "莹酱"#未失效
print(my_dict1)
print(my_dict2)
#为了解决上面的不确定性,可以继承UserDict
from collections import UserDict
class My_dict(UserDict):
def __setitem__(self,key,value):
super().__setitem__(key,value*2)#super()记得要带括号
my_dict = My_dict(name="莹酱")
print(my_dict)
#defaultdict
from collections import defaultdict
my_dict = defaultdict(dict)#这里的默认值不能将dict实例化,可以放python中任意的数据类型
value3 = my_dict["test"]
print(value3)
print(my_dict)

my_dict = dict(name = "akebi")
print(my_dict)
class Mydict(dict):
def __setitem__(self,key,value):
super().__setitem__(key,value*2)
#直接继承dict,可能会导致部分魔法函数覆盖失效
my_dict1 = Mydict(name="莹酱")#失效invalid
my_dict2 = Mydict()
my_dict2["name"] = "莹酱"#未失效
print(my_dict1)
print(my_dict2)
#为了解决上面的不确定性,可以继承UserDict
from collections import UserDict
class My_dict(UserDict):
def __setitem__(self,key,value):
super().__setitem__(key,value*2)#super()记得要带括号
my_dict = My_dict(name="莹酱")
print(my_dict)
#defaultdict
from collections import defaultdict#重写了missing方法
my_dict = defaultdict(dict)#这里的默认值不能将dict实例化,可以放python中任意的数据类型
value3 = my_dict["test"]#当找不到key的时候就会调用__missing__方法,将键值设置进去
print(value3)
print(my_dict)
set和frozenset(不可变集合)
集合是无序且不重复的
my_set = set("akebi")#set接收可迭代的对象
print(my_set)
#frozenset不可变,可以作为dict的key
#update
#add
#| & -
#-,求差集对应set中的魔法函数__isub__
#可以自行去看一下其中与数学运算相关的魔法函数
a = set("abc")
b = set("cdef")
re_set1 = a.difference(b)#返回存在于a中,但不存在于b中的数据
re_set2 = a - b
re_set3 = a & b
re_set4 = a | b
print(re_set1)
print(re_set2)
print(re_set3)
print(re_set4)
if "c" in re_set4:
print("akebi")
#issubset判断一个集合是否是另一个集合的一部分
print(a.issubset(re_set4))
实际上,set的性能是非常高的
和dict的实现原理是一样的,都是哈希

dict和set的实现原理
dict的查找性能远远大于list
哈希表,也叫散列表

计算好后就放在对应偏移量的数组里面
所以key值必须是可hash的
hash冲突的解决
在发生冲突时,可以直接往下查找空位进行存储,但这样可能会导致数据存储时聚集在一块,导致在查找时效率降低
如何在声明hash数组的时候,尽可能地避免冲突
在申请内存的时候,会初始化一个比较小的连续的空间(数组只有是一段连续的空间才能够有偏移量的这种说法),并且会保留大量的空白,当剩余空间小于1/3时(后续在插入数据的时候,hash冲突的概率非常大),会申请一个更大的连续空间,然后将数据拷贝过去,在拷贝时,采用数据搬迁的算法时,极有可能改变数据映射的位置(就是改变原有的数据的顺序)
则添加数据极有可能改变数据的顺序,所以尽量不要去期望dict的顺序性
所以可以直接计算所需要查找的数据的hash值,然后对应到偏移量来找到对应的值
数组相比于链表(同一个位置,连接起多个数据,查找数据时需要从头遍历到尾)的最大优势是,可以做到任何一个位置的直接存取

set占用的空间比dict要小
第七章:对象引用,可变性和垃圾回收
python中的变量是什么
= 和 is的区别
is:判断两个对象是否为同一个对象,其ID号是否相同
python内部的intern优化机制,对于一定范围内的小的整数或小段字符串,会建立全局唯一的对象,之后在使用对应整数或字符串时,就会指向已经定义好的小整数或字符串,不会再重新生成了,而对于“a == b”这种形式,其实际的作用是使用list中的__eq__魔法函数来判断是否相等
del语句和垃圾回收
当有一个变量指向一个值对象时,对象的内部计数器就会执行加1,此时已经加到2了,而del语句作用是将内部计数器的值减1。当减到0的时候,python就会回收 对象
一个经典的参数错误
当向函数传递list或dict类型的参数时,这个值是完全又可能被改变的
元类编程
1.property动态属性
from datetime import date , datetime
class User:
def __init__(self,name,birthday) -> None:
self.name = name
self.birthday = birthday
self._age = 0#加下划线,表示以一种属性描述符来进行访问,但单下划线只是一种代码上的规范
#计算属性
#是一种“取”属性
#将下面的agn()函数变成属性描述符
@property#可以将已经固定的属性,重新以函数的形式来进行动态修改
def age(self):
return datetime.now().year - self.birthday.year
#是一种“设置”属性
#对age这个字段进行设置
@age.setter
def age(self,value):
self._age = value
if __name__ == "__main__":
user = User("akebi",date(year=2004,month = 7,day = 1))
user.age = 30
print(user._age)
print(user.age)#将取函数的模式变成取属性的模式
2.__getattr__,__getattribute__魔法函数
__getattr__
__getattribute__
该函数是第一时间进入进行查询,不是在找不到的时候才进入
无论访问什么状态的属性,取到的值都是他
这样就把持了所有属性访问的入口了
一般不要取重写这个函数
3.属性描述符和属性查找过程
任何实现快捷地对属性的输入的数据类型进行判定和限制,可以使用@setter对属性进行自定义逻辑,但如果对多个属性(字段)都用该方法,就会增加代码的重复性
我们可以用属性描述符来复用代码
详细的属性查找过程
在之前的学习当中,给对象的属性直接赋值,实际上会记录在对象的__dict__当中 ,但有了data descriptor,其优先级最高,直接进行入set,value中,并不会进入user的实例中
可以根据下面的几张图片来看看使用两种不同的属性描述符的差异
以属性age为例,其他的属性同理
user.age才是真正的属性查找,会按找上面的详细的属性查找顺序进行查找
而user.__dict__["age"]实际是直接在user.__dict__中进行取值
import numbers
class IntField:#只要实现下面任何一个魔法函数就可以将该字段变为属性描述符
#数据属性描述符
def __get__(self,instance,owner):
return self.value
pass
def __set__(self,instance,value):
if not isinstance(value,numbers.Integral):#限制age的输入类型
raise ValueError("int value need")
if value < 0 :#对age数值限制为正数
raise ValueError("positive value need")
self.value = value
pass
def __delete__(self,instance):
pass
# class NonDataField:
# #非数据属性描述符
class User:
age = IntField()#"age"是属性描述符的对象
# class User():
# def __init__(self,name,email,birthday):
# self.name = name
# self.email = email
# self.birthday = birthday
# self._age = 0
# @property
# def age(self):
# return date.now().year - self.birthday.year
# @age.setter
# def age(self,value):
# self._age = value
if __name__ == "__main__":
user = User()
user.age = 30#对"age"进行赋值时,实际上会调用set方法
print(user.age)
print(getattr(user,'age'))#getattr是python中的全局函数
4.__new__和__init__的区别
class User:
def __new__(cls,*args,**kwargs):#可以在__new__里面加入生成类的逻辑
print("new")
return super().__new__(cls)
def __init__(self,name) -> None:
print("init")
#new是用来控制对象的生成过程,在对象生成之前
#init是用来完善对象的
#如果new方法不返回对象,则不会调用init函数
if __name__ == "__main__":
user = User(name = "akebi")
5.自定义元类
#动态实现类的创建
def my_class_create(args):
if args == "user":
class User:
def __str__(self) -> str:
return "user"
return User
elif args == "akebi":
class Akebi:
def __str__(self) -> str:
return "Akebi"
return Akebi
#动态添加方法
def say(self):
return "i am Komichi"
#动态添加基类
class BaseClass:
def answer(self):
return "i am baseclass"
#python中类的实例化过程
#如果定义类时,写入了metaclass参数,就会首先寻找metacLass,通过metacLass去创建user类
#如果定义类时,没有写入了metaclass参数,就会利用type去创建类对象,实例
#什么是元类,元类是创建类的类对象<-cLass(对象)<-type
class Metaclass(type):#在元类当中可以对class中的属性,函数等的定义进行检查,检查不通过就抛异常
def __new__(cls,*args,**kwargs):
return super().__new__(cls,*args,**kwargs)#super这里要加()
class User(metaclass = Metaclass):
def __init__(self,name):
self.name = name
def __str__(self) -> str:
return "i am user"
if __name__ == "__main__":
my_class = my_class_create("akebi")
my_project = my_class()
print(my_project)
#type实现动态类的创建
Komichi = type("Komichi",(BaseClass, ),{"name":"akebi","say" :say})#动态添加方法,属性和基类
my_project1 = Komichi()
print(my_project1.name)
print(my_project1.say())
print(my_project1.answer())
#利用元类实现动态类的创建
user = User(name = "komichi")
print(user)

如果最后实在找不到,那么就会使用type来进行创建