1 | 类 Class
1-1 | 对象
对象 = 属性 attributes + 方法 method,它代表了某种具体事物的唯一实例
对象是盒子,类就像制作盒子的模具
使用 class
定义类:
class Person:
pass # 可用于表示一个空类
class Person(): # 也可以加上括号
pass
class Person():
# 初始化函数
def __init__(self, name):
self.name = name
self.arr = []
def func(self):
self.other_func() # 通过self, 方法可互相调用
print('This is a method')
实例化 instantiation
captain = Person('Davy Jones')
获取属性 & 调用方法
>>> captain.name # data attributes
'Davy Jones'
>>> captain.func() # method
'This is a method'
方法也是普通函数,因此也具有 __doc__
等属性
1-2 | self
定义实例方法时,第一个参数必须是 self
,其没有任何特殊含义
car = Car()
car.exclaim()
执行这两行代码时,Python 实际做了两件事情:
先查找类Car
, 再把 car 对象作为 self
参数传给 Car 类的 exclaim()
方法
相当于:
Car.exclaim(car)
1-3 | 类中方法
-
普通方法
-
实例方法 instance method
以
self
作为第一个参数的方法 -
类方法 class method
作用于整个类,对类做出任何改变会对它所属的 instance 产生影响, 用
@classmethod
定义 -
静态方法 static method
既不影响类也不影响实例,既不用
self
也不用cls
参数, 类与实例均可调用,用@staticmethod
定义**何时使用?**逻辑上相对独立可以封装,与类相关,希望记录在类中
class A:
count = 0 # 类属性
def __init__(self):
self.no = A.count
A.count += 1
def exclaim(self): # 实例方法
print('I am an A')
@classmethod
def num(cls): # 类方法,第一个参数必须是cls, cls可调用类特性,写 self 也不出错
print(f'Count is {cls.count}')
@staticmethod
def intro():
print('This is a static method')
📋也可传入staticmethod()
定义来自类外的函数
def some_function():
pass
class C:
method = staticmethod(some_function)
📋实例可以调用实例方法、类方法、静态方法,类只能调用类方法、静态方法
类 | 实例 | |
---|---|---|
实例方法 | ✔️ | |
类方法 classmethod | ✔️ | ✔️ |
静态方法 staticmethod | ✔️ | ✔️ |
Classmethod 🆚 Staticmethod
-
入参不同:类方法第一个入参为
cls
, 静态方法无限制 -
属性可见:类方法可以获取/修改类属性,但静态方法需要改写法
A.intro(A)
-
定义方式不同:类方法用
@classmethod
,静态方法用@staticmethod
1-4 | 类特性
类与其实例均可定义特性,且相互独立
class Grape:
color = 'purple'
def __init__(self):
self.color = 'purple'
>>> Grape().color = 'green'
>>> Grape.color
'purple'
1-5 | 类保留名称
_
下划线开头的对属性或方法,无法被from mod import *
这种代码引入_*_
下划线开头结尾的属于解释器定义的 dunder__*
两个下划线开头,类“私有”名称,防止子类覆写父类
“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python
–Python official document
Python 不存在私有变量,但是可采用以上命名惯例予以表示,Python 会自动对其"保护"
class Person():
def __init__(self, name, age):
self._age = age
self.__nombre = name
class Man(Person):
pass
jax = Man("Jax", 29)
>>> jax._age
29
>>> jax.__nombre
AttributeError: 'Man' object has no attribute '__nombre'
💡双下划线开头的属性可通过 _class__attr
的形式取值、设值,jax._Person__nombre
💻dataclass
🆕Python 3.7+,省去了 __init__
方法的构造
from dataclasses import dataclass
@dataclass
class Car:
color: str
mileage: float
automatic: bool
>>> car1 = Car("red", 3812.4, True)
💻isinstance(obj, cls)
检查某 object 是否属于某 class
>>> a = 1
>>> isinstance(a, int)
True
💻vars(obj)
调用 __dict__
方法,只要底层定义了该方法的任何对象都可以,如模块,类,实例等
2 | 继承 Inheritance
被继承者被称为父类、超类或基类;继承者被称为子类、次类或派生类
从已有类中衍生出的新类,添加或修改部分功能,实质是代码复用
class Car:
pass
class Benz(Car):
pass
💡过多使用继承会导致代码复杂,不方便维护!建议采用聚合、组合
覆盖 Override:Python 不可能强制隐藏数据,子类的任何同名方法、属性会覆盖父类的
子类中的新方法,父类对象无法调用
💻 issubclass()
语法:issubclass(cls, class_or_tuple)
检查某 class
是否继承某 class
, 被继承的class 可以传一个,也可以传多个
>>> issubclass(OrderedDict, dict)
True
3 | 多继承 Multiple Inheritance
class D(A, B, C):
pass
调用顺序:D -> A -> B -C
假设有两个方法 fun1
fun2
,ABC 三类中均定义了这两个方法,若指定某类的某方法可以通过类名调用:
class D(A, B):
def call_func_from_a(self):
A.func(self) # 别忘传入self
3-1 |❓ 不确定先后顺序时怎么办
靠前的先使用!
每个 class 都有一个特殊方法 mro()
或__mro__
,返回一个类名列表,用于决定调用方法、属性的先后顺序
# mro() method of builtins.type instance.Return a type's method resolution order.
>>> D.mro()
Out[20]: [__main__.D, __main__.A, __main__.B, object]
3-2 | mixin
实质是一个普通类,作用是为子类添加额外功能,复用代码
class DocMixin:
def doc(self):
print(self.__doc__)
class Test(DocMixin):
"""This is a test class"""
pass
t = Test()
>>> t.doc()
This is a test class
4 | 调用父类方法 super()
需要父类的某一方法,不要直接复制粘贴代码,不利于维护,父类更改后需手动修改子类
class B(A):
def __init__(self):
super().p() # 调用时不用加self
# 其实相当于 super(B, self).p()
调用爷类方法
class A:
class B(A):
class C(B):
def foo(self):
# M1.直接用类 A 调用
A.func_belong_to_A(self)
# M2.super() 中写父类 B 调用爷类 A
super(B, self).func_belong_to_A()
5 | 描述符 Descriptors
Any object which defines the methods
__get__()
,__set__()
, or__delete__()
Descriptor 让对象可以自定义特性查找,存储,删除
Descriptor 是深度理解 Python 的重要基础知识,因为 Python 中很多东西都基于其实现,如函数,方法,类方法,静态方法等
# Descriptor Protocol
__get__(self, obj, obj_type=None) -> object
__set__(self, obj, value) -> None
__delete__(self, obj) -> None
# 可选
__set_name__
典型用例
-
返回常量:多个类返回同一个值时,可复用代码
class Ten: def __get__(self, obj, objtype=None): return 10 class A: num = Ten()
-
返回变量(动态求值)
class DirectorySize: def __get__(self, obj, objtype=None): return len(os.listdir(obj.dirname)) class Directory: size = DirectorySize() # 动态查看文件数量
-
控制数据访问:修改实例数据前,先执行一些业务逻辑,如数据校验
-
自定义名称:将实例的某个特性的名称分为对外名称和内部名称
class LoggedAccess: def __set_name__(self, owner, name): self.public_name = name self.private_name = '_' + name def __get__(self, obj, objtype=None): value = getattr(obj, self.private_name) return value def __set__(self, obj, value): setattr(obj, self.private_name, value) class Person: name = LoggedAccess() def __init__(self, name): self.name = name # 调用 __set_name__
6 | 属性 Property
Python 的特性 Attribute 都是公开的,如果不放心直接访问对象特性,可用属性替代
🆚descriptor:property 是高级别的机制,其实现依赖于 descriptor
如何定义
定义属性需要编写 getter
和 setter
,不设定 setter 方法将无法从外部修改属性
- 方法一:直接声明方法,再通过
property()
进行绑定
class Person():
def __init__(self, input_name):
self.the_name = input_name # 隐藏了属性 _name
def get_name(self):
print('This is Getter')
return self.the_name
def set_name(self, input_name):
print('Name can\'t change!')
name = property(get_name, set_name) # 获取 name 调用前者,修改调用后者
>>> batman = Person("Bruce Wayne")
>>> batman.name
This is Getter
'Bruce Wayne'
>>> batman.name = "Joker"
Name can't change!
- 方法二:加装饰器声明
class Person:
def __init__(self):
self._name = None
@property
def name(self):
return self._name # 隐藏了属性 _name
@name.setter
def name(self, value):
self._name = value
@name.deleter
def name(self):
del self._name
print("name is cleared")
batman = Person()
batman.name = "bruce wayne"
>>> batman.name
'bruce wayne'
>>> del batman.name
name is cleared
※ 但如果能猜到类中有一个 _name
的特性,仍可通过直接访问进行修改,属性包裹后也并不完全私密
7 | 多态 Polymorphism
-
父类声明接口,子类定义具体实现
-
不同子类对同一行为的不同表现形式
不同类,同名方法,入参相同,结果不同,这是多态的传统形式:
class A:
def say(self):
print('This is a A')
class B(A):
def say(self):
print('This is a B')
鸭子类型
Python 对于多态更进一步,即使类不相互继承,只要有该方法即可调用
def call_say(obj):
obj.say()
# 与 A, B 类无关的 C 也有一个方法 say ()
class C:
def say(self):
print('This is a C')
>>> call_say(A())
This is a A
>>> call_say(B())
This is a B
>>> call_say(C())
This is a C
这种行为有时被称作鸭子类型 duck typing,该名称源自以下这句俗语。
如果它像鸭子一样走路,像鸭子一样叫,那它就是一只鸭子
8 | 魔术方法 Dunder
以双下划线 __
开头和结束的方法,主要是 Python 底层方法,由于实际编程中不会采用这种格式命名变量,
示例:obj_A == obj_B
比较等值时会自动调用两个对象所属类中的__eq__
方法
这些接口也可自定义
__eq__(self, other) # self == other
__ne__(self, other) # self != other
__lt__(self, other) # self < other
__le__(self, other) # self <= other
__gt__(self, other) # self > other
__ge__(self, other) # self >= other
__add__(self, other) # self + other
__sub__(self, other) # self - other
__mul__(self, other) # self * other
__truediv__(self, other) # self / other
__floordiv__(self, other) # self // other
__mod__(self, other) # self % other
__pow__(self, other) # self ** other
__len__(self) # len(self)
9 | 组合 Composition
不继承类,将某类的实例作为当前类实例的特性,可调用其方法
class Hand:
def shake_hand(self):
print('shaking....')
class Person:
def __init__(self, hand):
self.hand = hand
hand = Hand()
p = Person(hand=hand)
>>> p.hand.shake_hand()
shaking....
END