类和对象的创建

  1. # 经典类 没有继承 object的类
  2. # 新式类 继承了 object的类
  3. class Money: # 2.x中默认是经典类,3.x中是新式类
  4. pass
  5. class Money(object): # 兼容的一种写法
  6. pass
  7. # Money既是类的__name__属性名,又是一个引用该类的变量
  8. print(Money.__name__) # Money
  9. xxx = Money
  10. print(xxx.__name__) # Money

对象

  1. one = Money()
  2. print(one) # <__main__.Money object at 0x000001555E9534A8>
  3. print(one.__class__) # <class '__main__.Money'>

属性相关

对象属性

  1. class Person:
  2. pass
  3. p = Person()
  4. # 给 p对象增加属性, 所有的属性是以字典的形式组织的
  5. p.age = 18
  6. print(p.age) # 18
  7. print(p.__dict__) # {'age': 18}
  8. print(p.sex) # AttributeError: 'Person' object has no attribute 'sex'
  9. # 删除p对象的属性
  10. del p.age
  11. print(p.age) # AttributeError: 'Person' object has no attribute 'age'

类属性

  1. class Money:
  2. num = 666
  3. count = 1
  4. type = "rmb"
  5. print(Money.num) # 666
  6. # 对象查找属性,先到对象自身去找,若未找到,根据 __class__找到对应的类,然后去类中查找
  7. one = Money()
  8. print(one.count) # 1
  9. # 不能通过对象去 修改/删除 对应类的属性
  10. one.num = 555 # 实际上是给 one 对象增加了一个属性
  11. print(Money.num) # 666
  12. print(one.num) # 555
  13. # 类属性会被各个对象共享
  14. two = Money()
  15. print(one.num, two.num) # 666 666
  16. Money.num = 555
  17. print(one.num, two.num) # 555 555

限制对象的属性添加

  1. # 类中的 __slots__属性定义了对象可以添加的所有属性
  2. class Person:
  3. __slots__ = ["age"] # 只允许添加一个 age属性
  4. p1 = Person()
  5. p1.age = 1
  6. p1.num = 2 # AttributeError: 'Person' object has no attribute 'num'

私有化属性

  • Python没有真正的私有化支持,只能用给变量添加下划线来实现伪私有;通过名字重整机制
  • 属性的访问范围:类的内部—>子类内部—>模块内的其他位置—>其他模块

    公有属性 x 的访问范围

  • [x] 类的内部

  • 子类内部
  • 模块内的其他位置
  • [x] 子类内部

    受保护属性 _x 的访问范围

  • [x] 类的内部

  • 子类内部
  • 模块内的其他位置(但不推荐)
  • [x] 子类内部(from … import xxx 不可以访问,要指明all变量)

    私有属性 __x 的访问范围

  • [x] 类的内部

  • 子类内部
  • 模块内的其他位置
  • [ ] 子类内部(同_x)

    保护数据案例

    ```python class Person: def init(self):

    1. self.__age = 18

    def set_age(self, age): # 错误数据的过滤

    1. if isinstance(age, int) and 0 < age < 150:
    2. self.__age = age
    3. else:
    4. print("Wrong age value")

    def get_age():

    1. return self.__age

p = Person() print(p.get_age()) # 18 p.set_age(22)
print(p.get_age()) # 22

  1. <a name="c17fa77c"></a>
  2. ### 只读属性
  3. ```python
  4. # 1. 属性私有化 + 属性化 get()方法
  5. class Person(object):
  6. def __init__(self):
  7. self.__age = 18
  8. # 可以以使用属性的方式来使用方法
  9. @property
  10. def age(self):
  11. return self.__age
  12. p = Person()
  13. print(p.age) # 18
  14. p.age = 666 # Attribute Error: can't set attribute
  15. # 2. 通过底层的一些函数
  16. class Person:
  17. # 通过 属性 = 值 的方式来给一个对象增加属性时,底层都会调用这个方法,构成键值对,存储在 __dict__字典中
  18. # 可以考虑重写底层的这个函数,达到只读属性的目的
  19. def __setattr__(self, key, value):
  20. if key == "age" and key in __dict__:
  21. print("read only attribute")
  22. else:
  23. self.__dict__[key] = value

