面向对象编程,OOP,Object-oriented Programing,编程范式之一。
基本组成
类和对象、属性和方法(变量与函数)
class Person:
uuid = "123456" # 类属性
def __init__(self, name, age, sex): # 方法
self.name = name # 对象属性
self.age = age # 对象属性
self.sex = sex # 对象属性
def walk(self, n): # 方法
print(self.name + "走路" + str(n) + "步")
def run(self):
print(str(self) + "跑路")
注:使用对象概念时,有广义和狭义之分,广义上,指Python一切皆对象,类、函数或方法、变量或属性皆为对象,狭义上,特指某场景下类实例化出来的独享。如在对象实例化p = Person("张三", 15, "男")中,广义上,p、Person、"张三"、15、男均为对象,狭义上,p为对象,Person为类。
属性,attribute
属性分为类属性和对象属性,对象属性为对象独有,类属性为该类所有对象共享。Python中支持类属性和对象属性的动态创建和删除,正因如此,仅仅通过类定义无法分析类或对象的全部属性。
类属性和对象属性
# 定义类
class Person:
uuid = "123456"
def __init__(self, name, age, sex): # 方法
self.name = name
self.age = age
self.sex = sex
def walk(self, n):
print(self.name + "走路" + str(n) + "步")
def run(self):
print(str(self) + "跑路")
# 实例化对象
a, b = Person("张三", 15, "男"), Person("洛五", 17, "女")
# 类属性方访问
print(a.uuid, b.uuid, Person.uuid) # 123456 123456 123456
Person.uuid = "654321"
print(a.uuid, b.uuid, Person.uuid) # 654321 654321 654321
# 类属性动态添加
Person.uuid2 = "uuid2"
print(a.uuid2, b.uuid2, Person.uuid2) #
# 类属性动态删除
del Person.uuid2
# print(Person.uuid2) # AttributeError: type object 'Person' has no attribute 'uuid2'.
# 对象属性访问
print(a.name, b.name) # 张三 洛五
a.name, b.name = b.name[0], a.name[0]
print(a.name, b.name) # 洛 张
# 对象属性动态创建,uuid为对象属性
a.uuid, b.uuid = "1", "2"
print(a.uuid, b.uuid, Person.uuid)
# 对象属性动态删除,打印类属性
del a.uuid, b.uuid
print(a.uuid, b.uuid, Person.uuid)
property
property装饰器修饰类中的方法,用于定义动态属性。
- 属性名与
property装饰的函数名一致,如r,可以直接以.访问属性,如c.r。 - 获取属性
r会执行r.getter修饰的方法r。 - 修改属性会执行
r.setter修饰的方法r。 - 删除属性的值会执行
r.setter修饰的方法r。 - 注意
r名称上的一致。 - 若不定义
r.getter修饰的方法,则获取属性r时会执行property修饰的函数。
from math import pi
class Circle:
def __init__(self):
self.__r = 0
@property
def r(self):
print("property")
return pi
@r.getter # getter
def r(self):
print("getter")
return self.__r
@r.setter # setter
def r(self, r):
print("setter")
self.__r = r
@r.deleter
def r(self):
print("deleter")
self.__r = 0
@property
def area_property(self):
return pi * self.r ** 2
def area_func(self):
return pi * self.r ** 2
c = Circle()
print(c.r)
# getter
# 0
c.r = 3
# setter
print(c.area_property)
# getter
# 28.274333882308138
print(c.area_func())
# getter
# 28.274333882308138
del c.r
# deleter
print(c.r)
# getter
# 0
注:关于attribute与property在python的区分的两种尝试性解释
attribute带有致敬,贡品,悼念等具有宗教意思和神学思想的意思,本身就是比较抽象的
Proper是正常的,独特的,适当的,弥撒,章节等,是更贴近于生活,是抽象的宗教和神学思想的具体化
可以理解为attribute是所有属性的统称,具体描述某一个属性时用property更准确,但用attribute无伤大雅。
方法,method
方法分为一般方法(对象方法)、类方法、静态方法。
一般方法
一般方法即对象方法,默认第一个参数为该类的实例化对象。习惯上使用对象.方法(除对象参数之外的参数列表)形式调用,调用形式直接指明对象参数,参数列表中无需手动传入,语法上支持类.方法(对象, 除对象参数之外的参数列表),调用形式未直接指明对象参数,需要手动传入对象,对象一般是该方法所属类的实例化对象,但允许传入任意对象。
class Person:
uuid = "123456"
def __init__(self, name, age, sex): # 方法
self.name = name
self.age = age
self.sex = sex
def walk(self, n):
print(self.name + "走路" + str(n) + "步")
def run(s): # 方法的第一个形参通常用于指向当前对象,习惯上命名为self,允许其他命名
print(str(s) + "跑路")
def run(self): # 习惯上,函数的形参一般不命名为self,允许该命名
print(str(self) + "跑路")
a, b = Person("张三", 15, "男"), Person("洛五", 17, "女")
# 方法的调用
a.walk(520) # 张三走路520步
Person.walk(a, 520) # 张三走路520步
b.run() # <__main__.Person object at 0x000001EE375447D0>跑路
Person.run(b) # <__main__.Person object at 0x000001EE375447D0>跑路
Person.run("删库") # 删库跑路,指定与Person类无关的"删库"对象
类方法
类方法是以装饰器classmethod修饰的方法,默认第一个参数为类。类方法用于执行与对象无关的操作,若操作与对象五无关,何必实例化再调用方法。习惯上,以类.方法(除类参数之外的参数列表)形式调用,调用形式直接指明类参数,参数列表中无需手动传入,语法上支持对象.方法(除类参数之外的参数列表),调用形式上并未指明类,但根据对象可以唯一确定类,同样无需手动传入类参数。
class Person:
uuid = "123456"
def __init__(self, name, age, sex): # 方法
self.name = name
self.age = age
self.sex = sex
@classmethod
def change_uuid(cls, uuid):
cls.uuid = uuid
print(f"{cls.__name__} {uuid}")
a = Person("张三", 15, "男")
a.change_uuid("123") # Person 123
Person.change_uuid("456") # Person 456
静态方法
静态方法是以装饰器classmethod修饰的方法,没有固定的对象参数或者类参数,等同于嵌套在类中但与类无关的函数,习惯上,以类.方法(参数列表)形式调用,同样支持对象.方法(除类参数之外的参数列表)。
class Person:
uuid = "123456"
def __init__(self, name, age, sex): # 方法
self.name = name
self.age = age
self.sex = sex
@staticmethod
def change_uuid(uuid):
Person.uuid = uuid
print(f"{Person.__name__} {uuid}")
a = Person("张三", 15, "男")
a.change_uuid("123") # Person 123
Person.change_uuid("456") # Person 456
绑定方法与非绑定方法
绑定方法指绑定到对象或者类的方法,绑定方法参数列表第一个即为绑定的对象或者类,使用对象或者类调用方法可以自动将对象或者类传入方法,特别地,对象调用类方法会传入对象对应的类。类中一般方法都是绑定到对象的方法,可以使用classmethod装饰器将方法绑定到类,可以使用staticmethod将方法解除绑定,既不绑定对象也不绑定类。
class Person:
uuid = "123456"
def __init__(self, name, age, sex): # 方法
self.name = name
self.age = age
self.sex = sex
def walk(self, n):
print(self.name + "走路" + str(n) + "步")
@classmethod
def change_uuid1(cls, uuid):
cls.uuid = uuid
print(f"{cls.__name__} {uuid}")
@staticmethod
def change_uuid2(uuid):
Person.uuid = uuid
print(f"{Person.__name__} {uuid}")
def run(self):
print(str(self) + "跑路")
a = Person("张三", 15, "男")
print(run) # <function run at 0x000001F4806DDEE0>
print(a.walk) # <bound method Person.walk of <__main__.Person object at 0x000001F4806F4C50>>
print(Person.change_uuid1) # <bound method Person.change_uuid1 of <class '__main__.Person'>>
print(Person.change_uuid2) # <function Person.change_uuid2 at 0x000001F4806DEB60>
访问权限控制
访问权限控制决定外部可以访问属性或方法与否,分为公有public和私有private.
- 属性和方法默认公有访问,使用双下划线前缀标记属性或方法为私有,如
__属性或方法名。 - 魔术方法(双下方法,前后对双下划线)为公有方法。
- 私有属性或方法会被自动转换为
_类__属性或方法形式的名字,且该转换是内部的,动态添加双下划线前缀属性或方法不会转换。
class A:
__uuid = 1
def __init__(self, a, b):
self.a = a
self.__b = b
def get_b(self):
print(f"公有方法get_b -> {self.__b}")
def __get_b(self):
print(f"私有方法__get_b -> {self.__b}")
o = A("a", "b")
print(o.a) # a
# print(o.__b) # AttributeError: 'A' object has no attribute '__b'
print(o._A__b) # b
o.get_b() # 公有方法get_b -> b
# o.__get_b() # AttributeError: 'A' object has no attribute '__get_b'. Did you mean: '_A__get_b'?
o._A__get_b() # 私有方法__get_b -> b
o.__c = "c"
print(o.__c) # c
# print(o._A__c) # AttributeError: 'A' object has no attribute '_A__c'. Did you mean: '_A__b'?
o.__get_c = lambda _: "c"
print(A.__dict__)
print(o.__dict__)
o.__get_c(None)
o._A__get_c(None) # AttributeError: 'A' object has no attribute '_A__get_c'. Did you mean: '_A__get_b'?
继承、封装、多态
继承
单继承
class A:
def __init__(self):
print("A __init__")
self.func()
def func(self):
print("A func")
class B(A):
def func(self):
print("B func")
b = B() # 显然使用继承的__init__方法,调用的自己的func
# A __init__
# B func
print(b.__dict__)
print(B.__dict__)
print(A.__dict__)
多继承
多继承经典情形,菱形继承
class A:
def __init__(self):
print("A __init__")
def func(self):
print("A func")
class B(A):
def func(self):
print("B func")
class C(A):
def func(self):
print("C func")
class D(B, C):
pass
D().func()
# A __init__
# B func
实例化链
某个类实例化对象时,在初始化该类中的对象属性之前应当先初始化父类定义的对象属性,否则可能出现调用继承自父类的方法时,由于父类对象属性未初始化导致方法调用异常。
初始化对象时,会调用__init__初始化方法使用,在该方法初始化对象属性之前,使用super来调用父类的初始化方法方法,同理,初始化父类对象属性之前,也会先调用父类的父类的初始化方法,依据继承关系,初始化方法会逐层调用直到调用顶层父类初始化方法执行完毕,随后次一层执行完毕,同理逐层执行完毕返回,直到再次回到这个类的初始化方法并执行完毕,此时整个初始化过程结束。
实例化链在C++、Java都有所体现,不同是的,Python中允许中断初始化链。
class A:
def __init__(self, a):
self.a = a
def get_a(self):
print(self.a)
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
class C(A):
def __init__(self, c):
self.c = c
B(1, 2).get_a() # 1
C(1).get_a() # AttributeError: 'C' object has no attribute 'a'
方法解析顺序,mro
Python中使用C3算法确定方法解析顺序,访问类的__mro__属性可以获得线性序列。
class O:
def func(self):
s = "O"
print(s + "()")
return s
class A(O):
def func(self):
s = "A"
print(s + "(" + super().func() + ")" + " (注意此处,D非A父类)")
return s
class B(O):
def func(self):
s = "B"
print(s + "(" + super().func() + ")")
return s
class C(A):
def func(self):
s = "C"
print(s + "(" + super().func() + ")")
return s
class D(B):
def func(self):
s = "D"
print(s + "(" + super().func() + ")")
return s
class E(C, D):
def func(self):
s = "E"
print(s + "(" + super().func() + ")")
return s
e = E()
e.func()
# O()
# B(O)
# D(B)
# A(D) (注意此处,D非A父类)
# C(A)
# E(C)
print(E.__mro__)
# (<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.O'>, <class 'object'>)
print(E.__base__)
# <class '__main__.C'>
print(E.__bases__)
# (<class '__main__.C'>, <class '__main__.D'>)
print(issubclass(A, O)) # True 是否子类
print(issubclass(A, D)) # False
print(issubclass(E, A)) # True
print(isinstance(e, E)) # True 是否实例
print(isinstance(e, C)) # True
# https://zhuanlan.zhihu.com/p/151856162
# https://zh.wikipedia.org/wiki/C3%E7%BA%BF%E6%80%A7%E5%8C%96
封装
property- 访问权限控制
多态
python没有向上转型,没有重载,不过调用方法时还是要根据mro搜索实际调用的方法。
抽象类
- 抽象类使用
ABCMeta元类生成。 - 抽象方法使用
abstractmethod装饰器修饰。 - 抽象类不能实例化。
- 继承抽象类的一般类需要实现抽象方法。
- 继承抽象类的抽象类无需实现抽象方法。
from abc import abstractmethod, ABCMeta
# Abstract Base Class
class A(metaclass=ABCMeta):
@abstractmethod
def func_1(self):
pass
class A_1(A):
def func_1(self):
pass
a = A_1()
# 不使用ABCMeta而单独使用abstractmethods
object
顶层父类,Python3中所有类默认继承object,Python2中可选择性继承object,不继承object的类称之为经典类,继承object的类称之为新式类,Python3中一切类均为新式类。
class object:
"""
The base class of the class hierarchy.
When called, it accepts no arguments and returns a new featureless
instance that has no instance attributes and cannot be given any.
"""
def __delattr__(self, *args, **kwargs): # real signature unknown
""" Implement delattr(self, name). """
pass
def __dir__(self, *args, **kwargs): # real signature unknown
""" Default dir() implementation. """
pass
def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass
def __format__(self, *args, **kwargs): # real signature unknown
""" Default object formatter. """
pass
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __getstate__(self, *args, **kwargs): # real signature unknown
""" Helper for pickle. """
pass
def __ge__(self, *args, **kwargs): # real signature unknown
""" Return self>=value. """
pass
def __gt__(self, *args, **kwargs): # real signature unknown
""" Return self>value. """
pass
def __hash__(self, *args, **kwargs): # real signature unknown
""" Return hash(self). """
pass
def __init_subclass__(self, *args, **kwargs): # real signature unknown
"""
This method is called when a class is subclassed.
The default implementation does nothing. It may be
overridden to extend subclasses.
"""
pass
def __init__(self): # known special case of object.__init__
""" Initialize self. See help(type(self)) for accurate signature. """
pass
def __le__(self, *args, **kwargs): # real signature unknown
""" Return self<=value. """
pass
def __lt__(self, *args, **kwargs): # real signature unknown
""" Return self<value. """
pass
@staticmethod # known case of __new__
def __new__(cls, *more): # known special case of object.__new__
""" Create and return a new object. See help(type) for accurate signature. """
pass
def __ne__(self, *args, **kwargs): # real signature unknown
""" Return self!=value. """
pass
def __reduce_ex__(self, *args, **kwargs): # real signature unknown
""" Helper for pickle. """
pass
def __reduce__(self, *args, **kwargs): # real signature unknown
""" Helper for pickle. """
pass
def __repr__(self, *args, **kwargs): # real signature unknown
""" Return repr(self). """
pass
def __setattr__(self, *args, **kwargs): # real signature unknown
""" Implement setattr(self, name, value). """
pass
def __sizeof__(self, *args, **kwargs): # real signature unknown
""" Size of object in memory, in bytes. """
pass
def __str__(self, *args, **kwargs): # real signature unknown
""" Return str(self). """
pass
@classmethod # known case
def __subclasshook__(cls, subclass): # known special case of object.__subclasshook__
"""
Abstract classes can override this to customize issubclass().
This is invoked early on by abc.ABCMeta.__subclasscheck__().
It should return True, False or NotImplemented. If it returns
NotImplemented, the normal algorithm is used. Otherwise, it
overrides the normal algorithm (and the outcome is cached).
"""
pass
__class__ = None # (!) forward: type, real value is "<class 'type'>"
__dict__ = {}
__doc__ = ''
__module__ = ''
魔术方法,magic method
魔术方法指内置的名称中带有上下划线前后缀的方法,又称双下方法,例如__init__。
__new__,构造方法,用于通过类创建实例
__init__,初始化方法,实例被创建后调用
__del__,终结方法,使用del将对象引用减一,引用计数为0时终结方法会被调用
class A:
def __init__(self, a):
print("__init__")
self.a = a
def __new__(cls, *args, **kwargs):
print("__new__")
return super().__new__(cls)
def __del__(self):
print("__del__")
o = A(1)
print(o.a)
del o
# __new__
# __init__
# 1
# __del__
__repr__,representation,返回一个对象的正式字符串表示,通常是便于解释器理解的,在适当环境下可以基于该字符串重建对象,若不可则返回一个 <...some useful description...>结构的表示信息。在调用内置函数repr时调用。
__str__,string,返回一个对象非正式的字符串表示,通常是便于人理解的。在调用内置函数str、print、format时调用,若没有__str__实现,会调用__repr__。
s = "yuzao"
print(str(s)) # yuzao
print(repr(s)) # 'yuzao'
print(eval(repr(s))) # yuzao eval重建对象
d = [1, '2', [3]]
print(str(d)) # [1, '2', [3]]
print(repr(d)) # [1, '2', [3]]
class C:
def __init__(self, c):
self.c = c
c = C(1)
print(repr(c)) # <__main__.C object at 0x000001DB21E33C50>
print(repr("12\t12\n12")) # '12\t12\n12'
print("12\t12\n12")
# 12 12
#12
class A:
def __str__(self):
return "__str__"
def __repr__(self):
return "__repr__"
print("%s" % A()) # __str__
print("%r" % A()) # __repr__
__bytes__,返回一个bytes对象。
__format__,返回一个对象的格式化字符串,在调用内置函数format、f-string和str.format()方法时调用。
比较方法
使用关系运算符比较两个对象时会调用对应方法。
__lt__,小于
__le__,小于等于
__eq__,等于
__ne__,不等于
__gt__,大于
__ge__,大于等于
__getattribute__获取属性、__getattr__获取属性、__setattr__设置属性、__delattr__删除属性
四者为obj.key形式的操作提供底层实现
class A:
def __getattribute__(self, item):
print("__getattribute__")
return super().__getattribute__(item)
def __getattr__(self, item):
print("__getattr__")
return super().__getattr__(item)
def __setattr__(self, key, value):
print("__setattr__")
super().__setattr__(key, value)
def __delattr__(self, item):
print("__delattr__")
super().__delattr__(item)
f = A()
# f.name
f.name = '张三' # __setattr__
f.name # __getattribute__
del f.name # __delattr__
__getitem__获取元素、__setitem__设置元素、__delitem__删除元素
三者为obj[key]形式的操作提供底层实现
# __getitem__ __setitem__ __delitem__
class A(dict):
def __getitem__(self, item):
print("__getitem__")
return super().__getitem__(item)
def __setitem__(self, key, value):
print("__setitem__")
super().__setitem__(key, value)
def __delitem__(self, key):
print("__delitem__")
super().__delitem__(key)
a = A()
a['name'] # __getitem__
a['name'] = '李四' # __setitem__
del a['name'] # __delitem__
__eq__、__hash__
==会调用__eq__is实质是id值的比较- 内置函数
hash会调用__hash__
class A:
def __init__(self, c):
self.c = c
def __eq__(self, other):
print("__eq__")
return self.c == other.c
def __hash__(self):
print("__hash__")
return self.c.__hash__()
a1, a2 = A(1), A(1)
print(a1 == a2) # __eq__ True
print(a1 is a2, id(a1) == id(a2)) # False False
print(a1.__eq__(a2)) # __eq__ True
print(hash(a1) == hash(a2)) # __hash__ __hash__ True
__call__、callable
class A:
def __init__(self):
self.name = "A"
def func(self):
print("func")
a = A()
print(hasattr(a.name, "__call__"), callable(a.name)) # False False
print(hasattr(a.func, "__call__"), callable(a.func)) # True True
print(callable(a.name), callable(A)) # False True
__dict__
一个用于存储对象的(可写)属性的字典或其他映射对象。 并非所有实例都具有 __dict__ 属性。
https://stackoverflow.com/questions/19907442/explain-dict-attribute
https://docs.python.org/zh-cn/3.13/reference/datamodel.html#object.__dict__
协议,protocol
PEP 544 – Protocols: Structural subtyping (static duck typing)
协议用于描述静态鸭子类型,约定有具有双下方法的类型即为同一类型。与Java中接口的概念类似。
迭代器协议
实现__iter__、__next__方法的类型即为迭代器。
上下文管理器协议
上下文管理协议,Context Management Protocol
__enter__()__exit__()
上下文管理器,Context Manager
上下文管理器是实现上下文管理协议的类。
上下文管理器要同时实现__enter__和__exit__
上下文管理器执行流程、with语句
with <experssion> [as var]:
<code_body>
其中expression是返回值为上下文管理器对象的表达式,var为一个变量,code_body为任意代码,整个执行流程如下:
- 执行
expression返回一个上下文管理器对象 - 调用上下文管理器的
__enter__方法,若使用as字句,将__enter__方法的返回值赋值给var - 执行
other_code - 无论执行过程中是否发生了异常,调用上下文管理器的
__exit__方法 __exit__通常用于释放资源等操作,若执行过程中没有出现异常,或者语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None),若执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)- 若
__exit__的返回值逻辑判断为True表示忽略可能的异常,若返回值逻辑判断为False,表示不忽略with语句code_body中可能出现的异常,会向外抛出,由外部代码处理异常,若没有发生异常且返回值逻辑判断是False,则异常抛无可抛代码会正常执行 __exit__的返回值不一定必须是True或False之一,若不显式指定__exit__返回值则默认返回None,逻辑判断为False,会尝试抛出可能的异常- 通常使用with语句来建立并自动维护上下文,也可通过手动调用
__enter__和__exit__的方式手动建立和维护上下文
一般形式上下文管理器
class DBManager(object):
def __init__(self):
print('__init__')
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
return False
with DBManager() as db_manager:
print(db_manager)
print('exec code')
基于装饰器的上下文管理器
将一个生成器函数转换为一个上下文管理器
import contextlib
import time
@contextlib.contextmanager
def timeit(x):
print(x)
print('__enter__')
start = time.time()
yield
print('__exit__')
end = time.time()
used_time = (end - start) * 1000
print('Use time %d ms' % used_time)
with timeit("A"):
time.sleep(1)
with timeit("B"):
time.sleep(2)
属性描述符
属性描述符,descriptor,属性描述符是实现了描述符协议的类,属性描述符至少实现一个描述符协议中的方法,使用方式是将描述符实例作为另一个类的类属性,将描述符实例作为另一个类的实例属性只会被当作一般类使用。
本质上,属性描述符用于将一个普通的类属性交由一个属性描述符类来控制,可以实现复杂的属性控制逻辑。
描述符协议
def __get__(self, instance, owner)def __set__(self, instance, value)def __delete__(self, instance)
描述符分类
数据描述符,data descriptor,__get__和__set__都定义的描述符
非数据描述符,non-data descriptor,定义__get__但不定义__set__的描述符
描述符原理
a.b会调用__getattrbute__获取属性
- 若有同名数据描述符,调用
__get__返回 - 若
__dict__中有同名属性,返回 - 若有同名非数据描述符,调用
__get__返回 - 若a所属的类有
__getattr__属性,调用返回 - 都没有抛出
AttributeError
import uuid
class Identity:
def __init__(self):
self.value = uuid.uuid4()
def __get__(self, instance, owner):
print(f"call __get__ {instance=}, {owner=}")
return self.value
class Age:
def __init__(self, value=0):
self.value = value
def __get__(self, instance, owner):
print(f"call __get__ {instance=}, {owner=}")
return self.value
def __set__(self, instance, value):
if value < 0:
raise ValueError("年龄必须为非负整数")
print(f"call __set__ {instance=}, {value=}")
self.value = value
class Person:
pid = Identity()
age = Age()
def __init__(self, name, age):
self.name = name
self.age = age
person1 = Person('张三', 18) # call __set__ instance=<__main__.Person object at 0x000001DE1E1D1E90>, value=18
person1.age # call __get__ instance=<__main__.Person object at 0x000001DE1E1D1E90>, owner=<class '__main__.Person'>
Person.age # call __get__ instance=None, owner=<class '__main__.Person'>
# person1.age = -1 # ValueError: 年龄必须为非负整数
person2 = Person('李四', 20)
print(person1.name, person1.age) # 张三 20
# 属性描述符实例age作为类属性会被所有实例共享
# 而本场景下所有Person的实例的age应当是不一样的
# 当前代码中还需要实现所有Person实例从同一个age属性描述符实例中获取到自己的age
描述符示例
- 类中方法是非数据描述符实例
class Person:
def __init__(self):
self.name = "A"
def name(self):
return "B"
print(Person().name) # A
print(dir(Person.name)) # [..., __get__, ...]
装饰器
property、staticmethod、classmethod是属性描述符绑定方法与非绑定方法,绑定方法就是一个对象(self或者cls)与方法进行绑定了,无需手动指定该参数
class FuncClass: def func1(self): pass @classmethod def func2(cls): pass @staticmethod def func3(): pass # 普通方法 print(FuncClass().func1) # <bound method FuncClass.func1 of <__main__.FuncClass object at 0x000001FA2448FF08>> print(FuncClass.func1) # <function FuncClass.func1 at 0x000001FA244E0D38> # 类方法 print(FuncClass().func2) # <bound method FuncClass.func2 of <class '__main__.FuncClass'>> print(FuncClass.func2) # <bound method FuncClass.func2 of <class '__main__.FuncClass'>> # 静态方法 print(FuncClass().func3) # <function FuncClass.func3 at 0x000001FA24707708> print(FuncClass.func3) # <function FuncClass.func3 at 0x000001FA24707708>Python并没有在语法上支持类方法和静态方法,而是通过装饰器和属性描述符两者实现类方法和静态方法,基本思路是:对一般方法使用装饰器,该装饰器是一个属性描述符,控制一般方法对外的访问逻辑,
classmethod装饰就控制该方法像类方法一样控制,staticmethod装饰就控制该方法像静态方法一样控制,property同理,Python中这三个装饰器由C语言实现,下面是Python实现,可以达到同样效果:import random class py_property: def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self.getter(self.fget) if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("unwritable attribute") return self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("unwritable attribute") return self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__) class py_staticmethod: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): return self.func class py_classmethod: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): _cls = objtype or type(obj) def wrapper(*args, **kwargs): return self.func(_cls, *args, **kwargs) return wrapper class FuncClass: def func1(self): return self @py_classmethod def func2(cls): return cls @py_staticmethod def func3(): pass @py_property def name(self): return str(random.Random().randint(1, 100)) # 普通方法 print(FuncClass().func1()) # <__main__.FuncClass object at 0x000001E88F58F708> # 类方法 print(FuncClass.func2()) # <class '__main__.FuncClass'> # 静态方法 print(FuncClass.func3()) # None # property print(FuncClass().name) # 1 print(FuncClass.name) # <property object at 0x000001E88F5AAC78>Django ORM 和 SQLAlchemy中的字段类便是属性描述符
代码示例
仿照SQLAlchemy
from abc import abstractmethod
class Value:
def __init__(self):
self.data = {}
def __get__(self, instance, owner):
return self.data[instance] # 区分实例的属性
def __set__(self, instance, value):
self.validate(value)
self.data[instance] = value
@abstractmethod
def validate(self, value):
pass
class Number(Value):
def __init__(self, min_value=None, max_value=None):
super(Number, self).__init__()
self.minvalue = min_value
self.maxvalue = max_value
def validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError(f'Expected {value!r} to be an int or float value')
if value < self.minvalue:
raise ValueError(f'Expected {value!r} not to be less than {self.minvalue!r}')
if value > self.maxvalue:
raise ValueError(f'Expected {value!r} not to be greater than {self.maxvalue!r}')
class String(Value):
def __init__(self, min_size=None, max_size=None):
super(String, self).__init__()
self.minsize = min_size
self.maxsize = max_size
def validate(self, value):
if not isinstance(value, str):
raise TypeError(f'Expected {value!r} to be a str')
if len(value) < self.minsize:
raise ValueError(f'Expected length of {value!r} not to be less than {self.minsize!r}')
if len(value) > self.maxsize:
raise ValueError(f'Expected length of {value!r} not to be grater than {self.maxsize!r}')
class Person:
name = String(min_size=1, max_size=10)
age = Number(min_value=1, max_value=120)
def __init__(self, name, age):
self.name = name
self.age = age
person1 = Person('张三', 18)
print(person1.name, person1.age) # 张三 18
person2 = Person('李四', 20)
print(person2.name, person2.age) # 李四 20
person3 = Person('王五', 121) # ValueError: Expected 121 not to be greater than 120
元类
https://blog.csdn.net/a2011480169/article/details/87891753
元类用于生成类对象,类本身是一种对象,而元类就是这种对象对应的类,Python 中继承type的都是元类,可以继承type来自定义元类。
元类可以控制类的创建和类的实例化行为,是通过__call__、__new__、__init__实现的。
元类对类创建的控制:类作为元类实例化的对象,类的创建必然要调用元类的__new__、和__init__方法,这样类创建可以在元类中控制。
元类对类的实例化的控制:若元类为A、类为B,则有B = A(),那么类的实例化就是A()(),这是就会调用元类的__call__方法,而元类的__call__方法,会负责调用传进来的类的__new__和__init__方法完成实例化,倘若改写元类__call__方法,则可以控制类实例化行为,例如实现单例模式。