全⾯深⼊了解python(⼀)
全⾯深⼊了解python(⼀)
写在开始前,此教程不是基础教程,在看之前你需要有⼀定的python基础,不然你可能⽆法理解教程到底教了哪些东西。
环境:python版本是3.6.5(>=3.4即可)
怎样才能化好妆1. Python数据模型
数据模型其实是对Python框架的描述,它规范了这门语⾔⾃⾝构建模块的接⼝,这些模块包括但不限于序列、迭代器、函数、类和上下⽂管理器。
Python解释器碰到特殊的句法时,会使⽤特殊⽅法去激活⼀些基本的对象操作,这些特殊⽅法的名字以两个下划线开头,以两个下划线结尾(例如__getitem__)。⽐如obj[key]的背后就是__getitem__⽅法,为了能求得my_collection[key]的值,解释器实际上会调
⽤my_collection.__getitem__(key)。
上⾯说的特殊⽅法其实有个昵称,可能你以前就听过,叫做魔术⽅法。这些魔术⽅法能让你⾃⼰的对象实现和⽀持以下的语⾔结构,并与之交互:
迭代
集合类
属性访问
运算符重载
函数和⽅法的调⽤
对象的创建和销毁
字符串表⽰形式和格式化
管理上下⽂(即with块)
1.1 ⼀叠Python风格的纸牌
接下来⽤⼀个⾮常简单的例⼦来展⽰如何实现__getitem__和__len__这两个特殊⽅法,通过这个例⼦我们也能见识到特殊⽅法的强⼤。import collections
Card = collections.namedtuple('Card',['rank','suit'])
class OrderCard:
ranks =[str(n)for n in range(2,11)]+list('JQKA')
suits ='spades diamonds clubs hearts'.split()
def__init__(self):
self._cards =[Card(rank,suit)for suit in self.suits for rank in self.ranks]
def__len__(self):
return len(self._cards)
def__getitem__(self, position):
return self._cards[position]
⾸先,我们⽤collections.namedtuple构建了⼀个简单的类来表⽰⼀张纸牌。⾃Python 2.6开始,namedtuple就加⼊到Python⾥,⽤以构建只有少数属性但是没有⽅法的对象,⽐如数据库条⽬。使⽤终端进⾏样例的输⼊,利⽤namedtuple,我们可以很轻松地得到⼀个纸牌对象:
>>> beer_card = Card('7','diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')
当然,我们这个例⼦主要还是关注OrderCard这个类,很短⼩精悍。⾸先,它跟任何标准Python集合类型⼀样,可以⽤len()函数来查看⼀叠牌有多少张:
>>> order_card = OrderCard()
>>>len(order_card)
52
从⼀叠牌中抽取特定的⼀张纸牌,⽐如说第⼀张或者最后⼀张,是很容易的:order_card[0]或order_card[-1]。这都是由__getitem__⽅法提供的:
>>> order_card[0]
Card(rank='2', suit='spades')
>>> order_card[-1]
Card(rank='A', suit='hearts ')
我们需要单独写⼀个⽅法⽤来随机抽取⼀张纸牌吗?没必要,Python已经内置了从⼀个序列中随机选出⼀个元素的函数random.choice,我们直接把它⽤在这⼀叠纸牌实例上就好:
>>>from random import choice
>>> choice(order_card)
Card(rank='8', suit='spades')列举三种植物传播种子的方式
>>> choice(order_card)
Card(rank='3', suit='hearts ')
>>> choice(order_card)
Card(rank='Q', suit='hearts ')
现在已经可以体会到通过实现魔术⽅法来利⽤Python数据模型的两个好处。
作为你的类的⽤户,他们不必去记住标准操作的各种名称(“怎么得到元素的总数?是.size()还是.length()还是别的什么?”)
可以更加⽅便地利⽤Python的标准库,⽐如random.choice函数,从⽽不⽤重新发明轮⼦。
因为__getitem__⽅法把[]操作交给了self._cards列表,所以我们的deck类⾃动⽀持切⽚(slicing)操作。下⾯列出了查看⼀叠牌最上⾯3张和只看排⾯是K的牌的操作。其中第⼆种操作的具体⽅法是,先抽出索引是11的那张牌,然后每隔13张牌拿1张:
>>> order_card[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> order_card[11::13]#前⾯是索引,后⾯是步长
[Card(rank='K', suit='spades'), Card(rank='K', suit='diamonds '), Card(rank='K', suit='clubs '), Card(rank='K', suit='hearts ')]
另外,仅仅实现了__getitem__⽅法,这⼀叠牌就变成可迭代的了:
>>>for card in order_card:
...print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...
反向迭代也没关系:
>>>for card in reversed(order_card):
...print(card)
Card(rank='A', suit='hearts ')
Card(rank='K', suit='hearts ')
Card(rank='Q', suit='hearts ')
...
迭代通常是隐式的,⽐如说⼀个集合类型没有实现__contains__⽅法,那么in运算符就会按顺序做⼀次迭代搜索。于是,in运算符可以⽤在我们的OrderCard类上,因为它是可迭代的:
>>> Card('3','spades')in order_card
True
>>> Card('K','test')in order_card
False
那么排序呢?我们⽤点数来判定扑克牌的⼤⼩,2最⼩、A最⼤;同时还要加上对花⾊的判定,⿊桃最⼤、红桃次之、⽅块再次、梅花最⼩。下⾯就是按照这个规则来给扑克牌排序的函数,梅花2的⼤⼩是0,⿊桃A是51:
suit_values =dict(spades=3,hearts=2,diamonds=1,clubs=0)
def spades_high(card):
rank_value = OrderCard.ranks.index(card.rank)
return rank_value *len(suit_values)+ suit_values[card.suit]
感恩节是几月几日有了spades_high函数,就能对这叠牌进⾏升序排序了:
>>>for card in sorted(order_card,key=spades_high):违约金定金
...print(card)
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
...
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')
虽然OrderCard隐式地继承了object类,但功能却不是继承⽽来的。我们通过数据模型和⼀些合成来实现这些功能。通过实
现__len__和__getitem__这两个特殊⽅法,OrderCard就跟⼀个Python⾃由的序列数据类型⼀样,可以体现出Python的核⼼语⾔特性(例如迭代和切⽚)。同时这个类还可以⽤于标准库中如:random.choice、reversed和sorded这些函数。另外,对合成的运⽤
使__len__和__getitem__的具体实现可以代理给self._cards
这个python列表(即list对象)。
1.2 如何使⽤特殊⽅法(魔术⽅法)
⾸先明确⼀点,魔术⽅法的存在是为了被python解释器调⽤的,你⾃⼰并不需要调⽤它们。也就是说
没有my_object.__len__()这种写法,⽽应该使⽤len(my_object)。在执⾏len(my_object)的时候,如果my_object是⼀个⾃定义类的对象,那么python会⾃⼰去调⽤其中由你实现的__len__⽅法。
通常代码中⽆需直接使⽤魔术⽅法,除⾮有⼤量的元编程存在,唯⼀的例外是经常使⽤__init__⽅法,⽬的是在代码的⼦类的__init__⽅法中调⽤超类的构造器。通过内置函数(len、iter、str等等)来使⽤魔术⽅法是最好的选择,这些内置函数不仅会调⽤魔术⽅法,对于内置类来说,运⾏它们速度更快。
还有⼀点,不要⾃⼰想当然的随意添加魔术⽅法,⽐如__foo__之类的,因为现在没有被Python内部使⽤,不代表以后不被⽤。
来实现⼀个⼆维向量(vector)类,这⾥的向量就是欧⼏⾥得⼏何中常⽤的概念,常在数学和物理中使⽤。
python内置的complex类可以⽤来表⽰向量,但是我们⾃定义的类可以扩展到n维向量。
取消值机⾃定义Vector类:
from math import hypot
class Vector:
def__init__(self,x=0,y=0):
self.x = x
self.y = y
def__repr__(self):
return f'Vector({self.x},{self.y})'# 3.6新功能,新的格式化⽅式
def__abs__(self):
return hypot(self.x,self.y)
def__bool__(self):
return bool(abs(self))
def__add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x,y)
def__mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
把上图的样例实现出来:
>>> v1 = Vector(2,4)
>>> v2 = Vector(2,1)
>>> v1 + v2
佛的多音字组词Vector(4,5)
通过+运算符所得到的结果也是⼀个向量,⽽且结果会被控制台友善的打印出来。
abs是⼀个内置函数,如果输⼊是整数或者浮点数,返回值是输⼊值的绝对值,输⼊值是复数,返回值应该是该复数的模,因此定义该函数时,也应该返回该向量的模:
>>> v = Vector(3,4)
>>>abs(v)
5.0
我们还可以利⽤*运算符来实现向量的标量乘法:
>>> v *3
Vector(9,12)
>>>abs(v *3)
15.0
实现的Vector类中是由:__repr__、__abs__、__add__、__mul__这些特殊⽅法实现。但是在上述使⽤中发现除了__init__会被使⽤之外,其他的魔术⽅法是被python解释器直接调⽤,⽽不是⾃⼰使⽤代码调⽤。(上⽂已经提过)
1.3 为什么len不是普通⽅法
如果x是⼀个内置类型的实例,那么len(x)的速度会⾮常快,背后的原因是CPython会直接从⼀个C结构体⾥读取对象的长度,完全不会调⽤任何⽅法。获取⼀个集合中元素的数量是⼀个很常见的操作,在str、list、memoryview等类型上,这个操作必须⾼效。
c语⾔中的结构体⾥可以存储这个对象的相关数据信息,是其属性,可以直接读取。例:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论