python高级编程

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.不能实例化

has attribute(拥有某种属性)

isinstance(a1,sized)是python 内置的函数,可以去判断某一个对象是否为指定类型

可以找到继承链,从而判断类型

抽象基类的使用场景

1.在某些情况下,需要判断某个对象的的类型,可以使用抽象基类

2.需要强制某些子类必须实现某些方法

实现了一个抽象基类,指定子类必须实现某些方法

如在实现web框架时,由于不知道后期cache(redis,cache,memorychache)

强制用户必须实现这些方法
​​​

一种利用abc来实现抽象基类的方法

实际上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是否相同

尽量使用isinstance去判断类型,type会有比较多的误差
​​​​​

类变量和对象变量(实例变量)

在实例上使用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)#类无法进行向下查找,

类属性和实例属性以及查找顺序

属性是定义在类内部的一些变量或方法

实例对象在查找属性的时候,实际上是由下而上的查找顺序

DFS:深度优先的一种搜索,出现分支,先将一条路走完,再换路进行查询
在这种继承结构之下,应该将B和D,C和E各自看成一个整体,先将一条路查到黑

DFS算法对菱形的继承结构有缺陷
在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))
list中的魔法函数

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...的判断

Sequence抽象基类,需要满足所有的接口

多个的魔法函数组成类就构成了序列协议

序列的+,+=和extend的区别

相关+=实现魔法函数
相关+=实现魔法函数,这里只要求可迭代的类型

 实现可切片的对象

[start:end:step]:当step为负整数时,表示为反向切片,这时的start应比end的值要大

切片操作会返回一个新的列表

实现__gettiem__可以让使用者对其操作像序列一样

slice对象是专门用来做切片的[x:y]

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

在对下标进行索引时,item传入的是int类型的对象
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)

default_factory会设置默认值,然后赋值给对应的key

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的实现原理是一样的,都是哈希

实现set的if... in..操作

dict和set的实现原理

dict的查找性能远远大于list

哈希表,也叫散列表

将key利用哈希函数进行计算
计算好后就放在对应偏移量的数组里面
所以key值必须是可hash的

hash冲突的解决

在发生冲突时,可以直接往下查找空位进行存储,但这样可能会导致数据存储时聚集在一块,导致在查找时效率降低

 如何在声明hash数组的时候,尽可能地避免冲突

在申请内存的时候,会初始化一个比较小的连续的空间(数组只有是一段连续的空间才能够有偏移量的这种说法),并且会保留大量的空白,当剩余空间小于1/3时(后续在插入数据的时候,hash冲突的概率非常大),会申请一个更大的连续空间,然后将数据拷贝过去,在拷贝时,采用数据搬迁的算法时,极有可能改变数据映射的位置(就是改变原有的数据的顺序)

则添加数据极有可能改变数据的顺序,所以尽量不要去期望dict的顺序性

所以可以直接计算所需要查找的数据的hash值,然后对应到偏移量来找到对应的值

数组相比于链表(同一个位置,连接起多个数据,查找数据时需要从头遍历到尾)的最大优势是,可以做到任何一个位置的直接存取

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

如果继承的基类传入了metaclass参数,这是其中的实例化过程
如果最后实在找不到,那么就会使用type来进行创建

6.元类实现简单的orm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值