一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
简历模板
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
7. >>> print Foo(‘ethan’) # 使用 print
5.3 iter
5.4 getitem
5.5 getattr
5.6 call
5.7 小结
6 slots 魔法
7 使用 @property
8 你不知道的 super
8.1 深入 super()
8.2 MRO 列表
8.3 super 原理
8.4 陌生的 metaclass
8.5 熟悉又陌生的 type
8.6 最简单的情况
8.7 继承的情况
9 元类
9.1 概念
9.2 元类的使用
9.3 小结
1 概述
Python 是一门面向对象编程( Object Oriented Programming, OOP )的语言,这里的对象可以。
看做是由数据(或者说特性)以及一系列可以存取、操作这些数据的方法所组成的集合。面向对象编程。
主要有以下特点:
多态( Polymorphism ):不同类( Class )的对象对同一消息会做出不同的响应。
封装( Encapsulation ):对外部世界隐藏对象的工作细节。
继承( Inheritance ):以已有的类(父类)为基础建立专门的类对象。
在 Python 中,元组、列表和字典等数据类型是对象,函数也是对象。那么,我们能创建自己的对象吗?答案是肯定的。跟其他 OOP 语言类似,我们使用类来自定义对象。
本章主要介绍以下几个方面:
2 类和实例
2.1 基本概念
类是一个抽象的概念,我们可以把它理解为具有相同属性和方法的一组对象的集合,而实例则是一个具体的对象。类和对象的关系就如同模具和用这个模具制作出的物品之间的关系。一个类为它的全部对象给出了一个统一的定义,而他的每个对象则是符合这种定义的一个实体,因此类和对象的关系就是抽象和具体的关系。
我们还是先来看看在 Python 中怎么定义一个类。
这里以动物( Animal )类为例, Python 提供关键字 class 来声明一个类:
1. class Animal ( object ):
2. pass
其中, Animal 是类名,通常类名的首字母采用大写(如果有多个单词,则每个单词的首字母大
写),后面紧跟着 (object) ,表示该类是从哪个类继承而来的,所有类最终都会继承自 object 类。
类定义好了,接下来我们就可以创建实例了:
1. >>> animal = Animal () # 创建一个实例对象
2. >>> animal
3. < __main__ . Animal at 0x1030a44d0 >
我们在创建实例的时候,还可以传入一些参数,以初始化对象的属性,为此,我们需要添加一个
__init__ 方法:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . name = name
然后,在创建实例的时候,传入参数:
1. >>> animal = Aniaml ( ‘dog1’ ) # 传入参数 ‘dog1’
2. >>> animal . name # 访问对象的 name 属性
3. ‘dog1’
我们可以把 __init__ 理解为对象的初始化方法,它的第一个参数永远是 self ,指向创建的
实例本身。定义了 __init__ 方法,我们在创建实例的时候,就需要传入与 __init__ 方法匹
配的参数。
接下来,我们再来添加一个方法:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def greet ( self ):
5. print ‘Hello, I am %s.’ % self . name
我们添加了方法 greet ,看看下面的使用:
1. >>> dog1 = Animal ( ‘dog1’ )
2. >>> dog1 . name
3. ‘dog1’
4. >>> dog1 . greet ()
5. Hello , I am dog1 .
现在,让我们做一下总结 。我们在 Animal 类定义了两个方法: __init__ 和 greet 。 __init__ 是 Python 中的特殊方法( special method ),它用于对对象进行初始化,类似于 C++ 中的构造函数; greet 是我们自定义的方法。
注意到 ,我们在上面定义的两个方法有一个共同点,就是它们的第一个参数都是 self ,指向实例
本身,也就是说它们是和实例绑定的函数, 这也是我们称它们为方法而不是函数的原因 。
2.2 访问限制
在某些情况下,我们希望限制用户访问对象的属性或方法,也就是希望它是私有的,对外隐蔽。比如,对于上面的例子,我们希望 name 属性在外部不能被访问,我们可以在属性或方法的名称前面加上两个下划线,即 __ ,对上面的例子做一点改动:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . __name = name
4. def greet ( self ):
5.
print ‘Hello, I am %s.’ % self . __name
1. >>> dog1 = Animal ( ‘dog1’ )
2. >>> dog1 . __name # 访问不了
3. ---------------------------------------------------------------------------
4. AttributeError Traceback ( most recent call last )
5. < ipython - input - 206 - 7f6730db631e > in < module >()
6. ----> 1 dog1 . __name
8. AttributeError : ‘Animal’ object has no attribute ‘__name’
9. >>> dog1 . greet () # 可以访问
10. Hello , I am dog1 .
可以看到,加了 __ 的 __name 是不能访问的,而原来的 greet 仍可以正常访问。
需要注意的是,在 Python 中,以双下划线开头,并且以双下划线结尾(即 __xxx__ )的变量是
特殊变量,特殊变量是可以直接访问的。所以,不要用 __name__ 这样的变量名。
另外,如果变量名前面只有一个下划线 _ ,表示不要随意访问这个变量,虽然它可以直接被访问。
2.3 获取对象信息
当我们拿到一个对象时,我们往往会考察它的类型和方法等,比如:
1. >>> a = 123
2. >>> type ( a )
3. int
4. >>> b = ‘123’
5. >>> type ( b )
6. str
当我们拿到一个类的对象时,我们用什么去考察它呢?回到前面的例子:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def greet ( self ):
5. print ‘Hello, I am %s.’ % self . name
第 1 招 :使用 type
使用 type(obj) 来获取对象的相应类型:
1. >>> dog1 = Animal ( ‘dog1’ )
2. >>> type ( dog1 )
3. __main__ . Animal
第 2 招 :使用 isinstance
使用 isinstance(obj, type) 判断对象是否为指定的 type 类型的实例:
1. >>> isinstance ( dog1 , Animal )
2. True
第 3 招 :使用 hasattr/getattr/setattr
使用 hasattr(obj, attr) 判断对象是否具有指定属性 / 方法;
使用 getattr(obj, attr[, default]) 获取属性 / 方法的值 , 要是没有对应的属性则返
回 default 值(前提是设置了 default ),否则会抛出 AttributeError 异常;
使用 setattr(obj, attr, value) 设定该属性 / 方法的值,类似于
obj.attr=value ;
看下面例子:
1. >>> hasattr ( dog1 , ‘name’ )
2. True
3. >>> hasattr ( dog1 , ‘x’ )
4. False
5. >>> hasattr ( dog1 , ‘greet’ )
6. True
7. >>> getattr ( dog1 , ‘name’ )
8. ‘dog1’
9. >>> getattr ( dog1 , ‘greet’ )
10. < bound method Animal . greet of < __main__ . Animal object at 0x10c3564d0 >>
11. >>> getattr ( dog1 , ‘x’ )
12. ---------------------------------------------------------------------------
13. AttributeError Traceback ( most recent call last )
14. < ipython - input - 241 - 42f5b7da1012 > in < module >()
15. ----> 1 getattr ( dog1 , ‘x’ )
17. AttributeError : ‘Animal’ object has no attribute ‘x’
18. >>> getattr ( dog1 , ‘x’ , ‘xvalue’ )
19. ‘xvalue’
20. >>> setattr ( dog1 , ‘age’ , 12 )
21. >>> dog1 . age
22. 12
第 4 招 :使用 dir
使用 dir(obj) 可以获取相应对象的所有属性和方法名的列表:
1. >>> dir ( dog1 )
2. [ ‘__class__’ ,
3. ‘__delattr__’ ,
4. ‘__dict__’ ,
5. ‘__doc__’ ,
6. ‘__format__’ ,
7. ‘__getattribute__’ ,
8. ‘__hash__’ ,
9. ‘__init__’ ,
10. ‘__module__’ ,
11. ‘__new__’ ,
12. ‘__reduce__’ ,
13. ‘__reduce_ex__’ ,
14. ‘__repr__’ ,
15. ‘__setattr__’ ,
16. ‘__sizeof__’ ,
17. ‘__str__’ ,
18. ‘__subclasshook__’ ,
19. ‘__weakref__’ ,
20. ‘age’ ,
21. ‘greet’ ,
22. ‘name’ ]
2.4 小结
(1)类是具有相同属性和方法的一组对象的集合,实例是一个具体的对象。
(2)方法是与实例绑定的函数。
(3)获取对象信息可使用下面方法:
type(obj) :来获取对象的相应类型;
isinstance(obj, type) :判断对象是否为指定的 type 类型的实例;
hasattr(obj, attr) :判断对象是否具有指定属性 / 方法;
getattr(obj, attr[, default]) 获取属性 / 方法的值 , 要是没有对应的属性则返回
default 值(前提是设置了 default ),否则会抛出 AttributeError 异常;
setattr(obj, attr, value) :设定该属性 / 方法的值,类似于 obj.attr=value ;
dir(obj) :可以获取相应对象的所有属性和方法名的列表;
3 继承和多态
3.1 继承
在面向对象编程中,当我们已经创建了一个类,而又想再创建一个与之相似的类,比如添加几个方法,或者修改原来的方法,这时我们不必从头开始,可以从原来的类派生出一个新的类,我们把原来的类称为父类或基类,而派生出的类称为子类,子类继承了父类的所有数据和方法。
让我们看一个简单的例子,首先我们定义一个 Animal 类:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def greet ( self ):
5. print ‘Hello, I am %s.’ % self . name
现在,我们想创建一个 Dog 类,比如:
1. class Dog ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def greet ( self ):
5. print 'WangWang…, I am %s. ’ % self . name
可以看到, Dog 类和 Animal 类几乎是一样的,只是 greet 方法不一样,我们完全没必要创建
一个新的类,而是从 Animal 类派生出一个新的类:
1. class Dog ( Animal ):
2. def greet ( self ):
3. print 'WangWang…, I am %s. ’ % self . name
Dog 类是从 Animal 类继承而来的, Dog 类自动获得了 Animal 类的所有数据和方法,而且还可以
对父类的方法进行修改,我们看看使用:
1. >>> animal = Animal ( ‘animal’ ) # 创建 animal 实例
2. >>> animal . greet ()
3. Hello , I am animal .
4. >>>
5. >>> dog = Dog ( ‘dog’ ) # 创建 dog 实例
6. >>> dog . greet ()
7. WangWang …, I am dog .
我们还可以对 Dog 类添加新的方法:
1. class Dog ( Animal ):
2. def greet ( self ):
3. print 'WangWang…, I am %s. ’ % self . name
4. def run ( self ):
5. print ‘I am running.I am running’
使用:
1. >>> dog = Dog ( ‘dog’ )
2. >>> dog . greet ()
3. WangWang …, I am dog .
4. >>> dog . run ()
5. I am running
3.2 多态
多态的概念其实不难理解,它是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。
事实上,我们经常用到多态的性质,比如:
1. >>> 1 + 2
2. 3
3. >>> ‘a’ + ‘b’
4. ‘ab’
可以看到,我们对两个整数进行 + 操作,会返回它们的和,对两个字符进行相同的 + 操作,
会返回拼接后的字符串。也就是说,不同类型的对象对同一消息会作出不同的响应。
再看看类的例子:
1. class Animal ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def greet ( self ):
5. print ‘Hello, I am %s.’ % self . name
7. class Dog ( Animal ):
8. def greet ( self ):
9. print ‘WangWang…, I am %s.’ % self . name
11. class Cat ( Animal ):
12. def greet ( self ):
13. print ‘MiaoMiao…, I am %s’ % self . name
15. def hello ( animal ):
16. animal . greet ()
看看多态的使用:
1. >>> dog = Dog ( ‘dog’ )
2. >>> hello ( dog )
3. WangWang …, I am dog .
4. >>>
5. >>> cat = Cat ( ‘cat’ )
6. >>> hello ( cat )
7. MiaoMiao …, I am cat
可以看到, cat 和 dog 是两个不同的对象,对它们调用 greet 方法,它们会自动调用实
际类型的 greet 方法,作出不同的响应。这就是多态的魅力。
4 类方法和静态方法
在讲类方法和静态方法之前,先来看一个简单的例子:
1. class A ( object ):
2. def foo ( self ):
3. print 'Hello ’ , self
5. >>> a = A ()
6. >>> a . foo ()
7. Hello , < __main__ . A object at 0x10c37a450 >
在上面,我们定义了一个类 A ,它有一个方法 foo ,然后我们创建了一个对象 a ,并调用方法 foo 。
4.1 类方法
如果我们想通过类来调用方法,而不是通过实例,那应该怎么办呢?
Python 提供了 classmethod 装饰器让我们实现上述功能,看下面的例子:
1. class A ( object ):
2. bar = 1
3. @classmethod
4. def class_foo ( cls ):
5. print 'Hello, ’ , cls
6. print cls . bar
8. >>> A . class_foo () # 直接通过类来调用方法
9. Hello , < class ‘__main__.A’ >
10. 1
在上面,我们使用了 classmethod 装饰方法 class_foo ,它就变成了一个类方
法, class_foo 的参数是 cls ,代表类本身,当我们使用 A.class_foo() 时, cls 就会接
收 A 作为参数。另外,被 classmethod 装饰的方法由于持有 cls 参数,因此我们可以在方法
里面调用类的属性、方法,比如 cls.bar 。
4.2 静态方法
在类中往往有一些方法跟类有关系,但是又不会改变类和实例状态的方法,这种方法是静态方法,我们 使用 staticmethod 来装饰,比如下面的例子:
1. class A ( object ):
2. bar = 1
3. @staticmethod
4. def static_foo ():
5. print 'Hello, ’ , A . bar
7. >>> a = A ()
8. >>> a . static_foo ()
9. Hello , 1
10. >>> A . static_foo ()
11. Hello , 1
可以看到,静态方法没有 self 和 cls 参数,可以把它看成是一个普通的函数,我们当然可以把它
写到类外面,但这是不推荐的,因为这不利于代码的组织和命名空间的整洁。
5 定制类和魔法方法
在 Python 中,我们可以经常看到以双下划线 __ 包裹起来的方法,比如最常见的
__init__ ,这些方法被称为魔法方法( magic method )或特殊方法( special method )。简
单地说,这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类,比如 __init__ 方
法可以对实例属性进行初始化。
完整的特殊方法列表可在 这里 查看,本文介绍部分常用的特殊方法:
__new__
__str__ , __repr__
__iter__
__getitem__ , __setitem__ , __delitem__
__getattr__ , __setattr__ , __delattr__
__call__
5.1 new
在 Python 中,当我们创建一个类的实例时,类会先调用 __new__(cls[, …]) 来创建实例,
然后 __init__ 方法再对该实例( self )进行初始化。
关于 __new__ 和 __init__ 有几点需要注意:
__new__ 是在 __init__ 之前被调用的;
__new__ 是类方法, __init__ 是实例方法;
重载 __new__ 方法,需要返回类的实例;
一般情况下,我们不需要重载 __new__ 方法。但在某些情况下,我们想控制实例的创建过程,这
时可以通过重载 __new_ 方法来实现。
让我们看一个例子:
1. class A ( object ):
2. _dict = dict ()
4. def __new__ ( cls ):
5. if ‘key’ in A . _dict :
6. print “EXISTS”
7. return A . _dict [ ‘key’ ]
8. else :
9. print “NEW”
10. return object . __new__ ( cls )
12. def __init__ ( self ):
13. print “INIT”
14. A . _dict [ ‘key’ ] = self
在上面,我们定义了一个类 A ,并重载了 __new__ 方法:当 key 在 A._dict 中
时,直接返回 A._dict[‘key’] ,否则创建实例。
执行情况:
1. >>> a1 = A ()
2. NEW
3. INIT
4. >>> a2 = A ()
5. EXISTS
6. INIT
7. >>> a3 = A ()
8. EXISTS
9. INIT
5.2 str & repr
先看一个简单的例子:
1. class Foo ( object ):
2. def __init__ ( self , name ):
3. self . name = name
5. >>> print Foo ( ‘ethan’ )
6. < __main__ . Foo object at 0x10c37aa50 >
在上面,我们使用 print 打印一个实例对象,但如果我们想打印更多信息呢,比如把 name 也打印
出来,这时,我们可以在类中加入 __str__ 方法,如下:
1. class Foo ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def __str__ ( self ):
5. return ‘Foo object (name: %s)’ % self . name
7. >>> print Foo(‘ethan’) # 使用 print
8. Foo object ( name : ethan )
9. >>>
10. >>> str ( Foo ( ‘ethan’ )) # 使用 str
11. ‘Foo object (name: ethan)’
12. >>>
13. >>> Foo ( ‘ethan’ ) # 直接显示
14. < __main__ . Foo at 0x10c37a490 >
可以看到,使用 print 和 str 输出的是 __str__ 方法返回的内容,但如果直接显示则不是,
那能不能修改它的输出呢?当然可以,我们只需在类中加入 __repr__ 方法,比如:
1. class Foo ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def __str__ ( self ):
5. return ‘Foo object (name: %s)’ % self . name
6. def __repr__ ( self ):
7. return ‘Foo object (name: %s)’ % self . name
9. >>> Foo ( ‘ethan’ )
10. ‘Foo object (name: ethan)’
可以看到,现在直接使用 Foo(‘ethan’) 也可以显示我们想要的结果了,然而,我们发现上面的代
码中, __str__ 和 __repr__ 方法的代码是一样的,能不能精简一点呢,当然可以,如下:
1. class Foo ( object ):
2. def __init__ ( self , name ):
3. self . name = name
4. def __str__ ( self ):
5. return ‘Foo object (name: %s)’ % self . name
6. __repr__ = __str__
5.3 iter
在某些情况下,我们希望实例对象可被用于 for…in 循环,这时我们需要在类中定义
__iter__ 和 next (在 Python3 中是 __next__ )方法,其中, __iter__ 返回一
个迭代对象, next 返回容器的下一个元素,在没有后续元素时抛出 StopIteration 异常。
看一个斐波那契数列的例子:
1. class Fib ( object ):
2. def __init__ ( self ):
3. self . a , self . b = 0 , 1
5. def __iter__ ( self ): # 返回迭代器对象本身
6. return self
8. def next ( self ): # 返回容器下一个元素
9. self . a , self . b = self . b , self . a + self . b
10. return self . a
12. >>> fib = Fib ()
13. >>> for i in fib :
14. … if i > 10 :
15. … break
16. … print i
17. …
18. 1
19. 1
20. 2
21. 3
22. 5
23. 8
5.4 getitem
有时,我们希望可以使用 obj[n] 这种方式对实例对象进行取值,比如对斐波那契数列,我们希望
可以取出其中的某一项,这时我们需要在类中实现 __getitem__ 方法,比如下面的例子:
1. class Fib ( object ):
2. def __getitem__ ( self , n ):
3. a , b = 1 , 1
4. for x in xrange ( n ):
5. a , b = b , a + b
6. return a
8. >>> fib = Fib ()
9. >>> fib [ 0 ], fib [ 1 ], fib [ 2 ], fib [ 3 ], fib [ 4 ], fib [ 5 ]
10. ( 1 , 1 , 2 , 3 , 5 , 8 )
我们还想更进一步,希望支持 obj[1:3] 这种切片方法来取值,这时 __getitem__ 方法传入的参数可能是一个整数,也可能是一个切片对象 slice ,因此,我们需要对传入的参数进行判断,可
以使用 isinstance 进行判断,改后的代码如下:
1. class Fib ( object ):
2. def __getitem__ ( self , n ):
3. if isinstance ( n , slice ): # 如果 n 是 slice 对象
4. a , b = 1 , 1
5. start , stop = n . start , n . stop
6. L = []
7. for i in xrange ( stop ):
8. if i >= start :
9. L . append ( a )
10. a , b = b , a + b
11. return L
12. if isinstance ( n , int ): # 如果 n 是 int 型
13. a , b = 1 , 1
14. for i in xrange ( n ):
15. a , b = b , a + b
16. return a
现在,我们试试用切片方法:
1. >>> fib = Fib ()
2. >>> fib [ 0 : 3 ]
3. [ 1 , 1 , 2 ]
4. >>> fib [ 2 : 6 ]
5. [ 2 , 3 , 5 , 8 ]
上面,我们只是简单地演示了 getitem 的操作,但是它还很不完善,比如没有对负数处理,不支持带
step 参数的切片操作 obj[1:2:5] 等等,读者有兴趣的话可以自己实现看看。
__geitem__ 用于获取值,类似地, __setitem__ 用于设置值, __delitem__ 用于删除
值,让我们看下面一个例子:
1. class Point ( object ):
2. def __init__ ( self ):
3. self . coordinate = {}
5. def __str__ ( self ):
6. return “point(%s)” % self . coordinate
8. def __getitem__ ( self , key ):
9. return self . coordinate . get ( key )
11. def __setitem__ ( self , key , value ):
12. self . coordinate [ key ] = value
14. def __delitem__ ( self , key ):
15. del self . coordinate [ key ]
16. print ‘delete %s’ % key
18. def __len__ ( self ):
19. return len ( self . coordinate )
21. __repr__ = __str__
在上面,我们定义了一个 Point 类,它有一个属性 coordinate (坐标),是一个字典,让我们看
看使用:
1. >>> p = Point ()
2. >>> p [ ‘x’ ] = 2 # 对应于 p.__setitem__(‘x’, 2)
3. >>> p [ ‘y’ ] = 5 # 对应于 p.__setitem__(‘y’, 5)
4. >>> p # 对应于 __repr__
5. point ({ ‘y’ : 5 , ‘x’ : 2 })
6. >>> len ( p ) # 对应于 p.__len__
7. 2
8. >>> p [ ‘x’ ] # 对应于 p.__getitem__(‘x’)
9. 2
10. >>> p [ ‘y’ ] # 对应于 p.__getitem__(‘y’)
11. 5
12. >>> del p [ ‘x’ ] # 对应于 p.__delitem__(‘x’)
13. delete x
14. >>> p
15. point ({ ‘y’ : 5 })
16. >>> len ( p )
17. 1
5.5 getattr
当我们获取对象的某个属性,如果该属性不存在,会抛出 AttributeError 异常,比如:
1. class Point ( object ):
2. def __init__ ( self , x = 0 , y = 0 ):
3. self . x = x
4. self . y = y
6. >>> p = Point ( 3 , 4 )
7. >>> p . x , p . y
8. ( 3 , 4 )
9. >>> p . z
10. ---------------------------------------------------------------------------
11. AttributeError Traceback ( most recent call last )
12. < ipython - input - 547 - 6dce4e43e15c > in < module >()
13. ----> 1 p . z
15. AttributeError : ‘Point’ object has no attribute ‘z’
那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入 __getattr__ 方法,比如:
1. class Point ( object ):
2. def __init__ ( self , x = 0 , y = 0 ):
3. self . x = x
4. self . y = y
5. def __getattr__ ( self , attr ):
6. if attr == ‘z’ :
7. return 0
9. >>> p = Point ( 3 , 4 )
10. >>> p . z
11. 0
现在,当我们调用不存在的属性(比如 z )时,解释器就会试图调用 __getattr__(self, ‘z’)
来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如 w ,会返回 None ,因为
__getattr__ 默认返回就是 None ,只有当 attr 等于 ‘z’ 时才返回 0 ,如果我们想让
__getattr__ 只响应几个特定的属性,可以加入异常处理,修改 __getattr__ 方法,如下:
1. def __getattr__ ( self , attr ):
2. if attr == ‘z’ :
3. return 0
4. raise AttributeError ( “Point object has no attribute %s” % attr )
这里再强调一点, __getattr__ 只有在属性不存在的情况下才会被调用,对已存在的属性不会调用
__getattr__ 。
与 __getattr__ 一起使用的还有 __setattr__ , __delattr__ ,类似 obj.attr =value , del obj.attr ,看下面一个例子:
1. class Point ( object ):
2. def __init__ ( self , x = 0 , y = 0 ):
3. self . x = x
4. self . y = y
6. def __getattr__ ( self , attr ):
7. if attr == ‘z’ :
8. return 0
9. raise AttributeError ( “Point object has no attribute %s” % attr )
11. def __setattr__ ( self , * args , ** kwargs ):
12. print ‘call func set attr (%s, %s)’ % ( args , kwargs )
13. return object . __setattr__ ( self , * args , ** kwargs )
15. def __delattr__ ( self , * args , ** kwargs ):
16. print ‘call func del attr (%s, %s)’ % ( args , kwargs )
17. return object . __delattr__ ( self , * args , ** kwargs )
19. >>> p = Point ( 3 , 4 )
20. call func set attr (( ‘x’ , 3 ), {})
21. call func set attr (( ‘y’ , 4 ), {})
22. >>> p . z
23. 0
24. >>> p . z = 7
25. call func set attr (( ‘z’ , 7 ), {})
26. >>> p . z
27. 7
28. >>> p . w
29. Traceback ( most recent call last ):
30. File “” , line 1 , in < module >
31. File “” , line 8 , in __getattr__
32. AttributeError : Point object has no attribute w
33. >>> p . w = 8
34. call func set attr (( ‘w’ , 8 ), {})
35. >>> p . w
36. 8
37. >>> del p . w
38. call func del attr (( ‘w’ ,), {})
39. >>> p . __dict__
40. { ‘y’ : 4 , ‘x’ : 3 , ‘z’ : 7 }
5.6 call
我们一般使用 obj.method() 来调用对象的方法,那能不能直接在实例本身上调用呢?在
Python 中,只要我们在类中定义 __call__ 方法,就可以对实例进行调用,比如下面的例子:
1. class Point ( object ):
2. def __init__ ( self , x , y ):
3. self . x , self . y = x , y
4. def __call__ ( self , z ):
5. return self . x + self . y + z
使用如下:
1. >>> p = Point ( 3 , 4 )
2. >>> callable ( p ) # 使用 callable 判断对象是否能被调用
3. True
4. >>> p ( 6 ) # 传入参数,对实例进行调用,对应 p.__call__(6)
5. 13 # 3+4+6
可以看到,对实例进行调用就好像对函数调用一样。
__new__ 在 __init__ 之前被调用,用来创建实例。
__str__ 是用 print 和 str 显示的结果, __repr__ 是直接显示的结果。
__getitem__ 用类似 obj[key] 的方式对对象进行取值
__getattr__ 用于获取不存在的属性 obj.attr
__call__ 使得可以对实例进行调用
5.7 小结
__new__ 在 __init__ 之前被调用,用来创建实例。
__str__ 是用 print 和 str 显示的结果, __repr__ 是直接显示的结果。
__getitem__ 用类似 obj[key] 的方式对对象进行取值
__getattr__ 用于获取不存在的属性 obj.attr
__call__ 使得可以对实例进行调用
6 slots 魔法
在 Python 中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。
看下面一个简单的例子:
1. class Point ( object ):
2. def __init__ ( self , x = 0 , y = 0 ):
3. self . x = x
4. self . y = y
6. >>> p = Point ( 3 , 4 )
7. >>> p . z = 5 # 绑定了一个新的属性
8. >>> p . z
9. 5
10. >>> p . __dict__
11. { ‘x’ : 3 , ‘y’ : 4 , ‘z’ : 5 }
在上面,我们创建了实例 p 之后,给它绑定了一个新的属性 z ,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。
因此,为了不浪费内存,可以使用 __slots__ 来告诉 Python 只给一个固定集合的属性分配空
间,对上面的代码做一点改进,如下:
1. class Point ( object ):
2. __slots__ = ( ‘x’ , ‘y’ ) # 只允许使用 x 和 y
4. def __init__ ( self , x = 0 , y = 0 ):
5. self . x = x
6. self . y = y
上面,我们给 __slots__ 设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个
新的属性,比如 z ,就会出错了,如下:
1. >>> p = Point ( 3 , 4 )
2. >>> p . z = 5
3. ---------------------------------------------------------------------------
文末有福利领取哦~
👉一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
👉二、Python必备开发工具
👉三、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉 四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。(文末领读者福利)
👉五、Python练习题
检查学习结果。
👉六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
👉因篇幅有限,仅展示部分资料,这份完整版的Python全套学习资料已经上传
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!