类与对象
class Student(object):
pass
stu = Student
s = Student()
print (stu)
print (Student)
print (s)
结果是:
<class '__main__.Student'>
<class '__main__.Student'>
<__main__.Student object at 0x000002E28CE33E10>
## 0x000002E28CE33E10是内存地址
类的方法和字段
类的方法
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
类的属性
- 定义在类内部方法外部(类属性)
- 定义在方法内部(对象属性)
- 定义在 _ self _ 方法中
- 定义在其他方法中
- 对实例变量绑定任何数据
class Student(object):
sex = "man"
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
self.grade = 1
print('%s: %s' % (self.name, self.score))
print (Student.sex)
s = Student("zhangsan",95)
print (s.name)
print (s.sex)
#print (s.grade) -- 这个时候访问grade会出错
s.print_score()
print (s.grade)
s.count = "China"
print (s.count)
类的访问限制
私有变量
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以 __开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
私有方法
方法前加两个下划线__
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def __print_score(self):
print('%s: %s' % (self.__name, self.__score))
s = Student("zhangsan",95)
s.__print_score()
# Traceback (most recent call last):
# File
# "C:/Users/disheng.zeng/Develop/Python/Scrapy/purePython1/main.py",
# line 27, in <module>
# s.__print_score()
# AttributeError: 'Student' object has no attribute '__print_score'
私有变量和特殊变量
在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:
>>> bart._Student__name
'Bart Simpson'
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
继承和多态
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
class Animal(object):
def run(self):
print ("animal running...")
class Dog(Animal):
def run(self):
print ("dog running...")
class Car(object):
def run(self):
print ("car running...")
def showrun(animal):
animal.run()
showrun(Animal())
showrun(Dog())
showrun(Car())
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
- 本质上来讲是因为python在方法参数中不指定变量类型
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
获取对象信息
使用type()
判断变量类型
判断变量是否是函数
使用isinstance()
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
- 判断类类型
- 判断继承关系
- 判断普通类型
- 判断一个变量是否是某些类型中的一种
使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法:
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100
剩下的都是普通属性或方法,比如lower()返回小写的字符串:
>>> 'ABC'.lower()
'abc'
反射
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
紧接着,可以测试该对象的属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:
sum = obj.x + obj.y
就不要写:
sum = getattr(obj, 'x') + getattr(obj, 'y')
一个正确的用法的例子如下:
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。
请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。
给类或者实例动态绑定属性或者方法
绑定属性
class Student(object):
pass
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael
绑定方法
- 如果是类绑定方法,则所有对象共享
- 如果是对象绑定方法,则不能共享
特别注意:
绑定的方法第一个参数一定要是self
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s = Student()
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
### Student.set_age = MethodType(set_age, Student) # 给类绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
- 给一个实例绑定的方法,对另一个实例是不起作用的:
- 为了给所有实例都绑定方法,可以给class绑定方法:
__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
问题:
__slot__的继承问题
使用@property装饰器
用法
- 加上
@property,把一个getter方法变成属性 @score.setter,负责把一个setter方法变成属性赋值
class Actress():
def __init__(self):
self._name = 'TianXin'
self._age = 20
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if age > 30:
raise ValueError
self._age = age
类Actress中有一个属性age,它有getter和setter两个方法,而具体getter和setter之中的实现,外部调用是不管的,也就是说外部不知道getter到的age是来自_age,还是来自_name
实例:快速进行代码重构
参考 http://www.jb51.net/article/86970.htm
可以这类内部进行属性值的getter和setter具体操作的变动,而不需要修改外部调用方
多重继承
class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
继承:主线与MixIn
由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。
只允许单一继承的语言(如Java)不能使用MixIn的设计。
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:
class MyTCPServer(TCPServer, CoroutineMixIn):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
定制类
__str__()和__repr__()
打印类
| 方法 | 区别 | 使用 |
|---|---|---|
__str__() | 返回用户看到的字符串 | 对应的print()方法 |
__repr__() | 返回程序开发者看到的字符串 | 为调试服务的 |
通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
__iter__() 和 __next__()
迭代器
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
class Fib(object):
def __init__(self):
self.a = 0
self.b = 1 # 初始化两个计算器
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a , self.b = self.b ,self.a + self.b #计算下一个值
if self.a > 1000: #退出循环的条件
raise StopIteration()
return self.a #返回下一个值
for n in Fib():
print (n)
# 1
# 1
# 2
# 3
# 5
# 8
# 13
# 21
# 34
# 55
# 89
# 144
# 233
# 377
# 610
# 987
__getitem__()
下标访问
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
切片访问
__getattr__()
动态化处理处理类的所有属性和方法
只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
class Student(object):
def __getattr__(self,attr):
if attr == 'score': # 对字段的处理
return 99
if attr == 'age': # 对方法的处理
return lambda:25
s = Student()
print (s.score)
print (s.age())
print (s.name)
print (s.grade())
# 99
# 25
# None
# Traceback (most recent call last):
# File "C:/Users/disheng.zeng/Develop/Python/Scrapy/purePython1/main.py", line 20, in <module>
# print (s.grade())
# TypeError: 'NoneType' object is not callable
注意到任意调用如s.name都会返回None,这是因为我们定义的__getattr__默认返回就是None
__call__()
class Student(object):
def __init__(self,name):
self.name = name
s = Student("Joy")
s()
# 报错
# TypeError: 'Student' object is not callable
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。
class Student(object):
def __init__(self,name):
self.name = name
def __call__(self):
print ("My name is %s" % self.name)
s = Student("Joy")
s()
# My name is Joy
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。
使用枚举类
使用Enum类
from enum import Enum
Mon = Enum("Month",("Jan","Feb","Mar","Apr"))
print (Mon.Jan)
print (Mon.Jan.value)
for name ,member in Mon.__members__.items():
print (name,"=>",member,",",member.value)
# Month.Jan
# 1
# Jan => Month.Jan , 1
# Feb => Month.Feb , 2
# Mar => Month.Mar , 3
# Apr => Month.Apr , 4
自定义枚举类
@unique装饰器可以帮助我们检查保证没有重复值。
@unique
class Month(Enum):
Jan = 1
Feb = 2
Jun = 6
Aug = 8
print (Month.Jan)
print (Month.Jan.value)
print (Month['Jan'])
print (Month(6))
print (Month(6).value)
# Month.Jan
# 1
# Month.Jan
# Month.Jun
# 6
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
使用元类
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
比方说我们要定义一个Student的class,就写一个student.py模块:
class Student(object):
def name(self,name="Mirceal"):
print ("Hello, %s" % name)
s = Student()
s.name()
print (type(Student))
print (type(s))
# Hello, Mirceal
# <class 'type'>
# <class '__main__.Student'>
当Python解释器载入student模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Student的class对象
type()函数可以查看一个类型或变量的类型,Student是一个class,它的类型就是type,而s是一个实例,它的类型就是class Student。
我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Student类,而无需通过class Student(object)...的定义:
def fn(self, name="Mirceal"):
print("Hello, %s" % name)
Student = type("Student",(object,),dict(name = fn))
s = Student()
s.name()
# Hello, Mirceal
# <class 'type'>
# <class '__main__.Student'>
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名Student上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
本文详细介绍了Python的面向对象编程,包括类与对象的概念、类的方法和字段、访问限制(私有变量和方法)、继承与多态。还讨论了如何获取对象信息、动态绑定属性和方法、使用@property装饰器进行属性管理、多重继承、定制类以及枚举类和元类的使用。文章深入浅出地展示了Python面向对象编程的各个方面。
408

被折叠的 条评论
为什么被折叠?



