0x01 Python是如何进行内存管理的
-
引用计数
- Python使用引用计数来保持追踪内存中的对象,所有对象都有引用计数
- 引用计数增加的情况:一个对象分配一个新名称;将其放入一个容器中(
list
tuple
dict
) - 引用计数减少的情况:使用
del
语句对对象别名进行显式销毁;引用超出作用域或被重新赋值 -
sys.getrefcount()
函数可以获取对象当前的引用计数 - 一般情况下,引用计数比你想象的要多很多
- 对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。
-
垃圾回收
- 当一个对象的引用计数为
0
时,该对象将被垃圾回收机制处理掉 - 标记清除:解释器定时执行一个循环检测器,对不可访问对象进行清除。(产生不可访问对象的原因是因为
a
、b
两个对象就行了相互引用,导致引用计数无法为0
,产生了内存泄漏)
- 当一个对象的引用计数为
-
内存池
- 上面提到的垃圾回收,是回收到
Python
的内存池中,而不是返回给了操作系统 - pymalloc机制,为了加快
python
的执行效率,对于小块内存的申请与释放使用内存池 -
python
中所有小于256B
的对象都使用pymalloc
实现的分配器分配内存,大的对象使用系统的malloc
- 对于
Python
对象(int
float
list
etc...
),都有其独立的私有内存池。即分配又释放了一个整数对象,则被分配的这部分内存不会被再分配给其他类型对象
- 上面提到的垃圾回收,是回收到
0x02 Lambda函数
-
lambda
表达式,匿名函数 - 优点:简单 快捷
0x03 赋值 浅拷贝 深拷贝
-
赋值时,整形、字符串等类型对象将值直接赋值给变量,而
list
、dict
等是将对象的地址赋值给变量 -
赋值
- 创建了对象的一个新引用,修改一个,另一个也会改变,也就是说两个变量指向同一个内存地址
-
浅拷贝
- 产生一个新的对象,它包含原始对象的包含项的引用,所以包含项内对象变更,则新产生的对象的包含项也会变更
- 完全切片方法; 工厂函数,如
list()
;copy
模块的copy()
函数
-
深拷贝
- 不止拷贝原始对象,原始对象内部的所有对象都会进行地递归拷贝,产生新的对象。
- 深拷贝的两个对象没有任何关联
-
copy
模块的deepcopy()
函数
0x04 pass语句作用
- 不会执行任何操作,作为占位符
0x05 range() & xrange()
-
range
和xrange
参数相同,第一个参数是起始值,第二个参数是结束值(闭区间),第三个参数是步长 -
range
函数产生一个list
,xrange
函数产生一个xrange
对象,占用内存是一定的,不会讲所有值存储到内存中
0x06 如何用Python来进行查询和替换一个文本字符串
-
re
模块,sub()
第一个参数是替换的字符串,第二个参数是被替换字符串,第三个参数是替换的次数 -
subn()
函数参数相同,返回值不同
0x07 单引号 双引号 三引号的区别
- 单引号和双引号等效,换行需要使用反斜杠(
\
) - 三引号则可以直接换行,并且可以包含注释
0x08 Python的函数参数传递
- 所有的变量都可以理解是内存中一个对象的“引用”
- 类型是属于对象的,而不是变量的
- 对象有两种:“可更改”(
mutable
)与“不可更改”(immutable
),string
、tuples
、number
是不可更改对象,而list
、dict
等则是可更改对象 - 函数参数传递的是引用,内部参数是外部参数的一个引用拷贝,但是引用是值传递的
def try_to_change_list_reference(the_list):
print('got', the_list)
the_list = ['and', 'we', 'can', 'not', 'lie']
print('set to', the_list)
outer_list = ['we', 'like', 'proper', 'English']
print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)
# Output:
before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']
- 如果想改变不可变对象的值,可以这样做,代码如下
## 1
def return_a_whole_new_string(the_string):
new_string = something_to_do_with_the_old_string(the_string)
return new_string
# then you could call it like
my_string = return_a_whole_new_string(my_string)
## 2
def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
new_string = something_to_do_with_the_old_string(stuff_to_change[0])
stuff_to_change[0] = new_string
# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)
do_something_with(wrapper[0])
0x09 Python自省
- 面向对象语言编写的程序,在运行时,可以获取到对象的类型,即运行时可以获取到对象的类型
- 自省函数:
type()
、isinstance()
、dir()
、hasattr()
、getattr()
0x09 多重继承
- 多重继承时,使用
super
方法调用父类方法时,默认是调用第一个父类,后边父类需要手动类名加方法名方式调用
0x0a staticmethod & classmethod
- 经常有一些跟类有关系的功能,但是在运行时又不需要实例和类的参与,在这个时候就需要使用静态方法(
staticmethod
)。比如更改环境变量或者修改其他类之外的东西,就需要使用静态方法,虽然这种情况可以直接使用类外的函数来解决,但是这个破坏了类的封装性,使代码难以维护。 - 只在类中运行,而不再实例中运行的方法,就是类方法(
classmethod
)。也可以在类外定义一个功能相同的函数来替代类方法,但是这个破坏了类的封装性。不管是从实例调用还是从类调用类方法,它的第一个参数都是类。 stackoverflow
最佳答案
0x0b 类变量和实例变量
- 类变量和实例变量是两个不同的作用域,在两个空间内
class Person:
name="aaa"
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name # bbb
print p2.name # aaa
print Person.name # aaa
##
class Person:
name=[]
p1=Person()
p2=Person()
p1.name.append(1)
print p1.name # [1]
print p2.name # [1]
print Person.name # [1]
0x0c 单下划线和双下划线
-
__foo__
:一种约定,Python
内部的名字,用来区别其他用户自定义的命名,以防冲突。 -
_foo
:一种约定,用来指定变量私有,其实python
解析器没有真正的对其进行私有,只是用来提示程序员此变量是私有变量。 -
__foo
:这个是间接的实现私有,解析器用_classname__foo
来代替这个名字,以区别和其他类相同的命名。注意:继承的时候双下划线属性是不继承的,因为它会被重命名为_classname__foo
。
0x0d *args and **kwargs
- 用
*args
和**kwargs
只是为了方便并没有强制使用它们 - 当你不确定你的函数里将要传递多少参数时你可以用
*args
- 相似的,
**kwargs
允许你使用没有事先定义的参数名 - 也可以混着用,命名参数首先获得参数值然后所有的其他参数都传递给
*args
和**kwargs
,命名参数在列表的最前端,*args
必须在**kwargs
前面 - 调用函数时你也可以用
*
和**
语法,*
表示list
等,**
表示dict
0x0e 面向切面编程AOP
- 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程,概念详解。
- 装饰器就是一种
AOP
编程思想
0x0f 鸭子类型
- “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
- 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
- 在
python
中,有很多file-like
的东西,比如StringIO
、GzipFile
、socket
。它们有很多相同的方法,我们把它们当作文件使用。 - 比如
list.extend()
方法中,我们并不关心它的参数是不是list
,只要它是可迭代的,所以它的参数可以是list
/tuple
/dict
/字符串
/生成器
等 - 鸭子类型在动态语言中经常使用,非常灵活,使得
python
不像java
那样专门去弄一大堆的设计模式。
0x10 Python中函数重载
- 函数重载主要是为了解决两个问题:可变的参数类型、可变的参数个数
- 参数类型不同的问题在
python
中是不存在的,因为python
的函数参数不需要限定类型,可以在函数内部使用自省判断类型,从而实现不同的操作 - 参数个数可变的问题,
python
中使用缺省参数来实现,*args
&**kwargs
0x11 __new__和__init__的区别
-
__new__
是一个staticmethod
,在创建新实例对象时调用的,返回一个创建的实例 -
__init__
是一个实例方法
,只有在__new__
返回一个cls
的实例时后面的__init__
才能被调用,在创建实例对象后调用的,进行实例初始化,什么都不返回 -
__metaclass__
是创建类时起作用,所以我们可以分别使用__metaclass__
、__new__
、__init__
来分别在类创建,实例创建和实例初始化的时候做一些小手脚
0x12 python实现单例模式
-
__new__
方法实现 - 共享属性
- 装饰器实现
- 单独模块导入
0x13 Python中的作用域
- 一个变量的作用域总是由在代码中被赋值的地方所决定的。
-
python
搜索变量的顺序:本地作用域(Local
)→当前作用域被嵌入的本地作用域(Enclosing locals
)→全局/模块作用域(Global
)→内置作用域(Built-in
)
0x14 闭包
- 闭包(
closure
)是函数式编程的重要的语法结构。 - 闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
- 当一个内嵌函数引用其外部作用域的变量,我们就会得到一个闭包
- 创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
- 重点是函数运行后并不会被撤销,而是继续留在内存空间里,这个功能类似类里的类变量,只不过迁移到了函数上
- 闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样
0x15 Python函数式编程
-
filter
函数的功能相当于过滤器。调用一个布尔函数bool_func
来迭代遍历每个seq
中的元素;返回一个使bool_seq
返回值为true
的元素的序列。
>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]
-
map
函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2
:
>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]
-
reduce
函数是对一个序列的每个项迭代调用函数,下面是求3
的阶乘:
>>> reduce(lambda x,y:x*y,range(1,4))
6
0x16 Python垃圾回收机制详解
-
Python GC
主要使用引用计数(reference counting
)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep
)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection
)以空间换时间的方法提高垃圾回收效率。 -
引用计数
-
PyObject
是每个对象必有的内容,其中ob_refcnt
就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就会减少.引用计数为0
时,该对象生命就结束了。 - 优点:简单、实时性;缺点:维护引用计数消耗资源、循环引用
-
-
标记-清除机制
- 基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
-
分代技术
- 分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
-
Python
默认定义了三代对象集合,索引数越大,对象存活时间越长
举例:
当某些内存块M
经过了3
次垃圾收集的清洗之后还存活时,我们就将内存块M
划到一个集合A
中去,而新分配的内存都划分到集合B
中去。当垃圾收集开始工作时,大多数情况都只对集合B
进行垃圾回收,而对集合A
进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B
中的某些内存块由于存活时间长而会被转移到集合A
中,当然,集合A
中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
0x17 GIL线程全局锁
- 线程全局锁(
Global Interpreter Lock
),即Python
为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程。 - 解决办法就是多进程和协程(协程也只是单
CPU
,但是能减小切换代价提升性能)
0x18 协程
- 协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态。
-
yield
就是使用协程的思想
0x19 Python的is
-
is
是对比地址,==
是对比值
0x1a read readline readlines
-
read
读取整个文件,当做一个字符串 -
readline
读取下一行,使用生成器特性,占用内存少 -
readlines
将整个文件读取到内存中,返回一个列表,每一行是一个元素
0x1b __all__的作用
- 提供模块对外的接口,不在
__all__
列表中的对象,在外部无法进行调用
0x1c 装饰器在加载模块的时候就会展开
0xfc 新式类和旧式类
- 新式类在2.2版本就出现了,所以旧式类完全是为了兼容性的问题,Python3里的类全部都是新式类
- 详细参考
0xfd 迭代器和生成器
Iterables
- 当你创建了一个列表,你可以一个一个的读取它的每一项,这叫做迭代(
iteration
),整个遍历列表的过程就叫做迭代 - 可以使用
for...in
迭代(iteration
)的就可以说它是可迭代的(Iterable
)
Iterator
- 可以被
next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
- 可以使用
isinstance()
判断一个对象是否是Iterator
对象 -
list
、dict
、str
虽然是Iterable
,却不是Iterator
,可以使用iter()
函数变成Iterator
-
Python
的Iterator
对象表示的是一个数据流,Iterator
对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。 -
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list
是永远不可能存储全体自然数的。
Generators
- 生成器(
generator
)是迭代器(iterator
)的一种,但是你只能迭代它们一次,因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成。 -
generator
可以参考列表生成式,但是需要 将[ ]
替换成( )
Yield
-
Yield
的用法和关键字return
类似 - 当你调用函数的时候,函数里的代码并没有运行,函数仅仅返回生成器对象,每当
for
语句迭代生成器的时候你的代码才会运转,一旦函数运行并没有碰到yeild
语句就认为生成器已经为空了
Itertools
-
itertools
模块包含了一些特殊的函数可以操作可迭代对象
理解迭代的内部机制
- 迭代是可迭代对象(对应
__iter__()
方法)和迭代器(对应__next__()
方法)的一个过程 - 可迭代对象就是任何你可以迭代的对象,可以使用
for...in
的对象 - 迭代器就是可以让你迭代可迭代对象的对象,可以被
next()
函数调用的对象
0xfe Python2和3的区别
0xff Python中的元类(metaclass)
- 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在
Python
中这一点仍然成立 - 但是,
Python
中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class
,Python
解释器在执行的时候就会创建一个对象 - 这个对象(
类
)自身拥有创建对象(类实例
)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象。
>>> print ObjectCreator # 你可以打印一个类,因为它其实也是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
… print o
…
>>> echo(ObjectCreator) # 你可以将类做为参数传给函数
<class '__main__.ObjectCreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' # 你可以为类增加属性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
>>> print ObjectCreator # 你可以打印一个类,因为它其实也是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
… print o
…
>>> echo(ObjectCreator) # 你可以将类做为参数传给函数
<class '__main__.ObjectCreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' # 你可以为类增加属性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
- 动态地创建类
- 因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用
class
关键字即可。 - 但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用
class
关键字时,Python
解释器自动创建这个对象。但就和Python
中的大多数事情一样,Python
仍然提供给你手动处理的方法。还记得内建函数type
吗?这个古老但强大的函数能够让你知道一个对象的类型是什么 - 这里,
type
有一种完全不同的能力,它也能动态的创建类。type
可以接受一个类的描述作为参数,然后返回一个类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python
中是为了保持向后兼容性)type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
- 因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用
- 元类
- 元类就是用来创建类的“东西”
- 元类就是用来创建这些类(对象)的,元类就是类的类
- 函数
type
实际上是一个元类。type
就是Python
在背后用来创建所有类的元类 -
str
是用来创建字符串对象的类,而int
是用来创建整数对象的类,type
就是创建类对象的类。 - 可以通过检查
__class__
属性,来查看对象属于哪个类 - 元类就是创建类这种对象的东西。如果你喜欢的话,可以把元类称为“类工厂”(不要和工厂类搞混了)
type
就是Python
的内建元类,当然了,你也可以创建自己的元类。
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>>foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
>>> a.__class__.__class__
<type 'type'>
>>> age.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
-
__metaclass__
属性- 在写一个类的时候为其添加
__metaclass__
属性,Python
就会用元类来创建该类 - 写下
class Foo(object)
,但是类对象Foo
还没有在内存中创建。Python
会在类的定义中寻找__metaclass__
属性,如果找到了,Python
就会用它来创建类Foo
,如果没有找到,就会用内建的type
来创建这个类 - 对于具有继承关系的类,查找流程是这样的(
class Foo(Bar)
):Python
会在内存中通过__metaclass__
创建一个名字为Foo
的类对象(我说的是类对象,请紧跟我的思路)。如果Python
没有找到__metaclass__
,它会继续在Bar
(父类)中寻找__metaclass__
属性,并尝试做和前面同样的操作。如果Python
在任何父类中都找不到__metaclass__
,它就会在模块层次中去寻找__metaclass__
,并尝试做同样的操作。如果还是找不到__metaclass__
,Python
就会用内置的type
来创建这个类对象。
- 在写一个类的时候为其添加
-
自定义元类
- 赋给
__metaclass__
属性的值是什么? 可以创建一个类的东西。type
,或者任何使用到type
或者子类化type
的东西都可以。 - 使用函数实现元类:
- 赋给
# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items()
if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # 这会作用到这个模块中的所有类
class Foo(object):
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'
- 使用类实现元类:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
- 使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。
- 元类本身而言,其实是很简单的:
拦截类的创建
、修改类
、返回修改之后的类
- 元类本身而言,其实是很简单的:
-
为什么建议使用类来实现元类而不是函数?
- 意图会更加清晰。当你读到
UpperAttrMetaclass(type)
时,你知道接下来要发生什么。 - 你可以使用
OOP
编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。 - 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
- 你可以使用
__new__
,__init__
以及__call__
这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__
里处理掉,有些人还是觉得用__init__
更舒服些。
- 意图会更加清晰。当你读到
-
那么究竟为什么要使用元类?
- 元类的主要用途是创建
API
。一个典型的例子是Django ORM
。它允许你像这样定义
- 元类的主要用途是创建
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
# 如果你按下面的方式做,并不会返回一个IntegerField对象,而是会返回一
# 个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model
# 定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person
# 类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过
# 暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后
# 完成真正的工作。
guy = Person(name='bob', age='35')
print guy.age
-
Python
中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type
-
-
type
实际上是它自己的元类,在纯Python
环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的
-
- 元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
Monkey patching
和class decorators
- 元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
- 当你需要动态修改类时,
99%
的时间里你最好使用上面这两种技术。当然了,其实在99%
的时间里你根本就不需要动态修改类
- 当你需要动态修改类时,
欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。
网友评论