方法相关

方法的划分

  • 实例方法
  • 类方法
  • 静态方法

    1. class Person:
    2. def instance_fun(self): # self: 调用对象的本身,调用时不用写,解释器会传参
    3. print("instance method", self)
    4. @classmethod
    5. def class_fun(cls): # cls: 类本身
    6. print("class method", cls)
    7. @staticmethod
    8. def static_fun():
    9. print("static method")
  • 所有的方法都存储在类中,实例中不存储方法

  • 类方法静态方法无法访问实例属性

    方法的私有化

  • 和变量的私有化思想差不多

    1. class Person:
    2. __age = 18
    3. def __run(self): # 只能在该类中被调用
    4. print("running...")

    元类

  • 创建类对象的类(类也是一个对象)

    1. a, s = 8, "123"
    2. print(a.__class__, s.__class__) # <class 'int'> <class 'str'>
    3. print(int.__class__, str.__class__) # <class 'type'> <class 'type'>
  • type是元类。
    面向对象 - 图1

  • 通过type元类来创建类,动态创建。也可以用__metaclass__来指明元类,进行类的创建。
    • 检测类对象中是否有 metaclass属性
    • 检测父类中是否有 metaclass属性
    • 检测模块中是否有 metaclass属性
    • 通过内置的type来创建类 ```python def run(self): print(“run…”)

Dog = type(“Dog”, (), {“count”: 0, “run”: run}) print(Dog) # d = Dog() print(d.count) # 0 print(d.run()) # run…

  1. 更加详细的内容,在进**高级部分**的元类编程讲解
  2. <a name="6a438b49"></a>
  3. # 内置的特殊属性
  4. ![](https://cdn.nlark.com/yuque/0/2019/png/235249/1547557661923-13188f50-c49e-487d-be90-ac0691c4dded.png#align=left&display=inline&height=390&originHeight=430&originWidth=766&size=0&status=done&width=694)
  5. <a name="a9a9c5cb"></a>
  6. # 内置的特殊方法(魔法函数)
  7. 这里只做一个了解,高级部分会详细地讲解魔法函数。可以理解为在类中实现了这些特殊的函数,类产生的对象可以具有**神奇**的语法效果。
  8. <a name="ba7964b8"></a>
  9. ## 信息格式化操作
  10. ```python
  11. calss Person:
  12. def __init__(self, n, a):
  13. self.name = n
  14. self.age = a
  15. # 面向用户
  16. def __str__(self):
  17. return "name: %s, age: %d" % (self.name, self.age)
  18. # 面向开发人员
  19. def __repr__(self):
  20. # todo
  21. # 一般默认给出对象的类型及地址信息等
  22. # 打印或进行格式转换时,先调用 __str__()函数,若未实现,再调用 __repr__()函数
  23. p = Person("Rity", 18)
  24. print(p) # name: Rity, age: 18
  25. res = str(p)
  26. print(res) # name: Rity, age: 18
  27. print(repr(p)) # <__main__.Person object at 0x000001A869BEB470>

调用操作

  1. # 使得一个对象可以像函数一样被调用
  2. class PenFactory:
  3. def __init__(self, type):
  4. self.type = type
  5. def __call__(self, color):
  6. print("get a new %s, its color is %s" % (self.type, color))
  7. pencil = PenFactory("pencil")
  8. pen = PenFactory("pen")
  9. # 一下两种使用方式会调用 __call__()函数
  10. pencil("red") # get a new pencil, its color is red
  11. pencil("blue") # get a new pencil, its color is blue
  12. pen("black") # get a new pen, its color is black

索引操作

  1. class Person:
  2. def __init__(self):
  3. self.cache = {}
  4. def __setitem__(self, key, value):
  5. self.cache[key] = value
  6. def __getitem__(self, key):
  7. return self.cache[key]
  8. def __delitem__(self, key):
  9. del self.cache[key]
  10. p = Person()
  11. p["name"] = "MetaTian"
  12. ...

