面向对象编程----Object Oriented Programming
,简称OOP
,是一种程序设计思想。OOP
把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
这个和Java的面向对象是一样的。
以下面打印学生成绩为例。
# 面向过程的思想
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
# 用dict存储学生成绩信息
std1 = {
'name': 'Michael', 'score': 98}
std2 = {
'name': 'Bob', 'score': 81}
# 调用函数打印成绩信息
print_score(std1)
print_score(std2)
# 面向对象的思想
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
std1 = Student("Michael", 98)
std2 = Student("Bob", 81)
std1.print_score()
std2.print_score()
面向过程的代码很好理解,就不再说明。
面向对象的代码,是首先抽象出一个类(class
)----Student
。然后这个学生类有两个属性name
和score
,有一个对象方法print_score
打印成绩。Student
就是抽象出来的类(class
),std1
和std2
就是学生类具体的对象即实例instance
。也可理解为类实例化。
对象方法和普通的方法并没有太大的区别,唯一的区别就是,对象方法的第一个参数必须是self
,但是调用对象方法的时候不用显示传递self
参数,python解释器会自动帮你传入实例对象。
__init__
对象方法相当于Java里的构造函数,用于创建对象的。
通过class
关键字定义一个类,class ClassName(object)
。其中类名需要首字母大写并采用驼峰式命名(编程习惯),括号里的object
是该ClassName
需要继承的类,如果找不到具体需要继承哪个类的话,就写object
,这个和Java继承Object类是一样的。
可以通过ClassName()
来创建类的实例,而不用像Java那样需要new
关键字。
如下图所示,可以看到对象sdt1
和sdt2
在内存中的地址都是不同的,但是都属于Student
。
有一点和Java区别很大的是,由于Python是动态语言,Python类的属性是可以动态绑定的。以Student
类为例,本来只有name
和score
两个属性,但是可以随便添加和修改及删除属性(delattr
和del
都可以删除属性,但建议用delattr
)。
# 动态添加age属性
std1.age = 26
print(std1.age) # 26
# 修改name属性
std1.name = "patrick"
std1.print_score() # patrick: 98
# 删除age属性
delattr(std1, "age")
# del std1.age
print(std1.age) # AttributeError: 'Student' object has no attribute 'age'
从前面Student
类的定义来看,我们还是可以在Student
类外部随意访问和修改Student
对象的属性。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,此时该属性变量就变成了私有变量(private
),外部无法访问。但是我们前面也说python无法完全限制变量的访问,前面加了__
的属性可以通过_Student__name
来访问(当然最好遵守编程习惯)。
class Student(object):
# 使name和score变成私有属性
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
std1 = Student("Michael", 98)
# 可以通过_Student__name来访问私有属性name
print(std1._Student__name) # Michael
# 无法访问私有属性__name
# print(std1.__name) # AttributeError: 'Student' object has no attribute '__name'
# 注意这个__name并不是Student类的那个__name,Student类的__name已经转变成_Student__name
# 现在这个是动态添加新的属性
std1.__name = "abc"
std1.print_score() # Michael: 98
# 通过Student__name来改变私有属性name
std1._Student__name = "abc"
std1.print_score() # abc: 98
有时候python的类里属性前面加一个_
,就表示该属性不能直接访问,要通过对象方法去访问,这是一种编程习惯。
python的继承和多态和Java里的说法是非常类似的。
如下所示,有Animal
类,自带一个对象方法run
。Monkey
继承了Animal
类没有重写对象方法run
,但是Dog
和Cat
继承Animal
类的同时重写了对象方法run
。类JustRun
只是有一个对象方法run
,但并没有继承Animal
类。
重写了对象方法run
的类如Dog
在调用run
方法时会打印dog is runnning...
,没有重写对象方法run
的类如Monkey
在调用run
方法时会调用父类的run
方法。
通过isinstance
方法判断对象d时发现它既是Dog
又是Animal
,这是和Java保持一致的地方。
class Animal(object):
def run(self):
print('Animal is running...')
class Monkey(Animal):
pass
class Dog(Animal):
def run(self):
print("dog is runnning...")
class Cat(Animal):
def run(self):
print("cat is runnning...")
class JustRun(object):
def run(self):
print("just run . not extends Animal")
a = Animal()
m = Monkey()
d = Dog()
c = Cat()
a.run() # Animal is running...
# Monkey继承了Animal, 所以继承了父类的run方法
m.run() # Animal is running...
# Dog继承了Animal但是重写了run方法,所以调用自己的run方法
d.run() # dog is runnning...
# Cat与Dog类似
c.run() # cat is runnning...
# dog对象d不仅是Dog,也是Animal,和Java是类似的
print(isinstance(d, Animal)) # True
print(isinstance(d, Dog)) # True
再看下面这个代码,方法run_once
的参数是animal
,本来参数类型应该是Animal
,但是依旧可以传入Dog
对象,因为继承关系嘛,这是和Java一致的地方。
但是同时也可传入JustRun
对象,因为JustRun
对象也有对象方法run
,这是和Java不一致的地方。
def run_once(animal):
animal.run()
# 这就是多态,以父类作为接口,调用子类的方法
# 对于一个变量,我们只需要知道它是Animal类型,运行时才决定是调用Animal还是其子类的run方法
# 这就是开闭原则。对修改关闭,对扩展开放
run_once(a) # Animal is running...
run_once(d) # dog is runnning...
# 由于Python是动态语言,传入的类型可以不必是Animal或其子类,只要传入的对象有run方法即可
run_once(JustRun()) # just run . not extends Animal
# 这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
# 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
拿到一个对象的引用后,我们可以通过type()
函数查看该对象是什么类型。
在模块types
里可以查看FunctionType
、BuiltinFunctionType
这些函数类型。
import types
print(type(123) == int) # True
print(type(12.34) == float) # True
print(type("hello") == str) # True
print(type(d) == Dog) # True
# 这是判断对象类型 和继承没关系 不等于isinstance
print(type(d) == Animal) # False
# 判读对象是什么函数类型
print(type(run_once) == types.FunctionType) # True
print(type(isinstance) == types.BuiltinFunctionType) # True
还可以使用上面用过的isinstance
函数判断一个对象是否属于某个类及其子类。
在Python基础–参数检查这里还可以通过isinstance
函数检查参数是否是整型或者浮点型
# dog对象d不仅是Dog,也是Animal,和Java是类似的
print(isinstance(d, Animal)) # True
print(isinstance(d, Dog)) # True
print(isinstance(12, (int, float))) # True
print(isinstance(12.34, (int, float))) # True
print(isinstance("hello", (int, float))) # False
使用dir()
函数获取对象的所有属性和方法。
如下图所示,dir(std1)
获取该对象所有的属性和方法,发现有上面提到的私有属性变换后的变量_Student__name
和_Student__score
。
类似__xx__
的属性后方法在python中都是由其特殊用途的。譬如获取长度的方法__len__
。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的
class Student(object):
# 使name和score变成私有属性
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
def __len__(self):
return 34
std1 = Student("patrick", 100)
# 下面两个获取对象长度的方法是等价的
print(std1.__len__()) # 34
print(len(std1)) # 34
hasattr
判断对象是否有某属性或方法
getattr
获取对象的某属性或方法
setattr
设置对象的某属性或方法
下面代码就展示了如何使用上面这三个函数。注意只有当不清楚一个对象是否含有该属性或者方法时,才会需要用到上面三个函数去判断和获取。
# 私有变量所有访问不到
print(hasattr(std1, "__name")) # False
# 私有变量换了名字而已 这样就能访问到
print(hasattr(std1, "_Student__name")) # True
# 没有age属性返回默认值 不给默认值会抛出异常AttributeError
print(getattr(std1,"age", None)) # None
std1.age = 26
print(getattr(std1,"age")) # 26
setattr(std1, "_Student__name", "faker")
print(getattr(std1,"_Student__name")) # faker
# 判断对象是否有对应的方法
print(hasattr(std1, "__len__")) # True
print(hasattr(std1, "print_score")) # True
# 获取对象的方法
f = getattr(std1, "print_score") # faker: 100
# 执行对象方法print_score
f()
从上面的学习中我们已经知道Python是动态语言,可以给类的实例任意添加属性,即实例属性。
但是如果我们要给类添加属性呢?这就是类属性。类属性是属于类的,但是类的所有实例都可以访问到。当然最好通过类去访问类属性如Student.count
。
类的属性名和实例的属性一定不要重复。因为,对象在访问属性时首先找实例属性,找不到再去找类属性。
如下面例子,给Student
类增加了一个count
的类属性。每增加一个学生,count
类属性就会加1。
此处暂不考虑多线程下的线程安全问题,带着问题继续学习。
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
Python作为动态语言,可以给实例对象绑定任何属性和方法,这是其非常灵活的地方。这在Java静态语言是很难实现的。
给实例对象添加属性的方法非常简单。
给实例对象添加方法需要使用到types.MethodType
给某个实例对象绑定对象方法,但是和添加属性一样,绑定的对象方法只能对某个具体实例对象有效,对其他的对象不有效。
如果想给所有实例对象绑定方法,那么可以给类class绑定方法,绑定后所有的实例对象均可以使用。当然这样最好在定义类的时候就写好对象方法,不建议动态绑定。
下面的代码就展示动态添加属性和方法的方法。
class Student(object):
pass
def set_age(self, age):
self.age = age
s1 = Student()
# 动态给对象添加属性
set_age(s1, 20)
print(s1.age) # 20
from types import MethodType
# 给实例对象绑定方法 仅对当前实例对象有效
s1.set_age = MethodType(set_age, s1)
s1.set_age(30)
print(s1.age) # 30
print(hasattr(s1, "set_age")) # True
s2 = Student()
# s2.set_age(10) # AttributeError
# s2对象就没有set_age方法
print(hasattr(s2, "set_age")) # False
Student.set_age = set_age
print(hasattr(s2, "set_age")) # True
s2.set_age(40)
print(s2.age) # 40
如果想限制实例的属性怎么办呢?可以使用关键字__slots__
来限定该类实例只能有哪些属性(其实方法也是属性的一种,所以也是可以通过该关键字来限制的)。
__slots__
是一个tuple类型。只有在这里注册了属性才能添加。
__slots__
仅对当前类的实例有效,对继承的子类无效,除非在子类一样定义__slots__
,即使是空的,这样子类的__slots__
就是父类的__slots__
加上自己的__slots__
class Student(object):
__slots__ = ("name", "age")
s1 = Student()
s1.name = "patrick"
# 给实例对象设置score属性就会抛出异常 因为score没有在__slots__里定义
# s1.score = 100 # AttributeError
class MinorStudent(Student):
__slots__ = ()
pass
ms1 = MinorStudent()
ms1.name = "patrick"
# ms1.score = 100 # AttributeError
class BigStudent(Student):
__slots__ = ("score")
pass
bs1 = BigStudent()
bs1.score = 100
我们以学生类为例,可以通过将属性前面加上_
表示该属性不要直接访问,然后提供getter和setter方法给外部访问。但是这样写又略显复杂,没有用直接用属性这么简单。
下面代码就是直接提供getter和setter方法。
class Student(object):
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, name):
self._name = name
s = Student("patrick")
s.set_name("faker")
print(s.get_name())
Python内置的@property
装饰器就是负责把一个方法变成像属性那样调用的。
如下面代码所示。
class Student(object):
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
s = Student("patrick",26)
s.name = "faker"
s.age = 20
print(s.name,s.age)
请注意,我把名字变量就命名为_name
来表示不应该直接访问,然后通过@property
装饰器将对象方法name
变成像属性一样访问,同时通过@name.setter
来提供修改方法name(self, name)
。
如果想让某个属性只读的话,就只需使用@property
装饰器即可,不用@xxx.setter
来设置可写。
Java是单继承多接口来扩展功能。Python支持多重继承来扩展类功能。
在设计类的继承关系时,通常,主线都是单一继承下来的。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计被称之为MixIn
。MixIn
的目的就是给一个类增加多个功能。
例如,Dog
类就继承三个类Mammal
、RunnableMixIn
、CarnivorousMixIn
。其中RunnableMixIn
和CarnivorousMixIn
就是“混入”的额外功能。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
__len__
返回实例对象的长度,可作用于len(instance)
__str__
定制化打印类__repr__
为调试服务的,给程序开发者看的。如果想保持和__str__
一样的话,就向下面代码注释那样,简写为__repr__ = __str__
class Student(object):
def __init__(self, name, age):
self._name = name
self._age = age
def __str__(self):
return "姓名是 {}\t年龄是 {}".format(self._name, self._age)
# __repr__ = __str__
s = Student("patrick", 26)
print(s)
s
__iter__
返回迭代对象,然后不断调用迭代对象的__next__
方法获取下一个值,直到遇到StopIteration
错误时退出循环。可以让一个类用于for ... in ...
迭代循环。__getitem__
使得实例可以像list那样实现索引和分片即list[4] list[2:5]
class Fib(object):
def __init__(self,max):
self._a, self._b = 0, 1
# 最大的数
self._max = max
# 返回迭代对象
def __iter__(self):
return self
# 调用一次返回一个值
def __next__(self):
self._a, self._b = self._b, self._a+self._b
# 超过了最大值就抛出异常StopIteration表明迭代已结束
if self._a > self._max:
raise StopIteration()
return self._a
# 支持像list那样 list[4] list[2:5]
def __getitem__(self, n):
if isinstance(n, int):
a,b = 0,1
for i in range(n+1):
a,b = b, a+b
return a
elif isinstance(n, slice):
a, b = 0, 1
start = n.start
stop = n.stop
step = n.step
for i in range(start+1):
a,b = b, a+b
res_list = []
res_list.append(a)
for i in range(stop - start - 1):
a,b = b, a+b
res_list.append(a)
return res_list
from functools import reduce
s = reduce(lambda x,y: str(x)+" "+str(y), (x for x in Fib(100)))
print(s) # 1 1 2 3 5 8 13 21 34 55 89
print(Fib(100)[4]) # 5
print(Fib(100)[2:5]) # [2, 3, 5]
__getattr__
动态返回一个属性(返回函数也是可以的)。当调用不存在的属性时,比如学生类不存在的address
属性``,Python解释器会试图调用__getattr__(self, 'address')
来尝试获得属性,这样,我们就有机会返回address
的值:class Student(object):
def __getattr__(self, attr):
if attr=='info':
def info():
print("学生信息打印中")
return info
if attr == "address":
return "China"
# 找不到的属性就抛出异常
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
s = Student()
# 调用的属性是方法 所以需要后面添加括号来调用
s.info()
# 在__getattr__也找不到就会抛出AttributeError异常
# print(s.name) # 抛出异常
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
例如针对API地址如下,利用完全动态的__getattr__
,我们可以写出一个链式调用:
http://api.server/user/friends
http://api.server/user/timeline/list
还有些REST API会把参数放到URL中,如下,调用时,需要把:user替换为实际用户名
GET /users/:user/repos
我们再学下一个方法再解决上面的链式调用的问题。
__call__
当调用对象方式时通过instance.method()
来调用,那么能不能在实例本身上调用呢?是可以的,就是通过__call__
来调用自身。这里我们就可以把实现了__call__
方法的对象看做是个函数。那么如何判断一个变量是对象还是函数呢?可以通过callable(var)
来判别。class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
self._path = '%s/%s' % (self._path, path)
return self
# return Chain('%s/%s' % (self._path, path))
def __call__(self, *args, **kwargs):
self._path = '%s/%s' % (self._path, args[0])
return self
# return Chain('%s/%s' % (self._path, args[0]))
def __str__(self):
return self._path
__repr__ = __str__
# Fib没有实现了__call__方法 所以Fib实例对象无法当做函数被调用
print(callable(Fib(100))) # False
# Chain实现了__call__方法 所以Chain实例对象可当做函数调用
print(callable(Chain())) # True
s = Chain().users.repos.detail
print(s) # /users/repos/detail
# GET /users/:user/repos/detail
s = Chain().users('michael').repos.detail
print(s) # /users/michael/repos/detail
python提供了枚举类的支持,不用自己去定义常量了。有两种实现方法。
Enum
类去定义一个枚举类如Level
,然后在后面定义枚举成员,其中枚举成员有对应的value
值,默认从1开始。Enum
类,这样可以自定义value值。同时可以通过@unique
检查枚举成员或枚举成员的值是否有重复。注意这种方式遍历所有的枚举成员时和上一种方法是不同的。from enum import Enum,unique
# 定义枚举
level = Enum("Level", ("Best", "High", "Middle", "Low", "Worst"))
# 获取枚举值
print(type(level.Best)) #
print(level.Worst) # Level.Worst
# 遍历所有枚举对象 value是自动给成员赋的值,默认从1开始
for name,mem in level.__members__.items():
print(name, mem, mem.value) # Best Level.Best 1
# unique 可以帮助我们检查是否有重复值
@unique
class CustomLevel(Enum):
Best = 100
High = 99
Middle = 80
Low = 60
Worst = 10
# 遍历所有枚举对象 注意这里直接用CustomLevel即可
for name,mem in CustomLevel.__members__.items():
print(name, mem, mem.value) # Best CustomLevel.Best 100
# 用名称引用枚举成员
print(CustomLevel.Best)
print(CustomLevel["Best"])
# 用value值引用枚举成员
print(CustomLevel(100))
type()
函数除了可以查看变量类型之外,还可以用于动态创建class。
Student = type("Student", (object,), dict(hello=func))
语法中
第一个参数是 类的名字
第二个参数是 类所要继承的父类,注意单元素tuple的写法
第三个参数是 实例方法的集合,字典类型。
def func(self, name="World"):
print("Hello %s" % name)
# 通过type来动态创建class
Student = type("Student", (object,), dict(hello=func))
print(type(Student)) #
s = Student()
s.hello() # Hello World
s.hello("patrick") # Hello patrick
正常情况下,我们都是通过class
关键字定义类的。但是Python作为动态语言,本身就可以在运行期间创建类,可以用type()
函数创建类。
除了可以用type()
函数创建类之外,还可以用元类metaclass
创建。元类用于创建类,然后再创建类的实例。可以理解为元类的实例化就是类。
以一个简单的例子来讲解,我们给类MyList
创建add()
方法。
定义ListMetaclass
,按照默认习惯,metaclass
的类名总是以Metaclass
结尾,以便清楚地表示这是一个metaclass
:
自定义的ListMetaclass
是创建类的模块,必须继承于type
。然后在__new__
方法内修改类的定义如添加方法,返回类的定义时通过type
创建后返回。
同时MyList
类需要指定metaclass=ListMetaclass
,表明在创建MyList
类时通过ListMetaclass.__new__
方法创造。
__new__
方法参数说明:
第一个参数是 __new__
方法所在类的类对象(本例是ListMetaclass
),
第二个参数是 要动态创建的类的名称
第三个参数是 要动态创建的类的父类的集合
第四个参数是 要动态创建的类的属性和方法的集合
# metaclass是类的模板,所以必须从type类型派生:
class ListMetaclass(type):
# __new__是一个classmethod,第一个参数是类对象即当前类名ListMetaclass
# staticmethod和classmethod的区别是classmethod第一个参数需要是cls即类对象,但是staticmethod可以不需要任何参数
def __new__(cls, name,bases,attrs):
attrs["add"] = lambda self, value: self.append(value)
# print("cls={}\tname={}\tbases={}\tattrs={}".format(cls, name, bases, attrs))
# 注意cls是当前类名ListMetaclass name才是真正要创建的类名
# return type(name,bases,attrs) # 这也是可行的
return type.__new__(cls, name, bases, attrs)
#
# def __new__(cls, *args, **kwargs):
# print("cls={}".format(cls))
# print("args={}".format(args))
# print("kwargs={}".format(kwargs))
# 传入关键字metaclass表示python解释器在创建MyList时通过ListMetaclass.__new__来创建
# 故而可以在该方法里修改类的定义,譬如加上新的方法,然后返回修改后的类的定义
class MyList(list, metaclass=ListMetaclass):
pass
my_list = MyList()
my_list.add(1)
my_list.add(2)
print(my_list) # [1, 2]
上面的代码就展示了给MyList
类动态添加add
方法,但是普通的list就没有add
方法。
我们假设用户使用的接口是如下所示的,其中Model
类和IntegerField
类及StringField
类都是由我们编写的ORM框架实现的。
然后通过User(id=10, name='Michael')
来创建一个用户对象,并通过user.save()
方法保存该对象到数据库。
# Model IntegerField StringField 都是由ORM框架提供
# user.save()等方法全部由metaclass自动完成
class User(Model):
# 定义类的属性到列的映射:
# name是属性名, username是字段名
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 创建一个实例:
u1 = User(id=10, name='Michael', email='[email protected]', password='my-pwd')
# 保存到数据库:
u1.save()
首先定义字段类Field
并分别从该类派生出两个类IntegerField
和StringField
分别表示数据库字段的整型和字符串型。
然后定义元类ModelMetaclass
,由于元类是可以下沉到子类生效的,所以我们可以通过元类ModelMetaclass
来动态改变继承了Model
类的属性和方法。在元类ModelMetaclass
的__new__
方法里进行对非Model
类做属性上的处理,即把id = IntegerField('id')
等关系变成映射并以属性__mapping__
存储到非Model
类对象里,并同时存储属性__table__
,最后要删除原有的id
等属性(因为已经用完后续用不到,最好删除免得出错)
元类编写完成后,再写复杂的Model
类,Model
类继承于字典,并写上__getattr__
和__setattr__
方法,再编写save
方法。
写完后就可以调试代码了。
完整代码如下
# -*- coding: UTF-8 -*-
# 存储字段名和其类型
class Field(object):
def __init__(self, column_name, column_type):
self._column_name = column_name
self._column_type = column_type
@property
def column_name(self):
return self._column_name
def __str__(self):
return "columnName:{}\tcolumnType:{}".format(self._column_name, self._column_type)
class IntegerField(Field):
def __init__(self, column_name):
# 显示调用父类的__init__方法
super(IntegerField, self).__init__(column_name, "bigint")
class StringField(Field):
def __init__(self, column_name):
# 显示调用父类的__init__方法
super(StringField, self).__init__(column_name, "varchar(100)")
# 编写ModelMetaclass
class ModelMetaclass(type):
def __new__(cls, name,bases,attrs):
if name == "Model":
return type.__new__(cls, name, bases, attrs)
# 那就是Model的子类
# 首先分析子类的字段及其类型 并保存到该类的实例对象属性里
mappings = {
}
for k, v in attrs.items():
if isinstance(v, Field):
print("found {} on class {}".format(v, name))
mappings[k] = v
# 保存完后删除掉原先的字段属性
for k in mappings.keys():
attrs.pop(k)
# 保存mapping关系和table到attr里
attrs["__mapping__"] = mappings
# 假设类名就是表名
attrs["__table__"] = name
print("------------------------------------------------")
return type.__new__(cls, name, bases, attrs)
# 编写基类Model metaclass能向下继承
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError("'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
sql = "insert into {table_name}({fields}) values({params})"
table_name = self.__table__
fields = []
params = []
values = []
for k,v in self.__mapping__.items():
fields.append(v.column_name)
params.append("?")
values.append(self.get(k, "NULL"))
# 格式化sql
sql = sql.format(table_name=table_name, fields=",".join(fields), params=",".join(params))
print("SQL: {}".format(sql.upper()))
print("ARGS: {}".format(values))
# Model IntegerField StringField 都是由ORM框架提供
# user.save()等方法全部由metaclass自动完成
class User(Model):
# 定义类的属性到列的映射:
# name是属性名, username是字段名
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 创建一个实例:
u1 = User(id=10, name='Michael', email='[email protected]', password='my-pwd')
u1.password="comlicated password"
# 保存到数据库:
u1.save()
print("------------------------------------------------")
u2 = User(id=11, name='patrick', email='[email protected]')
# 保存到数据库:
u2.save()
运行结果如下图所示,其中图中红色一号部分的打印内容是python解释器在扫描到User
类的定义时触发的。红色二号部分的打印内容就是调用save()
方法触发的。
至此,一个简单的ORM框架就实现了。metaclass
真是一个非常有魔法力的对象!好用,但也要小心使用!!!
廖雪峰老师的python教程
Python元类操作