Python 中的一切皆对象,包括 class、function 等,具体表现在:
- 可赋值给变量
- 可添加到集合
- 可作为函数参数
- 可作为函数返回值
对象和类型
对象的特征:身份(即对象在内存中的地址,id)、类型、值
type、object、class
type:
- 可以创建所有对象(类、函数、集合)的类,或返回对象的类型(
type(Object) == type
) - type 也是对象(自身类的对象),且作为类时 type 继承于 object:
type.__bases__ == (object, )
object:
- object 是 type 的实例,type 是 object 的类:
type(object) == type
- object 是所有类的基类(
xxx.__bases__
),自定义类默认继承 object:object.__bases__ == ()
常见内置类型
- None:全局只有一个
- 数值:int、float、complex、bool
- 迭代:for ... in ...
- 序列:list、bytes、range、tuple、str、array
- 映射:dict
- 集合:set、fronzenset
- 上下文:with
- 其他:模块(import)、class 和 object、函数、方法、代码、object、type、elipsis、notimplemented
isinstance 与 type
使用 isinstance 与 type 都可以判断对象的类型,但更推荐使用 isinstance,会检查对象类的继承链。
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b, B))
print(isinstance(b, A)) # b属于B类也属于A类
print(type(b) is B) # 判读的是id,要使用is
print(type(b) is A) # b不是A类,所以是false
魔法函数
- 在 Python 中使类获得某种功能不必通过继承基类、实现方法,而是实现魔法函数(协议);
- 魔法函数属于数据模型的一种特性,可以在任何对象中定义;
- 魔法函数对 Python 原生的类型支持更好,应尽量使用原生;
- 可迭代对象
__iter__
会优先于__getitem__
且性能更好。
常用魔法函数及其实现特性
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list
def __getitem__(self, item):
return self.employee[item]
def __len__(self):
return len(self.employee)
def __str__(self):
print("is Company")
def __repr__(self):
print("is Company(dev)")
obj = Company()
__getitem__
:可遍历、切片
for i in obj:
pass
print(obj[:2])
__len__
:取长度
len(obj)
__str__
:输出类信息(__repr__
于交互式环境自动调用)
print(obj)
另外更多魔法函数用法见:https://rszalski.github.io/magicmethods/
类的比较操作
和其他语言(Java、C++ 的运算符重载)类似,Python 也可以通过重写魔法函数使任意类型支持运算和比较操作,下面以比较不同图形的面积大小为例:
from functools import total_ordering
from abc import abstractmethod
# 抽象基类
# 只需实现其中两种判断的魔法函数即可(自动推测出其他的比较方法)
@total_ordering
class Shape(object):
def __init__(self, radius):
self.radius = radius
self.pi = 3.14
# 定义抽象方法(抽象基类 + 抽象方法 -> 接口)
@abstractmethod
def area(self):
pass
# 小于 <
def __lt__(self, obj):
if not isinstance(obj, Shape):
raise TypeError("Obj is not Shape!")
return self.area() < obj.area()
# 等于 =
def __eq__(self, obj):
if not isinstance(obj, Shape):
raise TypeError("Obj is not Shape!")
return self.area() == obj.area()
# 长方形
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# 圆形
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
self.pi = 3.14
def area(self):
return self.radius ** 2 * self.pi
rectangle = Rectangle(2, 2)
circle = Circle(2)
print(rectangle > circle, rectangle == circle, rectangle < circle) # (False, False, True)
加减乘除等运算也支持这样的操作,感兴趣的朋友可以试试。
鸭子类型
- 变量可以指向任何类型的对象(不需要继承,也不必考虑多态);
- 定义了相同方法(包括魔法函数)的类,可以归为同一种类型(比如 list、set 都是可迭代对象,都可以调用
extend
),以相同的方式调用该方法、也可以for
循环迭代; - 缺陷是作为动态语言无法在编译时发现错误。
class Cat(object):
def say(self):
print("i am a cat")
class Dog(object):
def say(self):
print("i am a fish")
class Duck(object):
def say(self):
print("i am a duck")
animal_list = [Cat, Dog, Duck]
for animal in animal_list:
animal().say() # 都可以“叫”,都可以当它是动物来用,而不必区分是哪种动物
抽象基类
- 类似 Java 中的接口(定义子类共有的方法且必须被实现),不能实例化;
- 当希望判定某个对象的类型,或强制某个子类必须实现某方法时,可以通过定义抽象基类让子类继承;
-
collections.abc
模块包含了内部定义的抽象基类,也可以直接导入 abc 自己实现; - 虽然提供了抽象基类功能,但还是应该尽可能重用鸭子类型。
from collections.abc import Sized # Sized表示实现了__len__的类型
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list
def __len__(self):
return len(self.employee)
com = Company(["b1", "b2"])
# 如果没有基类,就需要hasattr来判断对象是否存在某个方法
hasattr(com, "__len__")
# 继承抽象基类的类,只需要根据对象其基类即可判断是否存在某方法
isinstance(com, Sized) # Sized抽象基类肯定存在 __len__ 方法,所以可以放心调用 len
实例:
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod # 如果子类没有实现该方法,则会抛出异常
def get(self, key):
pass
@abc.abstractmethod
def set(self, key, value):
pass
class RedisCache(CacheBase):
pass
r = RedisCache()
类属性和实例属性
定义在类和实例中的变量与方法
class A:
aa = 1
def __init__(self, x, y):
self.x = x
self.y = y
a = A(2,3)
a.aa = 100 # 修改的是实例变量,不会影响类变量
print(a.x, a.y, a.aa)
print(A.aa)
b = A(3,5)
print(b.aa)
查找顺序:
- 由下而上,先检查实例是否存在该属性,不存在再从类中查找;
- 多继承时,采用 C3 算法在父类中查找。
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
静态方法、类方法、对象方法
class Date:
# 构造函数
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 实例方法
def tomorrow(self):
self.day += 1
@staticmethod
def parse_from_string(date_str):
year, month, day = tuple(date_str.split("-"))
return Date(int(year), int(month), int(day))
# 静态方法(与普通函数没什么区别,只是封装在类中,与类交互时要指定类名)
@staticmethod
def valid_str(date_str):
year, month, day = tuple(date_str.split("-"))
return int(year)>0 and (int(month) >0 and int(month)<=12) and (int(day) >0 and int(day)<=31)
# 类方法(隐含参数为类本身,不依赖于类名)
@classmethod
def from_string(cls, date_str):
year, month, day = tuple(date_str.split("-"))
return cls(int(year), int(month), int(day))
# 实例方法
def __str__(self):
return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day)
if __name__ == "__main__":
new_day = Date(2018, 12, 31)
new_day.tomorrow()
print(new_day)
# 2018-12-31
date_str = "2018-12-31"
year, month, day = tuple(date_str.split("-"))
new_day = Date(int(year), int(month), int(day))
print (new_day)
# 用 staticmethod 完成初始化
new_day = Date.parse_from_string(date_str)
print(new_day)
# 用 classmethod 完成初始化
new_day = Date.from_string(date_str)
print(new_day)
print(Date.valid_str("2018-12-32"))
数据封装与私有属性
- 以双下划线开头的私有属性不可以直接通过对象直接访问,而应该定义 get、set 方法进行有条件的读写;
- 以单下划线开头的私有属性不能用
from module import *
导入,其他方面和公有属性访问方式一样; - 私有属性仍然可以通过
obj._classname__field
访问,所以不是绝对安全的; - 利用这个机制,可以解决类继承后私有变量重名的问题。
from x.class_method import Date
class User:
def __init__(self, birthday):
self.__birthday = birthday
def get_age(self):
return 2018 - self.__birthday.year # 返回年龄
if __name__ == "__main__":
user = User(Date(1990,2,1))
print(user._Student__birthday)
print(user.__birthday) # 报错
print(user.get_age())
自省机制
自省是指对象创建后可以通过一定的机制查询和修改其内部结构(如运行时能够获得对象的类型、为对象添加方法)。
from x.class_method import Date
class Person:
name = "user"
class Student(Person):
def __init__(self, scool_name):
self.scool_name = scool_name
if __name__ == "__main__":
user = Student("ywh")
print(user.__dict__) # 通过__dict__查询实例具有的属性(C实现,性能很高)
user.__dict__["school_addr"] = "北京市" # 动态修改对象
print(user.school_addr)
print(Person.__dict__)
print(user.name) # 自动向上查找
a = [1, 2]
print(dir(a))
super 函数
- 子类继承自父类,有时不需要在子类中执行变量赋值:
self.name = name
,只需要执行父类初始化方法即可; - super 执行顺序:MRO 顺序(而不是调用父类构造方法)
from threading import Thread
class MyThread(Thread):
def __init__(self, name, user):
self.user = user
super().__init__(name=name)
class A:
def __init__(self):
print ("A")
class B(A):
def __init__(self):
print ("B")
super().__init__() # 获取父类,调用其初始化方法
class C(A):
def __init__(self):
print ("C")
super().__init__()
class D(B, C):
def __init__(self):
print ("D")
super(D, self).__init__()
if __name__ == "__main__":
print(D.__mro__)
d = D()
多继承设计(暂时没理解)
建议使用 Mixin 模式:
- Mixin 类功能尽量单一
- 不和基类关联,可以和任意基类组合,基类可以不和 Mixin 关联就能初始化成功
- 在 Mixin 中不要使用 super 这种用法(不能按照 MRO 顺序)