python面向对象编程

文章目录

      • 面向对象基础
        • 类和实例
        • 访问限制
        • 继承和多态
        • 获取对象信息
        • 实例属性和类属性
      • 面向对象高级编程
        • 使用__slots__
        • 使用@property
        • 多重继承
        • 定制类
        • 使用枚举类
        • 使用元类
          • ORM框架简单实现
      • 参考网址

面向对象基础

面向对象编程----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。然后这个学生类有两个属性namescore,有一个对象方法print_score打印成绩。Student就是抽象出来的类(class),std1std2就是学生类具体的对象即实例instance。也可理解为类实例化。
对象方法和普通的方法并没有太大的区别,唯一的区别就是,对象方法的第一个参数必须是self,但是调用对象方法的时候不用显示传递self参数,python解释器会自动帮你传入实例对象。
__init__对象方法相当于Java里的构造函数,用于创建对象的。

类和实例

通过class关键字定义一个类,class ClassName(object)。其中类名需要首字母大写并采用驼峰式命名(编程习惯),括号里的object是该ClassName需要继承的类,如果找不到具体需要继承哪个类的话,就写object,这个和Java继承Object类是一样的。
可以通过ClassName()来创建类的实例,而不用像Java那样需要new关键字。

如下图所示,可以看到对象sdt1sdt2在内存中的地址都是不同的,但是都属于Student
python面向对象编程_第1张图片
有一点和Java区别很大的是,由于Python是动态语言,Python类的属性是可以动态绑定的。以Student类为例,本来只有namescore两个属性,但是可以随便添加和修改及删除属性(delattrdel都可以删除属性,但建议用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类,自带一个对象方法runMonkey继承了Animal类没有重写对象方法run,但是DogCat继承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里可以查看FunctionTypeBuiltinFunctionType这些函数类型。

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
python面向对象编程_第2张图片
类似__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

面向对象高级编程

使用__slots__

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

使用@property

我们以学生类为例,可以通过将属性前面加上_表示该属性不要直接访问,然后提供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支持多重继承来扩展类功能。
在设计类的继承关系时,通常,主线都是单一继承下来的。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计被称之为MixInMixIn的目的就是给一个类增加多个功能。

例如,Dog类就继承三个类MammalRunnableMixInCarnivorousMixIn。其中RunnableMixInCarnivorousMixIn就是“混入”的额外功能。

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开始。
  • 自定义class并继承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方法。

ORM框架简单实现

我们假设用户使用的接口是如下所示的,其中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并分别从该类派生出两个类IntegerFieldStringField分别表示数据库字段的整型和字符串型。

然后定义元类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()方法触发的。
python面向对象编程_第3张图片

至此,一个简单的ORM框架就实现了。metaclass真是一个非常有魔法力的对象!好用,但也要小心使用!!!

参考网址

廖雪峰老师的python教程
Python元类操作

你可能感兴趣的:(python,python,orm,meta,oop)