property动态属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#计算属性,将对象的函数调用以属性的形式调用 from datetime import date, datetime class User: def __init__(self, name, birthday): self.name = name self.birthday = birthday self._age = 0 @property #用于读取属性 def age(self): return datetime.now().year - self.birthday.year @age.setter #用于设置属性 def age(self, value): self._age = value user = User("Tom", date(2018, 1, 1)) print(user.age) user.age = 30 print(user._age) |
__getattr__、__getattribute__魔法函数
__getattr__、__getattribute__两个魔法函数是python解释器内部需要用的方法,其中,前者是在查找不到属性的时候调用,后者是对象查找自身属性必然要先进入的,是所有属性访问的入口,能不重写尽量不要覆盖,当然在写框架的时候,为了控制类的属性访问过程很可能会重写该函数。
1 2 3 4 5 6 7 8 9 10 11 |
from datetime import date, datetime class User: def __init__(self, name, birthday): self.name = name self.birthday = birthday def __getattr__(self): #可以在这里面写查找不到属性的处理逻辑 return "Not find attr" def __getattribute__(self): return "ABC" #如果有这种逻辑,则访问该对象的任何属性(无论有无)都会返回ABC。 user = User("Tom", date(2018, 1, 1)) print(user.age) |
属性描述符和属性查找过程
在很多场景下需要对属性的类型(整型、字符串类型等)做出规范和限制,典型的应用场景如djanog的ORM中,虽然如上文提到的property动态属性(@property+@xxx.setter)可以实现某个特定的属性做类型格式限制,但当字段属性很多的情况就不适用了,这时候就需要用到属性描述符,如下例所示做整数类型的验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import numbers class IntField: #在python中任意一个类中实现下面三个函数中的一个即为属性描述符 #数据描述符,实现了__get__()和__set__()两个魔法函数 def __get__(self, instance, owner): return self.value def __set__(self, instance, value): if not isinstance(value, number.Integral): raise ValueError("int value nedd") self.value = value def __delete__(self, instance): pass class NonDataIntField: #非数据属性描述符,仅实现了__get__()一个魔法函数 def __get__(self, instance, owner): return self.value class User: age = IntField() user = User() user.age = 1 user.__dict__["age"] = "abc" print(user.__dict__) #仅会查找user的__dict__ print(user.age) #会按照属性描述符的查找属性查找 |
属性查找过程如下:”
如果user是某个类的实例,那么user.age(以及等价的getattr(user,’age’))
首先调用getattribute__。如果类定义了_getattr_方法,那么在_getattribute_抛出 AttributeError 的时候就会调用到_getattr_,而对于描述符(_get_)的调用,则是发生在__getattribute内部的。
user = User(), 那么user.age 顺序如下:
(1)如果“age”是出现在User或其基类的__dict__中, 且age是data descriptor, 那么调用其__get__()方法, 否则
(2)如果“age”出现在user的__dict__中, 那么直接返回 obj.__dict__[‘age’], 否则
(3)如果“age”出现在User或其基类的__dict__中
(3.1)如果age是non-data descriptor,那么调用其__get__方法, 否则
(3.2)返回__dict__[‘age’]
(4)如果User有__getattr__()方法,调用__getattr__()方法,否则
(5)抛出AttributeError
__new__和__init__的区别
该部分是元类编程的核心知识内容,其中__new__()魔法函数在python新式类才有,即python 2.2之前不存在,在python高级编程如很多框架中用途很大,但日常开发过程中一般使用不到。__new__()和__init__()两个魔法函数的根本区别在于前者接收的第一个参数是类cls,可以用来自定义类的生成过程,后者接收的第一个参数是对象self,二者调用的顺序不一样,先__new__()生成类,然后__init__()生成对象。__new__()是用来控制对象的生成过程,在对象生成之前;__init__()是用来完善对象的;如果__new__()不返回对象,则不会调用__init__()。
1 2 3 4 5 6 7 8 |
class User: def __new__(cls, *args, **kwargs): print(" new method") return super().__new__(cls) #只有这样才能进入__init__() def __init__(self, name): print(" init method") user = User("Tom") #这里首先进入__new__()函数,实参“Tom”会以tuple的形式保存在args中,如果以“user=User(name="Tom")”的形式声明,则“Tom”会以dict的形式保存在kwargs中。而如果以后面这种形式调用,如果在User类的__init__()方法中没有接受name的形参,则会报错,即二者需要保持一致。 |
自定义元类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#类也是对象,type创建类的类,依照此可以有如下动态创建类的方法。 def create_class(name): if name == "user": class User: def __str__(self): return "user" elif name == "company": class Company: def __str__(self): return "company" return Company MyClass = create_class("user") my_obj = MyClass() print(type(my_obj)) |
上述方法不够灵活,需要创建大量的class语句,所以我们需要使用type。type可以用来动态创建类,提供了三种方法,可以参见type的__init__()方法,如下例展示其中一种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
User = type("User", (), {"name": "user"}) #分别为创建的类名,继承的父类(tuple对象),提供的属性字典 my_obj = User() print(my_obj.name) #这里还可以动态绑定函数到类中,如下 def say(self): return self.name User = type("User", (), {"name": "user", "say": say}) my_obj = User() print(my_obj.say()) #还可以动态继承基类,如下 class BaseClass: def answer(self): return "i am baseclass" User = type("User", (), {"name": "user", "say": say}) my_obj = User() print(my_obj.answer()) |
元类是创建类的类,如上所示type就是元类,理解起来就是,对象是有类创建的,类class在python中也是对象,是由type创建的。在日常开发中,我们很少使用type直接创建类,而是使用元类写法。元类能够控制继承它的类的实例化过程的原理在于:在默认情况下,python通过调用type来创建类对象,该类对象在全局中是唯一的。实际情况是,类实例化的过程中,会首先寻找metaclass,通过metaclass创建继承的类,如果在自身找不到metaclass,则会向上查找父类及模块中是否存在metaclass,如果都找不到才会去调用内置的type创建类对象,即metaclass的优先级很高,可以控制类生成的过程。
1 2 3 4 5 6 7 8 9 10 |
class MetaClass(type): #创建的元类(命名无限制)需要继承type,继承了type就是元类 def __new__(cls, *args, **kwargs): return super().__new__(cls, *args, **kwargs) #不同于上面,这里必须传递args和kwargs,否则报错,其保存的内容与使用type创建类的参数一致 class User(metaclass= MetaClass): #通过传递metaclass来传递元类,控制User实例化的过程 def __init__(self, name): self.name = name def __str__(self): return "user" my_obj = User(name= "Tom") print(my_obj) |
自定义元类实现ORM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
import numbers class Field: pass class IntField(Field): # 数据描述符 def __init__(self, db_column, min_value=None, max_value=None): self._value = None self.min_value = min_value self.max_value = max_value self.db_column = db_column if min_value is not None: if not isinstance(min_value, numbers.Integral): raise ValueError("min_value must be int") elif min_value < 0: raise ValueError("min_value must be positive int") if max_value is not None: if not isinstance(max_value, numbers.Integral): raise ValueError("max_value must be int") elif max_value < 0: raise ValueError("max_value must be positive int") if min_value is not None and max_value is not None: if min_value > max_value: raise ValueError("min_value must be smaller than max_value") def __get__(self, instance, owner): return self._value def __set__(self, instance, value): if not isinstance(value, numbers.Integral): raise ValueError("int value need") if value < self.min_value or value > self.max_value: raise ValueError("value must between min_value and max_value") self._value = value class CharField(Field): def __init__(self, db_column, max_length=None): self._value = None self.db_column = db_column if max_length is None: raise ValueError("you must spcify max_lenth for charfiled") self.max_length = max_length def __get__(self, instance, owner): return self._value def __set__(self, instance, value): if not isinstance(value, str): raise ValueError("string value need") if len(value) > self.max_length: raise ValueError("value len excess len of max_length") self._value = value class ModelMetaClass(type): def __new__(cls, name, bases, attrs, **kwargs): if name == "BaseModel": return super().__new__(cls, name, bases, attrs, **kwargs) fields = {} for key, value in attrs.items(): if isinstance(value, Field): fields[key] = value attrs_meta = attrs.get("Meta", None) _meta = {} db_table = name.lower() if attrs_meta is not None: table = getattr(attrs_meta, "db_table", None) if table is not None: db_table = table _meta["db_table"] = db_table attrs["_meta"] = _meta attrs["fields"] = fields del attrs["Meta"] return super().__new__(cls, name, bases, attrs, **kwargs) class BaseModel(metaclass=ModelMetaClass): def __init__(self, *args, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) return super().__init__() def save(self): fields = [] values = [] for key, value in self.fields.items(): db_column = value.db_column if db_column is None: db_column = key.lower() fields.append(db_column) value = getattr(self, key) values.append(str(value)) sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"], fields=",".join(fields), values=",".join(values)) pass class User(BaseModel): name = CharField(db_column="name", max_length=10) age = IntField(db_column="age", min_value=1, max_value=100) class Meta: db_table = "user" if __name__ == "__main__": user = User(name="Tom", age=28) # user.name = "Tom" # user.age = 28 user.save() |
转载请注明:北凉柿子 » Python高级编程和异步I/O并发编程笔记 7 元类编程