此时被导入模块若定义了all属性,则只有all内指定的属性、方法、类可被导入;若没定义,则导入模块内的所有公有属性,方法和类。
Python中有许多内置函数,不像print、len那么广为人知,但它们的功能却异常强大,用好了可以大大提高代码效率,同时提升代码的简洁度,增强可阅读性
Counter
collections在python官方文档中的解释是High-performance container datatypes,直接的中文翻译解释高性能容量数据类型。这个模块实现了特定目标的容器,以提供Python标准内建容器 dict , list , set , 和 tuple 的替代选择。在python3.10.1中它总共包含以下几种数据类型:
容器名简介
namedtuple() 创建命名元组子类的工厂函数
deque 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)
ChainMap 类似字典(dict)的容器类,将多个映射集合到一个视图里面
Counter 字典的子类,提供了可哈希对象的计数功能
OrderedDict 字典的子类,保存了他们被添加的顺序
defaultdict 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值
UserDict 封装了字典对象,简化了字典子类化
UserList 封装了列表对象,简化了列表子类化
UserString 封装了字符串对象,简化了字符串子类化
其中Counter中文意思是计数器,也就是我们常用于统计的一种数据类型,在使用Counter之后可以让我们的代码更加简单易读。Counter类继承dict类,所以它能使用dict类里面的方法
举例
#统计词频
fruits = ['apple', 'peach', 'apple', 'lemon', 'peach', 'peach']
result = {}
for fruit in fruits:
if not result.get(fruit):
result[fruit] = 1
else:
result[fruit] += 1
print(result)
#{'apple': 2, 'peach': 3, 'lemon': 1}下面我们看用Counter怎么实现:
from collections import Counter
fruits = ['apple', 'peach', 'apple', 'lemon', 'peach', 'peach']
c = Counter(fruits)
print(dict(c))
#{'apple': 2, 'peach': 3, 'lemon': 1}显然代码更加简单了,也更容易阅读和维护了。
elements()
返回一个迭代器,其中每个元素将重复出现计数值所指定次。元素会按首次出现的顺序返回。如果一个元素的计数值小于1,elements()将会忽略它。
>>>c = Counter(a=4, b=2, c=0, d=-2)
>>>sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']most_common([n])
返回一个列表,其中包含n个最常见的元素及出现次数,按常见程度由高到低排序。如果n被省略或为None,most_common()将返回计数器中的所有元素。计数值相等的元素按首次出现的顺序排序:
>>>Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]这两个方法是Counter中最常用的方法,其他方法可以参考 python3.10.1官方文档
实战
Leetcode 1002.查找共用字符
给你一个字符串数组words,请你找出所有在words的每个字符串中都出现的共用字符(包括重复字符),并以数组形式返回。你可以按任意顺序返回答案。
输入:words = ["bella", "label", "roller"]
输出:["e", "l", "l"]
输入:words = ["cool", "lock", "cook"]
输出:["c", "o"]看到统计字符,典型的可以用Counter完美解决。这道题是找出字符串列表里面每个元素都包含的字符,首先可以用Counter计算出每个元素每个字符出现的次数,依次取交集最后得出所有元素共同存在的字符,然后利用elements输出共用字符出现的次数
class Solution:
def commonChars(self, words: List[str]) ->List[str]:
from collections import Counter
ans = Counter(words[0])
for i in words[1:]:
ans &= Counter(i)
return list(ans.elements())提交一下,发现83个测试用例耗时48ms,速度还是不错的
sorted
在处理数据过程中,我们经常会用到排序操作,比如将列表、字典、元组里面的元素正/倒排序。这时候就需要用到sorted(),它可以对任何可迭代对象进行排序,并返回列表
对列表升序操作:
a = sorted([2, 4, 3, 7, 1, 9])
print(a)
# 输出:[1, 2, 3, 4, 7, 9]对元组倒序操作:
sorted((4,1,9,6),reverse=True)
print(a)
# 输出:[9, 6, 4, 1]使用参数:key,根据自定义规则,按字符串长度来排序:
fruits = ['apple', 'watermelon', 'pear', 'banana']
a = sorted(fruits, key = lambda x : len(x))
print(a)
# 输出:['pear', 'apple', 'banana', 'watermelon']all
all() 函数用于判断给定的可迭代参数iterable中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。元素除了是 0、空、None、False外都算True。注意:空元组、空列表返回值为True。
>>>all(['a', 'b', 'c', 'd']) # 列表list,元素都不为空或0
True
>>>all(['a', 'b', '', 'd']) # 列表list,存在一个为空的元素
False
>>>all([0, 1,2, 3]) # 列表list,存在一个为0的元素
False
>>>all(('a', 'b', 'c', 'd')) # 元组tuple,元素都不为空或0
True
>>>all(('a', 'b', '', 'd')) # 元组tuple,存在一个为空的元素
False
>>>all((0, 1, 2, 3)) # 元组tuple,存在一个为0的元素
False
>>>all([]) # 空列表
True
>>>all(()) # 空元组
Trueany函数正好和all函数相反:判断一个tuple或者list是否全为空,0,False。如果全为空,0,False,则返回False;如果不全为空,则返回True。
F-strings
在python3.6.2版本中,PEP 498提出一种新型字符串格式化机制,被称为 “字符串插值” 或者更常见的一种称呼是F-strings,F-strings提供了一种明确且方便的方式将python表达式嵌入到字符串中来进行格式化:
s1='Hello'
s2='World'
print(f'{s1} {s2}!')
# Hello World!在F-strings中我们也可以执行函数:
def power(x):
return x*x
x=4
print(f'{x} * {x} = {power(x)}')
# 4 * 4 = 16而且F-strings的运行速度很快,比传统的%-string和str.format()这两种格式化方法都快得多,书写起来也更加简单。
本文主要讲解了python几种冷门但好用的函数,更多内容以后会陆陆续续更新~
Python中的类(classes)Python的类机制使用尽可能少的新语法和语义将类引入语言。Python的类提供了面向对象程序设计语言所有的 标准特性:类继承机制允许有多个基类,一个派生类可以覆盖基类中的任何方法,一个方法可以使用相同的名字调用 基类中的方法。
Table of Contents
1 名字和对象
2 Python的域(scopes)和名称空间(namespaces)
3 初识类
3.1 定义类
3.2 类对象
3.3 实例化对象
3.4 方法对象
4 漫谈
5 派生
6 多重继承
7 私有变量和类局部引用
8 结构体
9 异常(Exceptions)也是类
10 迭代器
11 生成器(Generators)
1 名字和对象
对象有其特性,同一个对象可以有多个名字,这与其它语言中的别名很相似。别名有时候像指针,例如将对象当做 函数参数传递的时候非常高效,因为只传递了指针,这避免了pascal中的两套参数传递机制。
2 Python的域(scopes)和名称空间(namespaces)
在引入类之前,我们讲Python的域规则。类的定义巧妙地运用了名称空间,所以你需要知道域和名称空间如何工作才能理解发生了什么。
首先从定义开始。 名称空间是名字和对象之间的映射。多数名称空间使用Python的字典来实现,但除非出于性能考虑,我们通常不关心具体如何实现。名称空间的例子有,内置的名称例如abs(),内置的异常名,模块的全局名称,函数调用时的局部名称。在某种程度上,对象的属性也构成名称空间。
关于名称空间最重要的一点是:不同名称空间中的名称没有关系。例如 两个不同模块中都可以包含名为maximize的函数,这不会造成混肴,因为使用这些模块时必须加上模块名作为前缀。 另外,我把任何点后的名称叫做属性。例如,在表达式z.real中,real是对象z的属性。严格来说,引用模块中的名称是对属性的引用,在表达式modname.funcname中,modname是一个模块,funcname是它的一个属性。这个例子中模块属性和模块 内定义的全局名称有着直接的映射,它们有着相同的名称空间。 属性可能是只读的或者可写的,上面的例子中,属性就是可写的,例如:modname.the_ answer = 42.可写的属性可以被删除, 例如 del modname.the_ answer 会删除模块 modname中的 the_ answer属性。
名称空间在不同的时刻创建,有着不同的生命周期。包含内置名称的名称空间在Python解释器启动时被创建,且不会被删除。 模块的全局名称空间在模块被导入时被创建,正常情况下,模块的名称空间会持续到解释器退出。来自脚本文件或者交互式环境 被解释器最顶层调用执行的语句,被认为是 __ main __ 模块的一部分,所以他们有着自己的全局名称空间。 函数的局部名称空间当函数被调用时被创建,函数返回时或者出现异常而函数又没有提供处理方式时被删除。当然,在递归调用 中每一次调用都有他们自己的局部名称空间。 域(scpoe)是Python程序的一个名称空间可以直接访问的一个文本范围,“直接访问”在这里的意思时当对一个名字的访问没有 前缀时,会尝试在名称空间内查找这个名字。 在执行的任意时刻,至少有三个嵌套域,它们有名称空间可以直接访问。
最内层的域,它会首先被搜索,包含局部名称
任何封装函数的域,从最近的封装域开始搜索,包含非局部,非全局的名称
倒数第二个域,包含当前模块的全局名称
最外层的域,最后被搜索,包含内置名字的名称空间
如果一个名字被声名为全局的,那么所有的引用和赋值都是针对中间层的域,这一层域包含模块的全局名称。 意识到域是由文本决定是非常重要的,定义在模块中的一个函数的全局域就是这个模块的名称空间,无论这个函数在哪儿, 通过哪个别名被调用。
3 初识类
3.1 定义类
class ClassName:
<statement-1>
.
.
.
<statement-N>
类定义,像函数定义一样,在执行时才会起作用。你可以把类定义放在任何地方比如if语句的分支,或者在函数内部。 在实际应用时,定义在类中的语句通常都是函数定义,但是其它语句也是允许出现的,并且有的时候非常有用。 当进入一个类定义时,一个新的名称空间被创建,并且被当作局部域来使用。
3.2 类对象
类对象提供两种操作,属性引用和实例化。 属性引用使用标准句法:obj.name. 有效的属性名是类对象创建时类的名称空间内的所有名字。 例如下面的类定义中,MyClass.i和MyClass.f都是有效的属性名。
>>>class MyClass:
... """A simple example class"""
... i = 123
... def f(self):
... return 'hello world'
...
>>>MyClass.i
123
>>>MyClass.i = 10
类的实例化使用函数记号,例如:
>>>x = MyClass()
>>>x.i
10
这个实例化操作创建了一个空对象,许多类在实例化时定义了一些初始化操作。例如:
>>>class MyClass():
... def __init__(self):
... self.data = []
当一个类定义了__ init __ 方法后,类实例化时会自动调用 __ init __ ().
__ init __ 函数还可以有其它参数,例如:
>>>class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>>x = Complex(3.0, -4.5)
>>>x.r, x.i
(3.0, -4.5)
3.3 实例化对象
现在我们可以用实例化的对象做些什么呢?它唯一可以进行的操作是属性引用。有两类有效的属性名,数据属性和方法。 数据属性(data attributes)对应c++中的数据成员,数据属性无需声明,第一次给它赋值时就表明了它的存在。 另一种实例化的属性引用叫做方法(Method).方法是对象内的一个函数。
3.4 方法对象
通常我们调用一个方法的方式是:
x.f()
但是,由于x.f是一个方法对象,所以它可以存储起来,以便以后调用
>>>class MyClass:
... """A simple example class"""
... i = 12345
... def f(self):
... return 'hello world'
...
>>>x = MyClass()
>>>x.f()
'hello world'
>>>xf = x.f
>>>xf()
'hello world'
你可能已经发现,f名名有一个参数,但是调用时为什么没有使用呢。其实,原因在于 x.f() 与 MyClass.f(x) 是等价的。
>>>MyClass.f(x)
'hello world'
4 漫谈
数据属性如果和方法属性名称相同,前者会覆盖后者。所以为了避免名称冲突,最好养成一些习惯,比如方法名称大写,数据属性 名称前加一个短小,唯一的前缀。或者数据属性用名词,方法属性用动词。 数据属性可以被方法引用,也可以被对象的使用者引用。换句话说,类不能实现为纯抽象数据类型。 通常,方法的第一个参数是self.虽然这只是一个习惯用法,但是遵循一些习惯用法会让你的程序可读性更强。 函数定义没有必要非在类里面,例如:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
一个方法可以通过self参数调用其它方法,
>>>class Bag:
... def __init__(self):
... self.data = []
... def add(self, x):
... self.data.append(x)
... def addtwice(self, x):
... self.add(x)
... self.add(x)
...
>>>b = Bag()
>>>b.data
[]
>>>b.add('1')
>>>b.data
['1']
>>>b.addtwice('x')
>>>b.data
['1', 'x', 'x']
5 派生
派生类的形式如下:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
BaseClassName必须在包含派生类的域内定义,BaseClassName可以是一个表达式,例如:
class DerivedClassName(modname.BaseClassName):
当派生类的对象引用了一个属性时,会先在派生类内查找这个属性名,如果找不到,再到基类中查找。 派生类可以覆盖基类中的方法,即使基类中的方法被覆盖了,也可以使用下面的方法来调用
BaseClassName.methodname(self, arguments)
6 多重继承
Python 支持有限的多重继承:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
在旧风格的类中,唯一的规则是深度优先,从左到右。以上述类定义为例,如果一个属性没有在 DerivedClassName中被 找到,那么会继续搜索Base1,Base2等等 在新风格的类中,对方法的解析次序是动态改变的,这是因为类的继承关系会呈现出一个或多个菱形。例如新风格的类都由 object类派生出,这样就会就多条路径通向object。为了避免基类被多次搜索,使用了线性化算法将所有基类排列成从左 到右的顺序
7 私有变量和类局部引用
实例的私有变量只能在对象内部使用,python中常常使用例如 _ spam 的形式来代表API的非公有部分,无论是函数,方法还是 数据成员。类私有成员的特性的一种有效的用法是可以避免与子类中定义的名字冲突,这种机制叫做 mangling:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
注意上述代码中 __ update 的使用,避免了子类中对update的覆盖影响到基类 __ init__ 中的 update.
8 结构体
有时候我们可能需要像C中的struct那样的数据类型,把少量的数据项放在一起。Python中可以使用定义一个空类来实现这一点:
# filename:p.py
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
>>>import p
>>>p.john
<p.Employee instance at 0xb71f50ac>
>>>p.john.name
'John Doe'
>>>p.john.dept
'computer lab'
>>>p.john.salary
1000
9 异常(Exceptions)也是类
用户定义的异常也可以用类来表示,使用这种机制可以创建出可扩展,层次化的异常。 raise 语句有两种新的形式
raise Class, instance
raise instance
第一种形式中,instance必须是Class的一个实例,或者是由它派生出的类。 第二种形式是下面这种形式的缩写
raise instance.__class__, instance
下面这个例子会依次打印出B,C,D
class B:
pass
class C(B):
pass
class D(C):
pass
for c in [B,C,D]:
try:
raise c()
except D:
print "D"
except C:
print "C"
except B:
print "B"
>>>import f
B
C
D
注意如果 B写在最前面,会打印出BBB,这是因为raise C和raise D时,执行到except B是都会 print "B". 因为B是C,D的基类.
10 迭代器
现在你可能已经注意到了多数容器对象都可以使用for语句来循环
>>>for elem in [1,2,3]:
... print elem
...
1
2
3
>>>for elem in (1,2,3):
... print elem
...
1
2
3
这一风格清晰,简捷,方便。迭代器的使用在Python中非常普便。for语句的背后,其实是对容器对象调用 iter(). 这个函数返回一个迭代器对象,它定义了next()函数,每次访问容器中的一个元素。当没有元素的时候,next()返回一个 StopIteration异常,告诉for语句循环结束了。
>>>s = 'asdf'
>>>it = iter(s)
>>>it
<iterator object at 0xb71f590c>
>>>it.next()
'a'
>>>it.next()
's'
>>>it.next()
'd'
>>>it.next()
'f'
>>>it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
理解了迭代机制,就可以很容易地把迭代器加入你的类中,定义__ iter__ ()方法,返回一个有next()方法的对象。 如果一个类定义了next()函数,__ iter__ () 可以仅返回 self:
# q.py
class Reverse:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index -1
return self.data[self.index]
>>>import q
>>>rev = q.Reverse('spam')
>>>iter(rev)
<q.Reverse instance at 0xb71f588c>
>>>for char in rev:
... print char
...
m
a
p
s
11 生成器(Generators)
生成器是创建迭代器的一个简单而强大的工具。它们像正常函数一样,只是需要返回数据时使用 yield语句。
# d.py
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>>import d
>>>for char in d.reverse('golf'):
... print char
...
f
l
o
g
任何可以使用生成器做的事,都可以使用前一版本的reverse实现,生成器之所以实现紧凑是因为自动创建了 __ iter() 和 next() 方法。