比较操作

  1. # 使得自己定义的类可以按照一定的规则进行比较
  2. import functools
  3. @functools.total_ordering
  4. class A:
  5. def __init__(self, age, height):
  6. self.age = age
  7. self.height = height
  8. def __eq__(self, other): # ==
  9. return self.age == other.age
  10. def __lt__(self, ohter): # <
  11. return self.age < other.age
  12. a1, a2 = A(18, 170), A(19, 178)
  13. # 因为逻辑具有相反性,当使用 > 时,首先会查找 __gt__()函数,若未定义,将参数交换后调用 __lt()__方法
  14. # 由 == 和 < 可以组合出其他的所有比价逻辑,使用装饰器可以自动生成其他逻辑对应的函数,简化代码
  15. print(a1 < a2) # True
  16. print(a2 > a1) # True
  17. print(a1 >= a2) # False

描述器

  • 描述器是一个对象,用来描述其他对象属性的操作;作用是对属性的操作做验证和过滤。
  • 前面只读属性案例中就是用到了描述器。
  • 在对象的内部增加一个描述器,可以接管对象属性的增删改查操作。 ```python class Age: def get(self, instance, owner): # instance是拥有 age 属性的对象

    1. pass

    def set(self, instance, value):

    1. instance.v = value # 将变量的值绑定在 Person 的实例中

    def delete(self, instance):

    1. pass

class Person: age = Age()

age实例是 p1和 p2两个对象所共享的,所以 Age 对象及实例不应该具有属性,只单纯地提供方法即可

p1 = Person() p1.age = 19 # 调用 set() print(p1.age) # 调用 get()

p2 = Person() p2.age = 20 print(p2.age) # 20

  1. <a name="b9711afe"></a>
  2. ## 资料描述器和非资料描述器
  3. - 也可以称为**数据描述器**和**非数据描述器**
  4. - 资料描述器:实现了`__get__()` 和 `__set__()`
  5. - 非资料描述器:仅仅实现了`__get__()`
  6. - 实例属性和描述器重名时,**操作的优先级**关系:
  7. 资料描述器 > 实例属性 > 非资料描述器
  8. <a name="176808a1"></a>
  9. # 生命周期
  10. - 用来表示一个对象从创建到释放的过程
  11. ```python
  12. class Person:
  13. __count = 0
  14. def __init__(self):
  15. Person.__count += 1
  16. def __del__(self):
  17. Person.__count -= 1
  18. @classmethod
  19. def log(cls):
  20. print("we have %d people" % cls.__count)
  21. p1 = Person()
  22. Person.log() # we have 1 people
  23. p2 = Person()
  24. Person.log() # we have 2 people

内存管理机制

引言

  • 万物皆对象,不存在基本数据类型
  • [-5, 正无穷) 范围内相等的整数和短小的字符串,Python会进行缓存,不会创建多个对象

    1. n1 = 1
    2. n2 = 1
    3. print(id(n1), id(n2)) # 1708655056 1708655056
  • 容器对象:存储的对象,仅仅是其他对象的引用(列表)

    内存回收

  • 引用计数

    • 一个对象会记录着自身被引用的个数
    • 每增加一个引用,引用数+1,减少一个引用,引用数-1
    • 引用数为0的时候,会被当做垃圾进行回收
    • 会出现两个对象循环引用的问题
  • 垃圾回收
    • 从经历过引用计数机制但仍然未被释放的对象中,进行内存释放
    • 新增的对象个数 - 消亡对象的个数达到一定阈值时才进行垃圾检测
  • 分代回收
    • 分代回收是垃圾回收的高效解决方案,不需频繁地进行垃圾检测
    • 存活时间越久的对象,越不可能在后面的过程中变成垃圾
    • 设立0, 1, 2三代对象集合,对其中的对象进行不同频率的检测
    • 第一次检测存活下来的,从0代纳入1代,0代检测一定次数后开始检测1代,以此类推

      深拷贝和浅拷贝

      浅拷贝

      1. a = [1, 2, 3]
      2. b = a
      3. print(id(a), id(b)) # 2229855665608 2229855665608

      深拷贝

      1. import copy
      2. a = [1, 2, 3]
      3. c = copy.deepcopy(a)
      4. print(id(a), id(c)) # 2229855665608 2229861709896

      copy和deepcopy的区别

      1. import copy
      2. a = [1, 2, 3]
      3. b = [4, 5, 6]
      4. c = [a, b]
      5. d = copy.deepcopy(c)
      6. e = copy.copy(c)

