【Python】【面向对象】
"""
# 【【面向对象】】
#【访问限制】
#如果要让内部属性不被外部访问,可加双下划线,编程私有变量。只有内部可以访问,外部不能访问。
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))
bart = Student('Bart Simpson',66)
#print(bart.__name) #AttributeError: 'Student' object has no attribute '__name'
#如果想让外部代码获取name score 可以如下
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))
def get_name(self):
return self.__name
def get_score(self):
return self.__score
#允许外部代码修改
def set_score(self,score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
#【注意】__name__是特殊变量,可直接访问,不是private变量,所以,不能用__name__这样的变量名。
# 一个下划线,外部可以访问,但是按照约定俗成,遇到这样的,就当成是私有的
# 双下划线不能直接访问的原因,python解释器对外把__name改成了_Student__name,所以仍然可通过_Student__name来访问__name变量
print(bart._Student__name) #Bart Simpson
# 但是强烈建议不要这么做,因为不同版本的python解释器,可能会把__name改成不同的变量名。
#注意下面错误写法
bart = Student('Bart Simpson',66)
print(bart.get_name()) #Bart Simpson
bart.__name = 'New Name'
print(bart.__name) #New Name 【注意】此时的__name和类内部的__name不是一回事,内部的已经被python解释器自动改成了_Student__name,外部代码给bart对象新增了一个__name变量
print(bart.get_name()) #Bart Simpson # 【继承&多态】
#继承可以把父类所有功能直接拿过来,这样就不必从零做起。子类只需要新增自己的方法,也可以把父类不适合的方法覆盖重写。
#动态语言的鸭子类型决定了继承不像静态语言那样是必须的。 #【获取对象信息】
# 1 使用type() 判断基本数据类型可直接写int str 等。 如果要判断是否是函数,可 用types 模块中定义的常量。
import types
def fn():
pass print (type(fn) == types.FunctionType) #True
print (type(abs) == types.BuiltinFunctionType) #True
print (type(lambda x:x) == types.LambdaType) #True
print (type(x for x in range(10)) == types.GeneratorType) #True # 2 使用isinstance() 可用type() 的都可用isinstance() 但可用isinstance()的不一定就能用type()代替
print (isinstance('12',str)) #True
print (isinstance((lambda x:x),types.LambdaType)) #True class Animal(object):
def run(self):
print ('animal is running..')
class Dog(Animal):
def run(self):
print ('dog is running..')
class Hushy(Dog):
def run(self):
print ('hushy is running..') a = Animal()
d = Dog()
h = Hushy()
print (type(d) == Dog) #True
print (type(d) == Animal) #False
print (isinstance(d,Dog)) #True
print (isinstance(d,Animal)) #True
print (isinstance(h,Animal)) #True
print (isinstance(d,Hushy)) #False
#还可以判断一个变量是否是某些类型中的一种。
print (isinstance([1,2,3],(list,tuple))) #True #【小结】总是优先使用isinstance(), 可以将指定类型及其子类"一网打尽" # 3 使用dir() 获取一个对象所有属性和方法。
print (dir('ABC')) #['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
#仅仅是把属性和方法列出来是不够的,配合getattr() setattr() hasattr() ,可以直接操作一个对象的状态
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject() print (hasattr(obj,'x')) #True
print (obj.x) #9
print (hasattr(obj,'y')) #False
setattr(obj,'y',19)
print (hasattr(obj,'y')) #True
print (getattr(obj,'y')) #19
print (obj.y) #19
#如果试图获取不存在的属性,抛出AttributeError
#print (getattr(obj,'z')) #AttributeError: 'MyObject' object has no attribute 'z'
#可传一个default参数,如果属性不存在,就返回默认值
print (getattr(obj,'z',404)) #404
#也可以获得对象的方法
print (hasattr(obj,'power')) #True
print (getattr(obj,'power')) #<bound method MyObject.power of <__main__.MyObject object at 0x102a36470>>
#获取属性power并赋值到变量fn
fn = getattr(obj,'power')
print (fn) #<bound method MyObject.power of <__main__.MyObject object at 0x1022364a8>>
print (fn()) #81 '''
获取对象信息 阅读: 214832
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢? 使用type() 首先,我们来判断对象类型,使用type()函数: 基本类型都可以用type()判断: >>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
如果一个变量指向函数或者类,也可以用type()判断: >>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同: >>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量: >>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
使用isinstance() 对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。 我们回顾上次的例子,如果继承关系是: object -> Animal -> Dog -> Husky
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象: >>> a = Animal()
>>> d = Dog()
>>> h = Husky()
然后,判断: >>> isinstance(h, Husky)
True
没有问题,因为h变量指向的就是Husky对象。 再判断: >>> isinstance(h, Dog)
True
h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。 因此,我们可以确信,h还是Animal类型: >>> isinstance(h, Animal)
True
同理,实际类型是Dog的d也是Animal类型: >>> isinstance(d, Dog) and isinstance(d, Animal)
True
但是,d不是Husky类型: >>> isinstance(d, Husky)
False
能用type()判断的基本类型也可以用isinstance()判断: >>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple: >>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
使用dir() 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法: >>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似__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()方法返回的是有效的图像数据,就不影响读取图像的功能。 ''' # 【实例属性& 类属性】
#可以为实例绑定任何属性和方法,这就是动态语言的灵活性。 # 【__slots__】 # 正常情况下,当我们定义了一个class,创建了一个class实例后,我们可以为该实例绑定任何属性和方法,这就是动态语言的灵活性。
class Student(object):
pass
s = Student()
s.name = 'Michael'
print(s.name)
#还可以给 实例绑定一个方法
def set_age(self,age):
self.age = age
from types import MethodType
s.set_age = MethodType(set_age,s)
s.set_age(25)
print(s.age) #25
#但是,给一个实例绑定的方法,对另一个实例是不起作用的
s2 = Student()
#s2.set_age(46) #AttributeError: 'Student' object has no attribute 'set_age'
#为了给所有实例都绑定方法,可以给class绑定方法
Student.set_age = set_age
s2 = Student()
s.set_age(23)
print (s.age) #23
s2.set_age(55)
print (s2.age) #55
#【小结】通常情况下,set_age可直接定义在类中,但动态绑定允许我们在程序运行过程中动态的给class加功能,这在静态语言中很难实现。 # 为了限制class实例可以添加的属性,用__slots__
class Student(object):
__slots__ = ('name','age')
s = Student()
s.name = 'Michael'
s.age = 23
#s.score = 99 #AttributeError: 'Student' object has no attribute 'score'
#【注意】__slots__只对当前类的实例起作用,对继承的子类是不起作用的
class AStudent(Student):
pass
a = AStudent()
a.score = 99 #此时不报错 # 【使用@property】
#在绑定属性时,如果直接把属性暴露出去,虽然容易实现,但是不安全。
s = Student()
s.age= 1000
#这显然不合逻辑,为了限制score的范围,可通过set_score()来设置成绩,再通过get_score()来获取成绩。这样在set_score()里就可以检查参数。
class Student2(object): def get_score(self):
return self.__score def set_score(self,value):
if not isinstance(value,int):
raise ValueError('score must be a integer!')
if value < 0 or value > 100:
raise ValueError('score must be between 0 ~ 100!')
self.__score = value
s = Student2()
s.set_score(60)
print(s.get_score()) #60
#s.set_score(1000) #ValueError: score must be between 0 ~ 100! '''
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?
还记得装饰器(decorator)可以给函数动态的加上功能吗?对于类的方法,装饰器一样起作用。python内置的@property装饰器就是负责把一个方法编程属性调用的。
'''
class Student3(object):
@property
def score(self):
return self._score @score.setter
def score(self,value):
if not isinstance(value,int):
raise ValueError('score must be a integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
'''
【解析】
把一个getter方法变成属性,只要加上@property。此时,@property又创建了另一个装饰器,@score.setter,负责把一个setter方法变成属性赋值。
于是我们就拥有一个可控的属性操作
'''
s = Student3()
s.score = 66
print (s.score) #66
#s.score = 9999 #ValueError: score must between 0 ~ 100! #定义只读属性,方式是只定义getter 方法,不定义setter方法
class Student4(object):
@property
def birthday(self):
return self._birthday @birthday.setter
def brithday(self,value):
self._birthday = value @property
def age(self):
return 2015 - self._birthday
#上面的birthday 是可读写属性,age是只读属性。 # 【多重继承】
# python允许一个类继承多个父类
# Mixln目的是给一个类增加多个功能,这样,在设计类时,我们优先考虑通过多重继承来组合多个Mixln的功能,而不是设计多层次的复杂继承关系。
'''
多重继承 阅读: 143522
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。 回忆一下Animal类层次的设计,假设我们要实现以下4种动物: Dog - 狗狗;
Bat - 蝙蝠;
Parrot - 鹦鹉;
Ostrich - 鸵鸟。
如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次: ┌───────────────┐
│ Animal │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Mammal │ │ Bird │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Bat │ │ Parrot │ │ Ostrich │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次: ┌───────────────┐
│ Animal │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Runnable │ │ Flyable │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Ostrich │ │ Parrot │ │ Bat │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
如果要把上面的两种分类都包含进来,我们就得设计更多的层次: 哺乳类:能跑的哺乳类,能飞的哺乳类;
鸟类:能跑的鸟类,能飞的鸟类。
这么一来,类的层次就复杂了: ┌───────────────┐
│ Animal │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Mammal │ │ Bird │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ MRun │ │ MFly │ │ BRun │ │ BFly │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Bat │ │ Ostrich │ │ Parrot │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。 正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计: class Animal(object):
pass # 大类:
class Mammal(Animal):
pass class Bird(Animal):
pass # 各种动物:
class Dog(Mammal):
pass class Bat(Mammal):
pass class Parrot(Bird):
pass class Ostrich(Bird):
pass
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类: class Runnable(object):
def run(self):
print('Running...') class Flyable(object):
def fly(self):
print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog: class Dog(Mammal, Runnable):
pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat: class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。 MixIn 在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。 为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个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
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
''' #【小结】
#由于python允许多重继承,因此,Mixln是一种常见的设计
#只允许单一继承的语言(Java),不能使用Mixln的设计。 # 【定制类】 看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的 #__str__
class Student4(object):
def __init__(self,name):
self.name = name
print (Student4('Michael')) #<__main__.Student4 object at 0x102274da0>
#打印出来<__main__.Student4 object at 0x102274da0>,不好看,此时只需定义__str__
class Student5(object):
def __init__(self,name):
self.name = name def __str__(self):
return 'Student4 object (name: %s)' % self.name
print (Student5('Michael')) #Student4 object (name: Michael)
#这样打印出来的实例,不但好看,而且容易看出实例内部重要数据。
'''
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看: >>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。 解决办法是再定义一个__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__ 如果一个类想被用于for....in ,类似tuple list 那样,就必须实现__iter__(),返回一个迭代对象,然后
# python的for循环会不断调用该迭代对象的__next__()拿到循环的下一个值,直到遇到StopIteration错误时退出循环
#斐波拉契为例,
class Fib(object):
def __init__(self):
self.a ,self.b = 0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 10:
raise StopIteration()
return self.a
#现在试试把Fib用于for循环
for n in Fib():
print (n)
'''
1
1
2
3
5
8
''' #__getitem__ Fib实例虽然能作用与for循环,看起来和list有些像,但是,把它当成list来使用还是不行,比如,取第五个元素
class Fib():
def __init__(self):
self.a,self.b = 0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 10:
raise StopIteration()
return self.a
#print (Fib()[5]) #TypeError: 'Fib' object does not support indexing
#要表现的像list那样按照下标取出元素,需要实现__getitem__()
class Fib():
def __init__(self):
self.a,self.b = 0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 10:
raise StopIteration()
return self.a
def __getitem__(self, item):
a,b = 1,1
for x in range(item):
a,b = b,a + b
return a
print(Fib()[5]) #8
print(Fib()[0]) #1
print(Fib()[1]) #1 #如果想实现list的切片,要优化
class Fib(object):
def __init__(self):
self.a,self.b = 0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 10:
raise StopIteration()
return self.a
def __getitem__(self, item):
if isinstance(item,int):
a,b = 1,1
for x in range(item):
a,b = b,a + b
return a
if isinstance(item,slice):
start = item.start
stop = item.stop
if start is None:
start = 0
a,b = 1,1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a,b = b,a+b
return L
f = Fib()
print (f[0:5]) #[1, 1, 2, 3, 5]
print (f[:10]) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55] # __getattr__
#正常情况下,调用类的方法或属性,如果不存在,就会报错。
class Student6(object):
def __init__(self):
self.name = 'Michael'
a = Student6()
print (a.name) #Michael
#print (a.score) #AttributeError: 'Student6' object has no attribute 'score'
#要避免这种情况,除了给类加属性,另一个办法是__getattr__(),动态返回一个属性
class Student7(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
a = Student7()
print (a.name) #Michael
print (a.score) #99
#返回函数也是可以的
class Student8(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, item):
if item == 'age':
return lambda :25
a = Student8()
print (a.age) #<function Student8.__getattr__.<locals>.<lambda> at 0x102249950>
print (a.age()) #25
print (a.abc) #None
'''
【注意】
1 只有在没有属性的情况下,才会调用__getattr__()
2 任意调用,如a.abc,都会返回None,因为我们定义的__getattr__()默认返回None.要让class只响应特定的几个属性,我们就要按照约定俗成,抛出AttributeError异常。
'''
class Student9(object):
#.....
def __getattr__(self, item):
if item == 'age':
return lambda : 25
raise AttributeError('\'Student9\' object has no attribute \'%s\'' % item)
#这实际上可以把一个类说有属性和方法全部动态化处理了,不需要任何特殊手段。这样完全动态调用的特性的作用是,可以针对完全动态的情况做调用。
'''
举个例子,现在很多网站都搞REST API ,比如新浪微博、豆瓣等,调用API的URL类似:
http://api.server/user/frends
http://api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SKD也要改。
利用完全动态的_getattr__(),可以写出一个链式调用
'''
class Chain(object):
def __init__(self,path = ''):
self._path = path def __getattr__(self, path):
return Chain('%s/%s' % (self._path,path)) def __str__(self):
return self._path __repr__ = __str__ print (Chain().status.user.timeline.list) #/status/user/timeline/list
#这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变! #__call__
#一个对象实例可以有自己的属性个方法,当调用实例方法,用instance.method().能不能直接在实例本身上调用?在python中答案是肯定的。
class Student10(object):
def __init__(self,name):
self.name = name def __call__(self, *args, **kwargs):
print('my name is %s' % self.name)
s = Student10('wuxe')
print (s()) #my name is wuxe
'''
__call__()还可以定义参数,对实例进行直接嗲用就好比对一个函数进行调用一样,所以完全可以把对象看成函数,把函数看成对象,因为这二者之间本来就没啥区别。
如果把对象看成函数,那么函数本身其实也可以在运行期间动态创建出来,因为类的实例都是运行期间创建出来的。这么以来,就模糊了对象和函数界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个callable对象
'''
print (callable(Student10('wxue'))) #True
print (callable(max)) #True
print (callable([1,2,3])) #False
print (callable(None)) #False
print (callable('str')) #False #【枚举类】
'''
当我们需要定义常量,一个办法是大写变量用过整数来定义,例如月份:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
好处是简单,缺点是类型是int,并且仍然是变量。
更好的办法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的唯一实例。python提供了Enum
'''
from enum import Enum Month = Enum('Month',('Jan','Feb','Mar', 'Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'))
for name,member in Month.__members__.items():
print (name,'=>',member,',',member.value)
'''
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
'''
#【解析】value属性是自动赋值给成员的int常量,默认从1开始。 #如果需要更精确的控制枚举类型,可以从Enum派生出自定义类。
from enum import Enum,unique @unique #帮助检出保证没有重复值
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6 day1 = Weekday.Mon
print (day1) #Weekday.Mon
print (Weekday.Tue) #Weekday.Tue
print (Weekday['Tue']) #Weekday.Tue
print (Weekday.Tue.value) #2
print (day1 == Weekday.Mon) #True
print (day1 == Weekday.Tue) #False
print (Weekday(1)) #Weekday.Mon
#既可以用成员名称引用枚举常量,又可以直接根据value获得枚举常量。
print (day1 == Weekday(1)) #True
for name,member in Weekday.__members__.items():
print (name ,'=>' ,member)
'''
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
'''
#print (Weekday(7)) #ValueError: 7 is not a valid Weekday # 练习 把Student 的gender 属性改造成枚举类型,可以避免使用字符串。
from enum import Enum,unique @unique
class Gender(Enum):
male = 0
female = 1
class Student11(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
wxue = Student11('wxue',Gender.female)
print (wxue.gender == Gender.female) #True # 【元类】
# 【元类】type()
#动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。比如要定义一个Hello 的class,就写一个hello.py模块(此处做法:同级目录下创建hello.py)
from hello import Hello h = Hello()
h.hello() #Hello ,world.
print (type(Hello)) #<class 'type'>
print (type(h)) #<class 'hello.Hello'>
'''
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello
我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数
type()函数既可以返回一个对象的类型,也可以创建出新的类型。比如。可以通过type()创建出Hello类,而无需通过class Hello(object)...的定义
'''
def fn(self,name = 'world'):
print ("Hello,%s" % name)
Hello = type('Hello',(object,),dict(hello=fn))
h = Hello()
h.hello() #Hello,world
print (type(Hello)) #<class 'type'>
print (type(h)) #<class '__main__.Hello'>
'''
【解析】要创建一个class对象,type()函数依次传入3个参数
1 class名称
2 继承的父类集合,注意python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
3 class的方法名称和函数绑定,这里把函数fn绑定到方法名hello上
通过type()函数创建的类和直接写 class 是完全一样的,因为python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type函数创建出class。
正常情况下,我们都用class Xxx....定义类,但是,type()函数也允许我们动态创建出类,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,
要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
''' # 【元类】metaclass
'''
除了使用type()动态创建类,要控制类的创建行为,还可用metaclass
metaclass 直译为元类,简单的解释就是:
当我们定义了类后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass, 然后创建类
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建和修改类。换句话说,可以把类看成是metaclass创建出来的"实例"。
''' #例子 这个metaclass 可以给我们自定义的类MyList增加一个add方法
#定义ListMetaclass 按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚的表示这是一个metaclass
#metaclass 是类的模版,所以必须从type类派生
class ListMetaclass(type):
def __new__(cls, name,bases, attrs):
attrs['add'] = lambda self,value :self.append(value)
return type.__new__(cls,name,bases,attrs)
#有了ListMetaclass ,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass
class MyList(list,metaclass=ListMetaclass):
pass
'''我们传入关键字参数metaclass时候,魔术就生效了,它指示python解释器在创建MyList时候,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,
比如,加上新的方法啊,然后返回修改后的定义。
__new__()接收的参数依次是:
1 当前准备创建的类的对象
2 类的名字
3 类继承的父类集合
4 类的方法集合
'''
#测试一下MyList是否可以 调用add()方法
L = MyList()
L.add(1)
print (L) #[1]
#而普通的list没有add()
L2 = list()
#L2.add(2) #AttributeError: 'list' object has no attribute 'add' """
'''
动态修改有什么意义?直接在MyList上定义add()不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。
但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子
ORM全程Object Relation Mapping ,即"对象-关系映射",就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码简单,
不用直接操作SQL语句。
要编写一个ORM框架,所有的类只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
让我们尝试编写一个ORM框架。
'''
#编写框架的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码
'''class User(Model):
#定义类的属性到列的映射
id = IntegerField('id')
name = StringField('name')
email = StringField('email')
password = StringField('password') #创建一个实例
u = User(id = 12345, name = 'Michael',email = 'test@orm.org',password = 'my-pwd')
#保存到数据库
u.save()
''' '''
其中,父类Model和属性类型IntegerField StringField 是由框架ORM提供的,剩下的魔术方法比如save()全部由metaclass 自动完成。虽然
metaclass的编写会比较麻烦,但ORM的使用者用起来会非常简单。
'''
#首先定义Field类,负责保存数据库表的字段名和字段类型
class Field(object):
def __init__(self,name,column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<%s:%s>' % (self.__class__.__name__,self.name)
#在Field的基础上,继续定义各种类型的Field,比如StringField,IntegerField
class StringField(Field):
def __init__(self,name):
super(StringField, self).__init__(name,'varchar(100)')
class IntegerField(Field):
def __init__(self,name):
super(IntegerField,self).__init__(name,'bigint')
#下一步,就是编写最复杂的ModelMetaclass
class ModelMetaclass(type):
def __new__(cls,name,bases,attrs):
if name == 'Model':
return type.__new__(cls,name,bases,attrs)
print ('Found model : %s' % name)
mappings = dict()
for k,v in attrs.items():
if isinstance(v,Field):
print ('Found mapping : %s ==> %s' % (k,v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings #保存属性和列的映射关系
attrs['__table__'] = name #假设表名和类名一致
return type.__new__(cls,name,bases,attrs)
# 基类
class Model(dict,metaclass=ModelMetaclass):
def __init__(self,**kw):
super(Model,self).__init__(**kw)
def __getattr__(self, item):
try:
return self[item]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % item)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k,v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self,k,None))
sql = 'insert into %s (%s ) values (%s)' % (self.__table__,','.join(fields),','.join(params))
print ('SQL : %s' % sql)
print ('ARGS: %s' % str(args)) class User(Model):
# 定义类的属性到列的映射
id = IntegerField('id')
name = StringField('name')
email = StringField('email')
password = StringField('password') u = User(id = 12345,name = 'Michael',email = 'test@orm.org',password = 'my_pass')
u.save()
'''
Found model : User
Found mapping : id ==> <IntegerField:id>
Found mapping : name ==> <StringField:name>
Found mapping : email ==> <StringField:email>
Found mapping : password ==> <StringField:password>
SQL : insert into User (id,name,email,password ) values (?,?,?,?)
ARGS: [12345, 'Michael', 'test@orm.org', 'my_pass']
'''
'''
【解析】
当用户定义一个class User(Model)时候,Python解释器首先在当前类User的定义中查找metaclass,如果没找到,就继续
在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,
metaclass可以隐式的继承到子类,但子类自己却感觉不到。
在ModelMetaclass一共作了几件事情:
1 删除掉对Model类的修改
2 在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到__mappings__的dict中,同时从
类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性)
3 把表名保存到__table__中,这里简化为表名默认为类名。
在Model类中,就可以定义各种操作数据库的方法,比如save() delete() find() update()等
我们实现了save(),把一个实例保存到数据库中。因为有表名,属性到字段的映射,属性值的集合,就可以构造出insert语句。 '''
【符合Python风格的对象】
"""
from array import array
print(bytes([9])) #当source参数是一个可迭代对象,那么这个迭代对象的元素都必须符合0 <= x < 256,以便可以初始化到数组里
print(bytes(array('d',(1,2))))
'''
首先引入
from array import array
然后list到array直接传参数进构造函数就可以。(不知道是不是叫构造函数)
np.array('d',[1,2,3])
转回来的话调用tolist函数
_.tolist()
'''
tuple1 = (2,3)
g1 = (i for i in tuple1)
print(g1)
#读取生成器元素方法一:下面这两行
#x,y= g1
#print(x,y) #2 3
#读取生成器元素方法二::下面这两行
#print(next(g1))
#print(next(g1))
'''
2
3
'''
g1 = (i for i in (2,3))
print(*g1) #2 3
#例子9-4 比较classmethod和staticmethod
class Demo:
@classmethod
def klassmeth(*args):
return args
@staticmethod
def statmeth(*args): #行为和普通函数相似
return args
print(Demo.klassmeth()) #(<class '__main__.Demo'>,)
print(Demo.klassmeth('spam')) #(<class '__main__.Demo'>, 'spam')
print(Demo.statmeth()) #()
print(Demo.statmeth('spam')) #('spam',) """
#9.2 再谈向量类
#例子9-2 定义的都是特殊方法
import array
import math
class Vector2d:
typecode = 'd'
'''
def __init__(self,x,y):
self.x = float(x) #转换称浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数
self.y = float(y)
'''
def __iter__(self):
return (i for i in (self.x,self.y)) #把Vector2d实例变成可迭代对象,这样才能拆包(例如,x,y = my_vector)。
def __repr__(self):
class_name = type(self).__name__
return '{}({!s},{!s})'.format(class_name,*self) #因为Vector2d实例是可迭代对象,所以**self会把x和y分量提供
def __str__(self):
return str(tuple(self)) #用可迭代实例可生成元祖
def __bytes__(self): #python有个独特的特性:类属性可用于为实例属性提供默认值。虽然此时实例并无属性typecode,但是此时它是默认取自Vector2d.typecode
#但是。如果为不存在的实例属性赋值,会新建实例属性。假如我们为typecode实例属性赋值,那么同名类属性不受影响。然而,自此之后,实例读取的self.typecode是实例属性,也就是
#把同名类属性覆盖了(但通过类访问这个属性,值还是不变的)。借助这一特性,可以为各个实例的typecode属性定制不同的值
return (bytes([ord(self.typecode)]) + bytes(array.array(self.typecode,self))) #为了生成自己序列,我们typecode转换称字节序列,然后迭代Vector2d实例,得到一个数组,再把数组转换称字节序列
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.hypot(self.x,self.y) #为了比较所有分量,构建元祖。对Vector2d实例来说,可以这样做,不过仍有问题。有了这种方式,再两个操作数都是Vector2d实例时可用,不过那Vector2d实例与其他
#具有相同数值的可迭代对象相比,结果也是True(如Vector2d(3,4) == [3,4])。这个行为可视作特性,也可视作缺陷。以后讲到运算符重载时才能进一步讨论
def __bool__(self):
return bool(abs(self)) def angle(self): #计算角度
return math.atan2(self.x,self.y) def __format__(self, format_spec=''): #自定义格式代码:如果格式说明拂以'p'结尾,那么在极坐标中显示向量,即<r,x>,其中r是模,x是弧度
if format_spec.endswith('p'):
format_spec = format_spec[:-1]
coords = (abs(self),self.angle())
outer_fmt = '<{},{}>'
else:
coords = self
outer_fmt = '({},{})'
components = (format(c,format_spec) for c in coords)
return outer_fmt.format(*components) #可散列的:属性必需是不变的,所以要将实例的两个属性设置成只读,因为现在我们还可以通过v1.x 的方式为属性赋值. 【步骤】1⃣️#修改构造方法2⃣️ #还要设置@property3⃣️实现__hash__方法(只有不可变的向量才能实现这个方法)
#这个方法应该返回一个整数,理想情况下还要考虑对象属性的散列值(__eq__方法也要使用),因为相等的对象应该具有相同的散列值。
#根据官方文旦,最好使用位运算符异或混合各分量的散列值 def __init__(self,x,y):
self.__x = float(x)
self.__y = float(y) @property
def x(self): #这样就可以通过self.x来把它当公开属性读取
return self.__x
@property
def y(self):
return self.__y def __hash__(self):
return hash(self.x)^hash(self.y) #这里可以直接用self.x代替self.__x是因为用@property设置了读取方法,所以其他方法也是通过这样来读取公开属性 """
v1 = Vector2d(3,4)
print(v1.x,v1.y) #3.0 4.0 .实例分量可直接通过属性访问(无需调用读值方法)
x,y = v1
print(x,y) #3.0 4.0 .实例可拆包成变量元祖
print(v1) #(3.0, 4.0) .print函数会调用str函数
v1 #在控制台,这里会打印出:Vector2d(3.0,4.0)
v1_clone = eval(repr(v1)) #这里用eval函数,表明repr函数调用Vector2d实例得到的是对构造方法的准确表述 【调用eval,传入repr后的对象,相当于调用__str__()】
print(v1_clone == v1) #True
print(abs(v1)) #5.0
print(bool(v1),bool(Vector2d(0,0))) #True False
octets = bytes(v1)
print(octets) #b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
print(Vector2d.frombytes(b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'))
#验证格式化
print(format(Vector2d(1,1),'p')) #<1.4142135623730951,0.7853981633974483>
print(format(Vector2d(1,1),'.3ep')) #<1.414e+00,7.854e-01>
print(format(Vector2d(1,1),'0.5fp')) #<1.41421,0.78540> #验证散列性
v3 = Vector2d(3,4)
v4 = Vector2d(3.1,4.2)
v5 = Vector2d(3,4)
print(hash(v3),hash(v4)) #7 384307168202284039
print(hash(v3),hash(v5)) #7 7
print(v3 == v5) #True #9.7私有属性和"受保护的"属性
v6 = Vector2d(3,4)
print(v6.__dict__)
print(v6._Vector2d__x)
v6._Vector2d__x = 5.0
print(v6.__dict__)
#这样看来,太危险了,同时还有人不喜欢这个语法,所以出现了受保护的属性,python解释器虽然不会对使用单个下划线的属性做特殊处理,但是很多python程序员严格遵守这个约定,不会在类外部访问单下划线这种受保护的属性(约定这是私有的) #9.8 使用__slots__类属性节省空间:只有在需要处理大数据时才能显现出价值,正常情况下就不要这么麻烦的取创建这种不寻常的类了
#默认情况下,python在各个实例中名为__dict__的字典里存储实例属性。为了使用低层的散列表提升访问速度,字典会消耗大量内存。如果要处理数百外各属性不多的实例,这种方式可节省大量内存,方法是让解释器在元祖中存储实例属性,而不用字典
#子类不继承,只有当前类有效
class V:
__slots__ = ('__x','__y')
#不过,也有要注意的
#1⃣️ 每个子类都要定义__slots__属性,因为解释器会忽略继承的
#2⃣️实例只能拥有__slots__中列出的属性,除非把'__dict__'加入__slots__中,不过这样做就失去了节省内存的功效
#3⃣️如果不把__weakref__加入__slots__,实例就不能作为弱引用的目标 #9.9 覆盖类属性
#例子9-13 设定从类中继承的typecode属性,自定义一个实例属性
v7 = Vector2d(1.1,2.2)
dumpd = bytes(v7)
print(dumpd)
print(len(dumpd))
v7.typecode = 'f'
dumpf = bytes(v7)
print(dumpf)
print(len(dumpf))
'''
b'd\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@'
17
b'f\xcd\xcc\x8c?\xcd\xcc\x0c@'
9
'''
#例子9-14 ShortVector2d是Vector2d的子类,只用于覆盖typecode的默认值
class ShortVector2d(Vector2d):
typecode = 'f'
sv = ShortVector2d(1/11,1/27)
print(sv) #(0.09090909090909091, 0.037037037037037035)
print(len(bytes(sv))) #9
#【分析】这也说明了我在Vector2d.__repr__方法中为什么没有硬编码class_name的值,而是使用type(self).__name__,如果硬编码的话,那么Vector2d的子类要覆盖__repr__方法,只是为了修改class_name的值。从实例的类型中
#读取类名,__repr__方法就可以放心继承
"""
【流畅的Python】【重载运算符】
#
#例子13-3 一元运算符+得到一个新Counter实例,但是没有零值和负值计数器
from collections import Counter ct = Counter('abracadabra')
#print(ct) #Counter({'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1})
ct['r'] = -3
ct['d'] = 0
#print(ct) #Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3})
#print(+ct) #Counter({'a': 5, 'b': 2, 'c': 1}) import itertools
a = (1,2,3)
b = [4,5]
d = itertools.zip_longest(a,b,fillvalue=0.0) #以下是Vector from array import array
import reprlib
import math
import numbers
import functools
import operator
import itertools class Vector:
typecode = 'd' def __init__(self, components):
self._components = array(self.typecode, components) def __iter__(self):
return iter(self._components) def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components) __str__ = __repr__ #def __str__(self):
#return str(tuple(self)) def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(self._components)) def __eq__(self, other):
return (len(self) == len(other) and
all(a == b for a, b in zip(self, other))) def __hash__(self):
hashes = (hash(x) for x in self)
return functools.reduce(operator.xor, hashes, 0) def __abs__(self):
return math.sqrt(sum(x * x for x in self)) def __bool__(self):
return bool(abs(self)) def __len__(self):
return len(self._components) def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = '{.__name__} indices must be integers'
raise TypeError(msg.format(cls)) shortcut_names = 'xyzt' def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
raise AttributeError(msg.format(cls, name)) def angle(self, n): # <2>
r = math.sqrt(sum(x * x for x in self[n:]))
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a
else:
return a def angles(self): # <3>
return (self.angle(n) for n in range(1, len(self))) def __format__(self, fmt_spec=''):
if fmt_spec.endswith('h'): # hyperspherical coordinates
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],
self.angles()) # <4>
outer_fmt = '<{}>' # <5>
else:
coords = self
outer_fmt = '({})' # <6>
components = (format(c, fmt_spec) for c in coords) # <7>
return outer_fmt.format(', '.join(components)) # <8> @classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
'''
def __add__(self, other):
pairs = itertools.zip_longest(self,other,fillvalue=0.0)
return Vector(a+b for a,b in pairs)
#print((3,40) + v1) #TypeError: can only concatenate tuple (not "Vector") to tuple 【解析】如果左操作数是Vector之外的对象,则无法处理
#...为了支持涉及不同类型的运算,python为中缀运算符特殊方法提供了特殊的分派机制。对表达式a+b来说,解释器会执行以下几步操作
#...(1)如果a有__add__方法,而且返回值不是NotImplemented,调用a.__add__(b),然后返回结果
#...(2)如果a没有__add__方法,或者调用__add__方法返回NotImplemented,检查b有没有__radd__方法,如果有,而且没有返回NotImplemented,调用b.__radd__(a),然后返回结果
#...(3)如果前两者条件都不满足,抛出TypeError,并在错误消息中指明操作数类型不支持
#[注意]别把NotImplemented NotImplementedError搞混了。前者是特殊的单例值,如果中缀运算符特殊方法不能处理给定的操作数,那么要把它返回给解释器。后者是一种异常,抽象类中的占位方法把它抛出(raise),提醒子类必须覆盖
'''
def __add__(self, other):
try:
pairs = itertools.zip_longest(self,other,fillvalue=0.0)
return Vector(a+b for a,b in pairs)
except TypeError: #如果中缀运算符方法抛出异常,就终止了运算符分派机制。对 TypeError来说,通常最好将其捕获,然后返回 NotImplemented。这样,解释器会尝试调用反向运算符方法,如果操作数是不同的类型,对调之后,反向运算符方法可能会正确计算
return NotImplemented
def __radd__(self, other):
return self + other #__radd__通常就这么简单:直接调用适当的运算符,在这里就委托__add__。任何可交换的运算符都能这么左。处理数字和向量时,+可以交换,但是拼接序列时不行 def __mul__(self, other):
if isinstance(other,numbers.Real): # 11.6节说过,decimal.Decimal没有吧自己注册为numbers.Real的虚拟子类。因此,Vector类不会处理decimal.Decimal数字。
return Vector(n*other for n in self)
else:
return NotImplemented
def __rmul__(self, other):
return self*other def __eq__(self, other):
if isinstance(other,Vector):
return (len(self) == len(other) and all(a==b for a,b in zip(self,other)))
else:
return NotImplemented '''
#栗子 13-12 比较 def __eq__(self, other):
return (len(self) == len(other) and all(a==b for a,b in zip(self,other))) va = Vector([1.0,2.0,3.0])
vb = Vector(range(1,4))
print(va == vb) #True
t3 = (1,2,3)
print(va == t3) #True
print(1 == 1.0) #True
print(all(a==b for a,b in zip(va,t3))) #True v7 = Vector([1,2])
print(v7) #Vector([1.0, 2.0])
v8 = Vector((1,2.0))
print(zip(v7,v8)) #<zip object at 0x0000000001EDFCC8>
print(list(zip(v7,v8))) #[(1.0, 1.0), (2.0, 2.0)]
print(all(a==b for a,b in zip(v7,v8))) #True #但是我们发现下面,所以应该保守点,做类型检查。
print([1,2] == (1,2)) #False
#所以改成
#栗子13-14 def __eq__(self, other):
if isinstance(other, Vector):
return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))
else:
return NotImplemented
#再次验证
va = Vector([1.0,2.0,3.0])
vb = Vector(range(1,4))
print(va == vb) #True
print(va == (1,2,3)) #False
#在示例 13-14 中, Vector 实例和元组比较时,具体步骤如下。
#(1) 为了计算 va == t3, Python 调用 Vector.__eq__(va, t3)。
#(2) 经 Vector.__eq__(va, t3) 确认, t3 不是 Vector 实例,因此返回NotImplemented。
#(3) Python 得到 NotImplemented 结果,尝试调用 tuple.__eq__(t3, va)。
#(4) tuple.__eq__(t3, va) 不知道 Vector 是什么,因此返回 NotImplemented。
#(5) 对 == 来说,如果反向调用返回 NotImplemented, Python 会比较对象的 ID,作最后一搏。
''' #栗子13-11
'''
def __mul__(self, other):
if isinstance(other,numbers.Real): # 11.6节说过,decimal.Decimal没有吧自己注册为numbers.Real的虚拟子类。因此,Vector类不会处理decimal.Decimal数字。
return Vector(n*other for n in self)
else:
return NotImplemented
def __rmul__(self, other):
return self*other
'''
v5 = Vector([1.0,2.0,3.0])
#print(14*v5) #Vector([14.0, 28.0, 42.0])
#print(v5*True) #Vector([1.0, 2.0, 3.0]) v1 = Vector([3, 4, 5])
#print(v1 + (10, 20)) # (13.0, 24.0, 5.0)
#例子13-8 Vector.__add__方法的操作数钥要是可迭代对象
'''
def __add__(self, other):
pairs = itertools.zip_longest(self,other,fillvalue=0.0)
return Vector(a+b for a,b in pairs)
def __radd__(self, other):
return self + other #__radd__通常就这么简单:直接调用适当的运算符,在这里就委托__add__。任何可交换的运算符都能这么左。处理数字和向量时,+可以交换,但是拼接序列时不行
'''
v2 = Vector([3,4,5])
#print(v2 + 1) #TypeError: zip_longest argument #2 must support iteration
#例子13-9 这个加法的操作数应该是可迭代的数值对象
'''
def __add__(self, other):
pairs = itertools.zip_longest(self,other,fillvalue=0.0)
return Vector(a+b for a,b in pairs)
def __radd__(self, other):
return self + other #__radd__通常就这么简单:直接调用适当的运算符,在这里就委托__add__。任何可交换的运算符都能这么左。处理数字和向量时,+可以交换,但是拼接序列时不行
'''
#print(v2 + 'ABC') #TypeError: unsupported operand type(s) for +: 'float' and 'str'
#例子13-10 针对13-8 13-9 需要再进行优化,即有错误要抛出
v3 = Vector([1,2,3])
#print((4,) + v3)
【流畅的Python】【继承】
#栗子12-1 内置类型dict 的__init__和__update__方法会忽略我们覆盖的__setitem__ class DoppelDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
dd = DoppelDict(one=1)
print(dd) #{'one': 1}
dd['two'] = 2
print(dd) #{'one': 1, 'two': [2, 2]}
dd.update(three=3)
print(dd) #{'one': 1, 'two': [2, 2], 'three': 3} #不只实例内部的调用有这个问题(self.get() 不调用 self.__getitem__()),内置类型的方法调用的其他类的方法,如果被覆盖了,也不会被调用 #栗子12-2 dict.update方法会忽略AnswerDict.__getitem__方法
class AnswerDict(dict):
def __getitem__(self, item):
return 42
ad = AnswerDict(a='foo')
print(ad['a']) #42
d = {}
d.update(ad)
print(d['a']) #foo
print(d) #{'a': 'foo'} import collections class DoppelDict2(collections.UserDict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
dd = DoppelDict2(one=1)
print(dd) #{'one': [1, 1]}
dd['two'] = 2
print(dd) #{'one': [1, 1], 'two': [2, 2]}
dd.update(three=3)
print(dd) #{'one': [1, 1], 'two': [2, 2], 'three': [3, 3]}
class AnswerDict2(collections.UserDict):
def __getitem__(self, item):
return 42
ad = AnswerDict2(a='foo')
print(ad['a']) #42
d = {}
d.update(ad)
print(d['a']) #42
print(d) #{'a': 42} #【小结】
'''
综上,本节所述的问题只发生在 C 语言实现的内置类型内部的方法委托上,而且只影响
直接继承内置类型的用户自定义类。如果子类化使用 Python 编写的类(collections 模块),如 UserDict 或
MutableMapping,就不会受此影响
''' #12.2 多重继承和方法解析顺序
#规则:按照__mro__(方法解析)顺序,若直接用类名调用方法则直接找到该类,建议使用super()[因为最安全,也不容易过时。]
print(bool.__mro__) #(<class 'bool'>, <class 'int'>, <class 'object'>)
【__new__ 和 __init__】
"""
知识点: 继承自object的新式类才有__new__ __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供 __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例 __init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值 若__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行
""" """
class A(object):
def __init__(self):
print('init') def __new__(cls, *args, **kwargs):
print('new %s' % cls)
return object.__new__(A,*args,**kwargs) print(A())
#结果
'''
new <class '__main__.A'>
init
<__main__.A object at 0x0000000002779128>
''' class A(object):
def __init__(self):
print('init') def __new__(cls, *args, **kwargs):
print('new %s' % cls)
return object.__new__(A,*args,**kwargs)
print(A())
#结果
'''
new <class '__main__.A'>
init
<__main__.A object at 0x0000000002779128>
''' class A(object):
pass class B(A):
def __init__(self):
print('init') def __new__(cls, *args, **kwargs):
print('new %s '% cls)
return A.__new__(cls,*args,**kwargs)
print(B())
#结果
'''
new <class '__main__.B'>
init
<__main__.B object at 0x00000000027A9128>
''' class A(object):
pass class B(A):
def __init__(self):
print('init') def __new__(cls, *args, **kwargs):
print('new %s' % cls)
return A.__new__(B,*args,**kwargs)
print(B())
#结果
'''
new <class '__main__.B'>
init
<__main__.B object at 0x00000000027A9128>
''' class A(object):
pass class B(A):
def __init__(self):
print('init')
def __new__(cls, *args, **kwargs):
print('new %s' % cls)
return object.__new__(A,*args,**kwargs)
print(B())
#结果
'''
new <class '__main__.B'>
<__main__.A object at 0x0000000001E69128>
'''
"""
【Python】【面向对象】的更多相关文章
- python 面向对象初级篇
Python 面向对象(初级篇) 概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发" ...
- Python 面向对象 基础
编程范式概述:面向过程 和 面向对象 以及函数式编程 面向过程:(Procedure Oriented)是一种以事件为中心的编程思想. 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现 ...
- python面向对象进阶(八)
上一篇<Python 面向对象初级(七)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- python 面向对象(进阶篇)
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- python 面向对象编程学习
1. 问题:将所有代码放入一个py文件:无法维护 方案:如果将代码才分放到多个py文件,好处: 1. 同一个名字的变量互相不影响 2.易于维护 3.引用模块: import module 2.包:解决 ...
- Python面向对象详解
Python面向对象的"怜人之处" Python的待客之道--谁能进来 Python的封装--只给你想要的 Python的继承--到处认干爹 Python的多态--说是就是
- python 面向对象和类成员和异常处理
python 面向对象 你把自己想象成一个上帝,你要创造一个星球,首先你要把它揉成一个个球,两个直径就能创造一个球 class star: '''名字(name),赤道直径(equatorial di ...
- python 面向对象学习
------Python面向对象初 下面写一个类的简单实用,以便方便理解类 #python 3.5环境,解释器在linux需要改变 #阅读手册查询readme文件 #作者:S12-陈金彭 class ...
- 初识python面向对象
一.初识python面向对象: class Person: #使用class关键字定义一个类 age=0 #类变量(静态变量) def eat(self,food): #定义一个方法 self.age ...
- python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异
1.python 面向对象 文章内容摘自:http://www.cnblogs.com/vamei/archive/2012/06/02/2532018.html 1.__init__() 创建对 ...
随机推荐
- 深入剖析Kubernetes k8s
深入剖析Kubernetes k8s 毫无疑问,Kubernetes 已经成为容器领域当之无愧的事实标准.除了 Google.Microsoft 等技术巨擘们在容器领域里多年的博弈外,国内的 BAT. ...
- ASP.NET定时调用WebService 运行后台代码
效果: 通过在网站的Global.asax的Application_Start方法中 加入定时器 定时调用WebService 该WebService的一个方法 负责在后台 向数据库的某个表加入数据 ...
- SpringMVC中参数接收
/** * * SpringMVC中参数接收 * 1.接收简单类型 int String * 2.可以使用对象pojo接收 * 3.可以使用集合数据接收参数 * 页面: name="ids ...
- MediaCodec在Android视频硬解码组件的应用
https://yq.aliyun.com/articles/632892 云栖社区> 博客列表> 正文 MediaCodec在Android视频硬解码组件的应用 cheenc 201 ...
- git学习总结 - 纯命令
全局安装git: npm intall git -g 查看git版本: git --version 进入目录,初始化git: 若在目录中使用上一个,不在目录中使用下一个. //已有目录: git in ...
- 01:jQuery的下拉选select2插件用法
1.1 select2插件基本使用 1.下载select2插件 1. 下载地址:https://github.com/select2/select2 2.官网地址:https://select2.or ...
- python简说(二十一)开发接口
一.flask举例 import flaskserver = flask.Flask(__name__)#新建一个服务,把当前这个python文件当做一个服务@server.route('/login ...
- 20145319 《计算机病毒》动态分析lab3-2
20145319 <计算机病毒>动态分析lab3-2(实践六) 实践过程 基础分析 拿到恶意代码时,首先使用PE ID打开,查看其中一些基础信息以及观察该恶意代码是否加壳,来确定下一步分析 ...
- 前后台分离开发--文件上传与下载,cookie,session
一.前后台分离开发的概念 ''' 1. 前台页面运行在前台服务器上,负责页面的渲染(静态文件的加载)与转跳 2. 后台代码运行在后台服务器上,负责数据的处理(提供数据请求的接口) ''' #如果没有前 ...
- django基础 -- 3. urls.py view.py 参数 别名 重定向 常用方法 静态文件
一.基本格式 from django.conf.urls import url from . import views #循环urlpatterns,找到对应的函数执行,匹配上一个路径就找到对应的函数 ...