面向对象 - 图2

使用copy拷贝可变类型时,进行单层次的深拷贝,若拷贝的是不可变类型,则进行浅拷贝。

面向对象三大特性

封装

继承

  • 非私有的属性和方法可以被继承,继承不是拷贝了资源,而是具有了资源的访问权,资源的存储位置在父类中,实现资源重用
  • Python中可以使用多继承。 ```python

    所有的类都继承了 object 类

    所有的类对象都由 type 实例化出来

    class A: pass

class B: pass

class C(A, B): # C 类继承了 A和 B类 pass

print(C.bases) # (, ) print(int.base) # print(bool.base) #

  1. - 几种继承的形式<br />
  2. ![](https://cdn.nlark.com/yuque/0/2019/png/235249/1547646419358-8ed4b7c9-a79d-46df-8e59-41f09d5610d0.png#align=left&display=inline&height=299&originHeight=368&originWidth=893&size=0&status=done&width=726)<br />
  3. - 资源查找顺序
  4. - 单继承链:C-->B-->A
  5. - 无重叠多继承链:按照单继承链**深度优先**查找(C-->B-->A-->D-->E)
  6. - 有重叠多继承链:广度优先查找(C-->B-->D-->A)
  7. - 资源覆盖
  8. - 在优先级较高的类中重新定义了**同名的属性和方法**,再次调用时,会调用到优先级较高类中的资源,并不是相关的资源在内存上被**覆盖**了,而是调用优先级出现了变化
  9. - `self` `cls`<br />
  10. ```python
  11. # 谁调用方法,self 和 cls 就是谁
  12. # 带着参数去找方法
  13. class A:
  14. def show(self):
  15. print(self)
  16. @classmethod
  17. def tell(cls):
  18. print(cls)
  19. class B(A):
  20. pass
  21. B.tell() # <class '__main__.B'>
  22. B().show() # <__main__.B object at 0x027674D0>
  • 资源的累加 ```python class A: def init(self):
    1. self.x = 2
    class B(A): pass class C(A): def init(self):
    1. self.y = 1
    class D(A): def init(self):
    1. self.y = 1

class E(A): def init(self): super().init() # 会调用 A的构造函数,参数可以省略

  1. # A.__init__(self) //和上面等价,要传参
  2. self.y = 1

b = B() print(b.x) # 2, 调用了父类的构造函数,b 调用,x就是 b的 c = C() print(c.y) # 1, C有了构造函数,就调用 C的,A的构造函数不会被调用 print(c.x) # 报错,没有这个属性 e = E() print(e.x, e.y) # 2, 1

  1. <a name="154ae683"></a>
  2. ## 多态
  3. - Python是动态类型的语言,不需要严格意义上的多态
  4. ```python
  5. def test(obj):
  6. obj.func()
  7. # 只要传入的参数有 func()这个方法,就可以传入进行执行,不用进行类型检测。
  8. # 不需要按照其他静态语言那样沿着继承链进行方法调用形成多态

类的设计原则

  • 单一职责原则:一个类只负责一项职责
  • 开放封闭原则:对外扩展开放,对内修改关闭
  • 里式替换原则:子类所继承下来的属性和方法都需能够合理地使用
  • 接口分离原则:功能一致的方法应该重新组成新的接口/类,进行细分
  • 依赖倒置原则:高层模块不应该直接依赖低层模块,核心是面向接口编程