赞
踩
参数的传递是通过自动将对象赋值给本地变量名来实现的。函数参数[调用者发送的(可能的)的共享对象引用值]在实际中只是Python赋值的另一个实例而已。因为引用是以指针的形式实现的,所有的参数实际上都是通过指针进行传递的。作为参数被传递的对象从来不自动拷贝。
在函数内部的参数名的赋值不会影响调用者。在函数运行时,在函数头部的参数名是一个新的、本地的变量名,这个变量名是在函数的本地作用域内的。函数参数名和调用者作用域中的变量名是没有别名的。
改变函数的可变对象参数的值也许会对调用者有影响。换句话说,因为参数是简单地赋值给传入的对象,函数能够就地改变传入的可变对象,因此其结果会影响调用者。可变参数对于函数来说是可以做输入和输出的。
这里有一个较为纯粹的技巧:因为return能够返回任意种类的对象,所以它也能够返回多个值,如果这些值封装进一个元组或其他的集合类型。
def multiple(x,y):
x = 2
y = [3,4]
return x,y #省略了括号。
x = 1
L = [1,2]
print(multiple(x,L)) #(2, [3, 4])
x,L = multiple(x,L)
print(x,L) #2 , [3,4]
看起来这里的代码好像返回了两个值,但是实际上只有一个:一个包含有2个元素的元组,它的圆括号是可选的,这里省略了。
语法 | 位置 | 解释 |
---|---|---|
func(value) | 调用者 | 常规参数:通过位置进行匹配 |
func(name=value) | 调用者 | 关键字参数:通过变量名匹配 |
func(*sequence) | 调用者 | 以name传递所有的对象,并作为独立的基于位置的参数 |
func(**dict) | 调用者 | 以name成对的传递所有的关键字/值,并作为独立的关键字参数 |
def func(name) | 函数 | 常规参数:通过位置或变量名进行匹配 |
def func(name=value) | 函数 | 默认参数值,如果没有在调用中传递的话 |
def func(*name) | 函数 | 匹配并收集(在元祖中的)所有包含位置的参数 |
def func(**name) | 函数 | 匹配并收集(在字典中的)所有包含位置的参数 |
def func(*args,name) | 函数 | 参数必须在调用中按照关键字传递 |
#1.常规参数 def f(a,b,c): print(a,b,c) f(1,2,3) #必须传递三个参数,不然报错。 #2.默认参数 def f(a,b,c=1): print(a,b,c) f(1,2) #c可以不传递。 f(1,2,3) f(a=10,b=10,c=100) #也可以这样调用。这种属于混合了。 # 3.关键字参数 def f(a=1,b=1,c=1): print(a,b,c) f(c=100) #只传递c f(10,c=100) #常规参数必须在前面。这里只传递了a和c。 # 4.任意参数*args def f(*args): print(args) f(1,2,3) #(1, 2, 3) # f(a=1,b=1)这种调用就错误的。 # 5.任意参数**kargs def f(**kargs): print(kargs) f(name='joker',age=30) #{'name': 'joker', 'age': 30} #f(1,2,3) 这种调用方式是错误的。 # 6.混合。 def f(a,*args,**kargs): print(a,args,kargs) f(1,2,3,name='joker',age=30) # 1 (2, 3) {'name': 'joker', 'age': 30} #7.只能使用keyword-only传递 def kwonly(a,*b,c): print(a,b,c) kwonly(1,2,3,4,5,c=100) #结果:1 (2, 3, 4, 5) 100 ;必须传递c,否则报错。 def kwonly(a,*,c): print(a,c) kwonly(1,c=100) #要传递两个,也只能传递两个参数。 def keyWordOnly2(*args,sep=' ',end='\n'): pass
解包参数
在最新的Python版本中,我们在调用函数时能够使用*语法。在这种情况下,它与函数定义的意思相反**。它会解包参数的集合**,而不是创建参数的集合。
def f(name='',age=0):
print(name,age)
person = {"name":"joker","age":30}
f(**person)
def g(a,b,c):
print(a,b,c)
aTuple = (1,2,3)
g(*aTuple)
在Python 3.0中(但不包括Python 2.6),也可以给函数对象附加注解信息—与函数的参数和结果相关的任意的用户定义的数据。Python为声明注解提供了特殊的语法,但是,它自身不做任何事情﹔注解完全是可选的,并且,出现的时候只是直接附加到函数对象的**annotations_**属性以供其他用户使用。
def func(a: 'spam',b:float,c:int = 100) ->int:
return a+b+c
print(func(1,2,3))
很遗憾,标准库和python内置的模块都没使用函数的注解。
def func(a,b,c=100):
print(a,b,c)
print(dir(func))
['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
再看如下:
def func(a,b,c=100):
print(a,b,c)
print(help(func.__annotations__))
属性或方法 | 含义 |
---|---|
__subclasshook__ | __subclasshook__ (…) method of builtins.type instance. Abstract classes can override this to customize issubclass(). This is invoked early on by abc.ABCMeta.__subclasscheck__ (). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached). |
__str__ | __str__ = <method-wrapper ‘__str__ ’ of function object> Return str(self). |
__sizeof__ | Size of object in memory, in bytes. |
__setattr__ | __setattr__ = <method-wrapper ‘__setattr__ ’ of function object> Implement setattr(self, name, value). |
__repr__ | __repr__ = <method-wrapper ‘__repr__ ’ of function object> Return repr(self). |
__reduce_ex__ | __reduce_ex__ (protocol, /) method of builtins.function instance Helper for pickle. |
__reduce__ | Helper for pickle. |
__qualname__ | |
__new__ | __new__ (*args, **kwargs) method of builtins.type instance. Create and return a new object. See help(type) for accurate signature. |
__ne__ | __ne__ = <method-wrapper ‘__ne__ ’ of function object> Return self!=value. |
__name__ | 函数名 |
__module__ | Help on module __main__ |
__lt__ | __lt__ = <method-wrapper ‘__lt__ ’ of function object> Return self<value. |
__le__ | __le__ = <method-wrapper ‘__le__ ’ of function object> Return self<=value. |
__kwdefaults__ | |
__init_subclass__ | |
__init__ | |
__hash__ | __hash__ = <method-wrapper ‘__hash__ ’ of function object> Return hash(self). |
__gt__ | __gt__ = <method-wrapper ‘__gt__ ’ of function object> Return self>value. |
__globals__ | 全局属性 |
__getattribute__ | __getattribute__ = <method-wrapper ‘__getattribute__ ’ of function object> Return getattr(self, name). |
__get__ | __get__ = <method-wrapper ‘__get__ ’ of function object> Return an attribute of instance, which is of type owner. |
__ge__ | __ge__ = <method-wrapper ‘__ge__ ’ of function object> Return self>=value. |
__format__ | __format__ (format_spec, /) method of builtins.function instance. Default object formatter. |
__eq__ | __eq__ = <method-wrapper ‘__eq__ ’ of function object> Return self==value |
__doc__ | 文档,在函数中,三引号注释的内容。 |
__dir__ | __dir__ () method of builtins.function instance Default dir() implementation. |
__dict__ | |
__delattr__ | __delattr__ = <method-wrapper ‘__delattr__ ’ of function object> Implement delattr(self, name). |
__defaults__ | 默认命名参数,以元组的方式展示。比如def a(a,b,c=200); (200,) |
__code__ | 返回一个code类实例。 |
__closure__ | |
__class__ | |
__call__ | |
__builtins__ | |
__annotations__ |
| code
| a code object
| globals
| the globals dictionary
| name
| a string that overrides the name from the code object
| argdefs
| a tuple that specifies the default argument values
| closure
| a tuple that supplies the bindings for free variables
def func(a,b,c=100):
t = 100
print(a,b,c)
func.age=20
print(func.age)
格式
lambda argument1,argument2,...argumentN: expression using arguments
lambda是一个表达式,而不是一个语句。
实例:
f = lambda x,y,z=100: x+y+z
#或者 f= (lambda x,y,z=100: x+y+z)
print(f(1,2,200))
def f():
z =10
def test(x,y):
return x+y+z
return test
k = f()
print(k(100,200))
global语句是Python中唯一看起来有些像声明语句的语句。但是,它并不是一个类型或大小的声明,它是-一个命名空间的声明。它告诉Python函数打算生成一个或多个全局变量名。也就是说,存在于整个模块内部作用域(命名空间)的变量名。
PI=3.1415
def changePi():
global PI
PI = 3.1
changePi()
print(PI) #3.1
nonlocal语句是global的近亲,前面已经介绍过global。nonlocal和global一样,声明了将要在一个嵌套的作用域中修改的名称。和global的不同之处在于,nonlocal应用于一个嵌套的函数的作用域中的一个名称,而不是所有def之外的全局模块作用域;而且在声明nonlocal名称的时候,它必须已经存在于该嵌套函数的作用域中——它们可能只存在于一个嵌套的函数中,并且不能由一个嵌套的def中的第一次赋值创建。
def tracer(func): call = 0 def wrapper(*args,**kwargs): nonlocal call call += 1 print('call {} to {}'.format(call,func.__name__)) func(*args,**kwargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) if __name__ == '__main__': spam(1,2,3) spam(3,4,5)
就是说: 嵌套的低一层函数,需要声明nonlocal,来获取上一层函数的变量修改权;nonlocal只能从嵌套函数自身的内部看到.
换句话说,nonlocal即允许对嵌套的函数作用域中的名称赋值,并且把这样的名称的作用域查找限制在嵌套的def。直接效果是更加直接和可靠地实现了可更改的作用域信息,对于那些不想要或不需要带有属性的类的程序而言。
def tracer(func): call = 0 def wrapper(*args,**kwargs): nonlocal call call += 1 print('call {} to {}'.format(call,func.__name__)) func(*args,**kwargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) if __name__ == '__main__': spam(1,2,3) spam(3,4,5)
同理,如果只是访问的话,不需要加nonlocal
注意:模块和包是有区别的
简而言之,模块通过使用自包含的变量的包,也就是所谓的命名空间提供了将部件组织为系统的简单的方法。在一个模块文件的顶层定义的所有的变量名都成了被导入的模块对象的属性。
事实上,在一个导入语句中的模块名起到两个作用:
模块定义的对象也会在执行时创建,就在import执行时;import会一次运行在目标文档中的语句从而建立其中的内容。
这么比较:在Python中,导入并非只是把一个文件文本插入另一个文件而已。导入其实是运行时的运算,程序第一次导入指定文件时,会执行三个步骤:
记住,这三个步骤只在程序执行时,模块第一次导入时才会进行。在这之后,导入相同模块时,会跳过这三个步骤,而只提取内存中已加载的模块对象。从技术上讲,Python把载入的模块存储到一个名为sys.modules的表中,并在一次导入操作的开始检查该表。
首先,Python必须查找到import语句所引用的模块文件。注意:上一节例子中的import语句所使用的文件名中没有a.py,也没有目录路径,只有import b,而不是import c: \dir1lb.py
。事实上,只能列出简单名称。路径和后缀是刻意省略掉的,因为Python使用了标准模块搜索路径来找出import语句所对应的模块文件。
如何搜索:可以导入sys并打印list(sys.modules.keys())
查看,按照顺序如下:
c:\pycode;d:\package
最后这4个组件组合起来就是sys.path
>>>import sys
>>>sys.path
模块文件选择:文件名的后缀(如,.py)都是刻意从import语句中省略的。python会选择在搜索路径中第一个符合导入文件名的文件。例如:import b形式:优先级从上往下。
遍历模块搜索路径,找到符合import语句的源代码文件后,如果必要的话,Python接下来会将其编译成字节码。
**Python会检查文件的时间戳,如果发现字节码文件比源代码文件旧(例如,如果你修改过源文件),就会在程序运行时自动重新生成字节代码。**另一方面,如果发现.pyc字节码文件不比对应的.py源代码文件旧,就会跳过源代码到字节码的编译步骤。此外,如果Python在搜索路径上只发现了字节码文件,而没有源代码,就会直接加载字节码(这意味着你可以把一个程序只作为字节码文件发布,而避免发送源代码)。换句话说,如果有可能使程序的启动提速,就会跳过编译步骤。
import操作的最后步骤是执行模块的字节码。文件中所有语句会依次执行,从头至尾,而此步骤中任何对变量名的赋值运算,都会产生所得到的模块文件的属性。因此,这个执行步骤会生成模块代码所定义的所有工具。例如,文件中的def
语句会在导入时执行,来创建函数,并将模块内的属性赋值给那些函数。之后,函数就能被程
序中这个文件的导入者来调用。
因为最后的导入步骤实际上是执行文件的程序代码,如果模块文件中任何顶层代码确实做了什么实际的工作,你就会在导入时看见其结果。例如,当一个模块导入时,该模块内顶层的print语句就会显示其输出。函数的def语句只是简单地定义了稍后使用的对象。
使用import或from语句,获取模块;
import会读取整个模块,所以必须进行定义后才能读取它的变量名
import sys
print(sys.path)
#或者
import sys as ss
print(ss.path)
from将获取(或者说是复制)模块特定的变量名。
from sys import path
print(path)
from *
语句:会取得模块顶层所有赋了值的变量名的拷贝。在Python 3.0中,这里所描述的from ...*
语句形式只能用在一个模块文件的顶部,不能用于一个函数中。
from sys import *
print(path)
就像def一样,import和from是可执行的语句,而不是编译期间的声明,而且它们可以嵌套在if
测试中,出现在函数def
之中等,直到执行程序时,Python执行到这些语句,才会进行解析。换句话来说,被导入的模块和变量名,直到它们所对应的import或from语句执行后,才可以使用。此外,就像def一样,import和from都是隐性的赋值语句。
在模块文件顶层(也就是不在函数或类的主体内)每一个赋值了的变量名都会变成该模块的属性。
例如,假设模块文件M.py的顶层有一个像x=1
这样的赋值语句,而变量名x会变成M的属性,我们可在模块外以M.x的方式对它进行引用。变量名x对M.py内其他程序而言也会变成全局变量,但是,我们需要更正式地说明模块加载和作用域的概念以了解其原因。
_dict_
或dir(M)
获取。由导入而建立的模块的命名空间是字典,可通过模块对象相关联的内置的_dict_
属性来读取,而且能通过dir
函数查看。dir
函数大至与对象的_dict__
属性的键排序后的列表相等,但是它还包含了类继承的变量名。也许不完整,而且会随版本而异。module2.py
import sys
def te():
pass
test.py
import module2
print(module2.sys)
print(module2.te)
print(module2.__dict__['sys'].path)
输出:
<module 'sys' (built-in)>
<function te at 0x0000026008D7EB90>
['c:\\Users\\Administrator\\Desktop\\jumpPointer\\example',...]
此处,sys、te都是在模块语句执行时赋值的,所以在导入后都变成了属性。
module2.py
X=100
def changeX():
global X
X = 999
test.py
import module2
X=777
module2.changeX()
print(X)
print(module2.X)
输出:
777
999
执行时,module2.changeX修改module2中的x,而不是test.py中的X。module2.changeX的全局作用域一定是其所在的文件,无论这个函数是由哪个文件调用的.
换句话说,导入操作不会赋予被导入文件中的代码对上层代码的可见度:被导入文件无法看见进行导入的文件内的变量名。更确切的说法是:
正如我们所见到过的,模块程序代码默认只对每个过程执行一次。要强制使模块代码重新载入并重新运行,你得刻意要求Python这么做,也就是调用reload
内置函数。
与import和from不同的是:
imp
或importlib
之中,并且必须导入自己。import importlib
import module2
import time
time.sleep(3)
importlib.reload(module2)
除了模块名之外,导入也可以指定目录路径。Python代码的目录就称为包
__init__.py
文件的目录导入目录,也就是包导入了。
事实上,包导入是把计算机上的目录变成另一个Python命名空间,而属性则对应于目录中所包含的子目录和模块文件。
形式:加入存在dir0\dir1\dir2\mod.py,此时我们在dir0下面。
如果有__init__.py
文件,则是包导入,每个层级的目录都要有__init__.py
(除了入口的顶层文件)
使用import的形式,在使用的时候,有时候很麻烦(当__init__.py
啥都不做的时候,只是一个空文件的时候)
#目录层级如下
"""
main.py
dir1\
__init__.py
dir2\
__init__.py
dir2Mod
"""
#main.py
import dir1.dir2.dir2Mod
print(dir1)
print(dir1.dir2)
dir1.dir2.dir2Mod.mod2() #可以看出只能这么调用它,前面要写一大串。
dir2Mod.py
def mod2():
print('222')
此时必须借助__init__.py
来优化使用体验
文件结构还是上面的,再main.py中写
from dir1.dir2 import dir2Mod #方式 1
from dir1.dir2.dir2Mod import mod2 #方式2
print(dir2Mod)
dir2Mod.mod2()
mod2()
在Python 3.0和Python 2.6中,我们可以使用from语句前面的点号来表示,导入应该相对于外围的包——这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys.path)上某处的同名模块。直接效果是包模块覆盖了外部的模块。
点号使用时,首先当前是一个包,即当前目录有__init__.py
包内部搜索
例子:
#目录结构 """ main.py dir1/ __init__.py dir2/ __init__.py dir2Mod.py dir2Say.py """ #dir2Mod.py """ from .dir2Say import say def mod2(): say() """ #dir2Say.py """ def say(): print('say hello') """ from dir1.dir2 import dir2Mod dir2Mod.mod2()
形式:带点号的才是相对导入
from . import spam
告诉Python把位于与语句中给出的文件相同包路径中的名为spam的一个模块导入
from .spam import name
从名为spam的模块导入变量name,而这个spam模块与包含这条语句的文件位于同一个包下
import string
在Python 3.0中,不带点号的一个import总是会引发Python略过模块导入搜索路径的相对部分,并且在sys.path所包含的绝对目录中查找。例如,在Python 3.0的方式中,如下形式的一条语句,总是在sys.path上的某处查找一个string模块,而不是查找该包中具有相同名称的模块。
from .. import moduleX
引入该语句所属的包的兄弟节点包。
#目录结构 """ main.py dir1/ __init__.py dir2/ __init__.py dir2Mod.py dir3/ __init__.py dir3Mod.py """ #dir2Mod.py from .. import dir3 def mod2(): dir3.mod3()
正如我们所学过的,不导入一个文件,就无法存取该文件内所定义的变量名。
导入一个包(含__init__.py
的目录),实际是导入这个__init__.py
.
_x
和__call__
有种特定的情况,把下划线放在变量名前面(例如,_x
),可以防止客户端使from*
语句导入模块名时,把其中的那些变量名复制出去。这其实是为了对命名空间的破坏最小化而已。因为from*
会把所有变量名复制出去,导入者可能得到超出它所需的部分(包括会覆盖导入者内的变量名的变量名)。下划线不是“私有”声明:你还是可以使用其他导入形式看见并修改这类变量名。例如,使用import语句。
*一个下划线的变量会使_x
被from 处理,导致外部看不到。
__all__ = ["Error", "encode","decode"] # Export these only
使用此功能时,from*
语句只会把列在__all__
列表中的这些变量名复制出来。事实上,这和_x
惯例相反:__all__
是指出要复制的变量名,而_X
是指出不被复制的变量名。Python会先寻找模块内的_all_
列表;如果没有定义的话,from*就会复制出开头没有单下划线的所有变量名。
所以要想使_X变量导出来,可以写在__all__
里面。
main.py
from dir1.say import *
import sys
hello()
print(_price)
dir1/say.py
__all__ = ['hello','_price']
_price=10
def hello():
print('hello')
__all__
列表只对from*
语句这种形式有效,它并不是私有声明。
__name__
和__main__
每个模块都有个名为__name__
的内置属性,Python会自动设置该属性:
如果文件是以顶层程序文件执行,在启动时,__name__
就会设置为字符串__main__
如果文件被导入,__name__
就会改设成客户端所了解的模块名
结果就是模块可以检测自己的__name__
,来确定它是在执行还是在导入。例如,假设我们建立下面的模块文件,名为runme.py,它只导出了一个名为tester的函数。
def tester():
print("test")
if __name__ == "__main__": #如果是被导入,则不会等于__main__,也就不会执行下面的语句了。
tester()
sys.path.append('c:\\youCode\\someExamples')
形式:
import moduleName as newName
#等价
import moduleName
newName = moduleName
del moduleName
from moduleName import attrName as newName
import dir1.dir2.mod as mod
__dict__
import dir1.say as say
import sys
say.hello()
hello = getattr(say,'hello')
hello()
sys.modules['dir1.say'].hello()
say.__dict__['hello']()
#效果等同。
__import__
""" 错误实例: import "string" 或 x="string" import x """ #正确方式1如下: import sys x="string" exec("import "+x) print(sys.modules[x]) #最推荐的方式2: x="string" string = __import__(x) print(string)
class Person:
name = "paopao"
@classmethod
def echoName(cls):
print(cls.name)
def echoAge(self):
pass
if __name__ == "__main__":
print(Person.name) #paopao
Person.echoName()
class语句创建类对象并将其赋值给变量名。就像函数def语句,Python class语句也是可执行语句。执行时,会产生新的类对象,并将其赋值给class头部的变量名。此外,就像def应用,class语句一般是在其所在文件导入时执行的。
**class语句内的赋值语句会创建类的属性。**就像模块文件一样,class语句内的顶层的赋值语句(不是在def之内)会产生类对象中的属性。**从技术角度来讲,class语句的作用域会变成类对象的属性的命名空间,就像模块的全局作用域一样。**执行class语句后,类的属性可由变量名点号运算获取object.name。
**类属性提供对象的状态和行为。**类对象的属性记录状态信息和行为,可由这个类所创建的所有实例共享。位于类中的函数def语句会生成方法,方法将会处理实例。
class Person: name = "paopao" age = 30 @classmethod def echoName(cls): print(cls.name) def echoAge(self): print(self.age) if __name__ == "__main__": p = Person() p.echoName() #可以调用 p.echoAge() #30 p.name = "joker" Person.echoName() #paopao
实例化(调用)一个类,会得到一个新的对象
像函数那样调用类对象会创建新的实例对象。每次类调用时,都会建立并返回新的实例对象。实例代表了程序领域中的具体元素。
每个实例对象继承类的属性并获得了自己的命名空间。由类所创建的实例对象是新命名空间。一开始是空的,但是会继承创建该实例的类对象内的属性。
**在方法内对self属性做赋值运算会产生每个实例自己的属性。**在类方法函数内,第一个参数(按惯例称为self)会引用正在处理的实例对象。对self的属性做赋值运算,会创建或修改实例内的数据,而不是类的数据。
class Person: name = "paopao" age = 30 type = "Cosmic man" @classmethod def echoName(cls): print(cls.name) def echoAge(self): print(self.age) class Earthman(Person): def echoType(self): print(self.type) if __name__ == "__main__": e = Earthman() e.echoAge() e.echoType()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g09wZfdK-1676732517789)(./static/QQ截图20220519144105.png)]
超类的连接是通过class语句首行的括号内列出类而生成的。
简而言之,对经典类(python2)而言,继承搜索程序是绝对深度优先,然后才是由左至右。Python一路往上搜索,深入树的左侧,返回后,才开始找右侧。在新式类(python3)中,在这类情况下,搜索相对来说是宽度优先的。Python先寻找第一个搜索的右侧的所有超类,然后才一路往上搜索至顶端共同的超类。
class Human:
width=100
height=200
color="black"
class Man(Human):
__slots__ = ['width','color']
if __name__ == '__main__':
m = Man()
print(m.height) # 200
# print(m.width) #报错,被限制了。
运算符重载就是让用类写成的对象,可截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。
__X__
)是特殊钩子。Python运算符重载的实现是提供特殊命名的方法来拦截运算。Python语言替每种运算和特殊命名的方法之间,定义了固定不变的映射关系。__add__
方法,当对象出现在+表达式内时,该方法就会调用。该方法的返回值会变成相应表达式的结果。__add__
,+表达式就会引发异常。还有注意的一点,如果忘记运算符,可以查看object类。
__sub__
class Turtle:
count = 1
def __sub__(self,value):
return self.count-value
if __name__ == "__main__":
t = Turtle()
print(t-10) #9
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EL7ENLyA-1676732517790)(./static/QQ截图20220519152702.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dr9Ywvwd-1676732517791)(C:\Users\xy\AppData\Roaming\Typora\typora-user-images\image-20220519153949294.png)]
__getitem__
和__setitem__
class Turtle:
count = [1,2,3]
def __getitem__(self,index):
return self.count[index]
def __setitem__(self,index,value):
self.count[index] = value
if __name__ == "__main__":
t = Turtle()
print(t[1]) #2
t[1]=22
print(t[:]) #2[1,22,3]
__iter__
和__next__
上面说的__getitem__
也可以实现迭代。不过并没有__iter__
级别优先。
class Turtle:
count = [1,2,3]
def __getitem__(self,index):
return self.count[index]
def __setitem__(self,index,value):
self.count[index] = value
if __name__ == "__main__":
t = Turtle()
for x in t:
print(x)
从技术角度来讲,迭代环境是通过调用内置函数iter去尝试寻找_iter__
方法来实现的,而这种方法应该返回一个迭代器对象。如果已经提供了,Python就会重复调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没找到这类__iter__
方法,Python会改用__getitem__
机制,就像之前那样通过偏移量重复索引,直到引发IndexError异常(对于手动迭代来说,一个next内置函数也可以很方便地使用:next(B)与B.__next__
()是相同的)。
class Array: def __init__(self,start,stop): if not ( isinstance(start,int) and isinstance(stop,int)) : raise TypeError('Not integer type') if start > stop: start,stop = stop,start self.start = start - 1 self.stop = stop def __iter__(self): return self def __next__(self): if self.start >= self.stop: raise StopIteration self.start+=1 return self.start if __name__ == "__main__": t = Array(1,5) for x in t: print(x,end=" ") #1 2 3 4 5
关于迭代器的原理,详见迭代器章节。
__contains__
和in操作符class Array: def __init__(self,array): self.array = array def __contains__(self,value): try: return bool(self.array.index(value)) except ValueError: return False if __name__ == "__main__": t = Array([2,5,4]) if 8 in t: print('is in ') else: print('not in')
__getattr__
和__setattr__
属性调用__getattr__
方法是拦截属性点号运算。更确切地说,当通过对未定义(不存在)属性名称和实例进行点号运算时,就会用属性名称作为字符串调用这个方法。如果Python可通过其继承树搜索流程找到这个属性,该方法就不会被调用。__setattr__
会拦截所有属性的赋值语句。如果定义了这个方法,self.attr = value会变成self.__setattr__
( ‘attr’,value)。这一点技巧性很高,因为在__setattr__
中对任何self属性做赋值,都会再调用__setattr__
,导致了无穷递归循环(最后就是堆栈溢出异常))。如果想使用这个方法,要确定是通过对属性字典做索引运算来赋值任何实例属性的(下一节讨论)。也就是说,是使用self.__dict__
[ ‘name’ ] = x,而不是self.name = x。class Person:
name = 'joker'
def __getattr__(self,attr):
if attr == 'name':
return self.__name
def __setattr__(self,attrName,value):
if attrName == 'name':
self.__dict__[attrName] = value
if __name__ == "__main__":
p = Person()
p.name = 'big'
print(p.name)#big
__getattribute__
方法拦截所有的属性获取,而不只是那些未定义的,但是,当使用它的时候,必须比使用__getattr__
更小心地避免循环。__get__
和__set__
方法与对特定类属性的访问关联起来。__repr__
和__str__
会返回字符串表达形式打印操作会首先尝试_str__
和str内置函数(print运行的内部等价形式)。它通常应该返回一个用户友好的显示。
__repr_
用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__
,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者一个详细的显示。
总而言之,__repr__
用于任何地方,除了当定义了一个_str_的时候,使用print和str。然而要注意,如果没有定义__str__
,打印还是使用__repr__
,但反过来并不成立—–其他环境,例如,交互式响应模式,只是使用__repr__
,并且根本不要尝试__str__
:
class Person:
def __str__(self): #可不写这个,用下面那个函数就可
return "class Person"
def __repr__(self):
return "class Person 2"
if __name__ == "__main__":
p = Person()
print(p)
__repr__
是最佳选择.两者必须返回字符串。
__radd__
和__iadd__
class Commuter(object): def __init__(self,val): self.val = val def __radd__(self,other):#右侧加法 return self.val + other def __iadd__(self,other): #查看object基类,发现必须返回self对象。 self.val += other return self def __repr__(self): return "val is {}".format(self.val) def echoVal(self): print(self.val) if __name__ == '__main__': t = Commuter(99) val = 1 + t #不是t+1哦,t+1是__add__。 print(val)#100 t += 1000 t.echoVal() print(t)
__call__
class Commuter:
def __init__(self,val):
self.val = val
def __call__(self, *args, **kwds) :
print('__call__',args,kwds)
def echoVal(self,*args,**kwargs):
print(args,kwargs)
if __name__ == '__main__':
t = Commuter(99)
t(1,2,3,4) #这里调用了一次__call__; _call__ (1, 2, 3, 4) {}
t.echoVal(1,2,3,4,5,name="joker",age=30)#这里没调用。
__del__
每当实例空间被收回时(在垃圾收集时),__del__
,也就是析构函数(destructor method),就会自动执行。
class Commuter:
def __del__(self):
print('good bye')
if __name__ == '__main__':
t = Commuter()
t = 1
基于某些原因,在Python中,析构函数不像其他OOP语言那么常用。
__slots__
限制class Human:
width=100
height=200
color="black"
class Man(Human):
__slots__ = ['width','color']
if __name__ == '__main__':
m = Man()
print(m.height) # 200
# print(m.width) #报错,被限制了。
m.width = 300#但是能被赋值。
这个特殊属性一般是在clas s语句顶层内将字符串名称顺序赋值给变量_slots_而设置:只有_slots_列表内的这些变量名可赋值为实例属性。然而,就像Python中的所有变量名,实例属性名必须在引用前赋值,即使是列在__slots__
中也是这样。
__get__
和__set__
object.__get__(self, instance, owner)
object.__set__(self, instance, value)
get: 调用以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。owner始终是自身类,而instance是通过其访问属性的实例(可以是其他类的实例对象),或者在通过owner访问属性时为None。此方法应返回(计算的)属性值或引发AttributeError异常。
参考装饰器-函数装饰器-类装饰类方法一节。
set:调用以将所有者类的实例实例上的属性设置为新值value。
__new__
调用以创建一个类的新实例。cls.__new__()
是一个静态方法(因为是特例所以你不需要显式声明)。它将会给这个实例的类
赋给实例属性值的通常方法是,在__init__
构造函数方法中将它们赋给self,构造函数方法包含了每次创建一个实例的时候Python会自动运行的代码。让我们给自己的类添加一个构造函数:
class Turtle:
def __init__(self,name) -> None:
self.name = name
if __name__ == "__main__":
t = Turtle('joker')
print(t.name)
_init_
实在没有什么奇妙之处,只不过当创建一个实例的时候,会自动调用它(__init__
)并且它有特殊的第一个参数。尽管它的名字很怪异,它仍然是一个常规的函数.方法有四种: 1. 实例方法(实例对象持有) ;2. 静态方法 3. 类方法。4. 无绑定实例的——函数
这里讲实例方法,其他的查看其他小节。
class Turtle:
def summonTheGods(self,name):
print("{} god is comming".format(name))
if __name__ == "__main__":
t = Turtle()
t.summonTheGods('Taiyi')
方法调用的本质:
instance.method(args.. .)
这会自动翻译成以下形式的类方法函数调用:
class.method(instance,args.. .)
第二种方法可以真的使用。第一个参数将实例对象传递进去即可。
class Commuter:
def echo(val,val2):
print(val,val2)
if __name__ == '__main__':
t = Commuter()
# t.echo(1,2) 不能被实例调用
Commuter.echo(1,2)
在Python 3.0中(以及随后的Python 3.X版本中),对一个无self方法的调用使得通过类调用有效,但从实例调用失效:
class Human:
def echo(v):
print(v)
def render(v):
print(v)
echoVal = staticmethod(echo)
renderVal = classmethod(render)
if __name__ == '__main__':
Human.echoVal(111)
Human.renderVal(222)
再不适用装饰器的前提下,使用内置的staticmethod和classmethod来特殊化一个简单的方法。
**好处:异化。**python3中简单函数就绝大部分功能替代静态方法和类方法。
#装饰器版本
class Human:
@staticmethod
def echo(v):
print(v)
@classmethod
def render(cls,v):
print(v)
if __name__ == '__main__':
Human.echo(111)
Human.render(222)
instance.__class__
属性提供了一个从实例到创建它的类的链接。类反过来有一个__name__
(就像模块一样),还有一个__bases__
序列,提供了超类的访问。我们使用这些来打印创建一个实例的类的名字,而不是通过硬编码来做到。object.__dict__
属性提供了一个字典,带有一个键/值对,以便每个属性都附加到一个命名控件对象(包括模块、类和实例)。由于它是字典,因此我们可以获取键的列表、按照键来索引、迭代其键,等等,从而广泛地处理所有的属性。我们使用这些来打印出任何实例的每个属性,而不是在定制显示中硬编码。只在方法名前面使用两个下划线符号:对我们的例子来说就是__x
。Python自动扩展这样的名称,以包含类的名称,从而使它们变得真正唯一。这一功能通常叫做伪私有类属性.
我们将推迟到第38章再给出属性私有性的一个更完整的解决方案,在那里,我们将使用类装饰器来更加通用地拦截和验证属性。
class Commuter:
__name="joker"
age=30
def echo(self):
print(self.__dir__())
if __name__ == '__main__':
t = Commuter()
t.echo()
print(t.age)
print(t.__name) #报错
为啥打印t.__name
会报错呢?因为python将两个下划线的变量,在外部访问的时候,自动变更为_Commuter__name
,所以看上去不可见,而内部访问不会变。
class Commuter:
def echo(self,val):
print(val)
def echo(self,val,val2):#同名函数,最后一个生效
print(val,val2)
if __name__ == '__main__':
t = Commuter()
t.echo(1)
本章包括以下内容:
__getattr__
和 __setattr__
方法,把未定义的属性获取和所有的属性赋值指向通用的处理器方法。
__getattribute__
方法,把所有属性获取都指向Python 2.6的新式类和Python 3.0的有类中的一个泛型处理器方法。
property内置函数,把特定属性访问定位到get和set处理器函数,也叫做特性(Property)。
描述符协议,把特定属性访问定位到具有任意get和set处理器方法的类的实例。
class Person:
name = 1
def __init__(self,name):
self.name = name
p = Person('k')
print(p.name) #'k'
p.name = 'b'
print(p.name) #'b'
del p.name
print(p.name) #1
del p.name
print(p.name) #报错:AttributeError: name
但是在python2.6及之前:使用property
class Person: def __init__(self,name): self._name = name def getName(self): return self._name def setName(self,value): self._name = value def delName(self): del self._name name = property(getName,setName,delName,'name docs') p = Person('bob,smit') print(p.name) #'bob,smit' p.name = 'joker' print(p.name) #'joker' del p.name print(p.name) #报错
class Person:
@property
def name(self):
return 'joker'
p = Person()
print(p.name)
等同于:
由于这一映射,证实了内置函数 property 可以充当一个装饰器,来定义一个函数,当获取一个属性的时候自动运行该函数:
class Person:
def name(self):
name = property(name)
对于Python 2.6,property对象也有 getter 、 setter 和 deleter 方法,这些方法指定相应的特性访问器方法赋值并且返回特性自身的一个副本。
class Person: def __init__(self,name='None'): self._name = name @property def name(self): return self._name @name.setter def name(self,value): self._name = value @name.deleter def name(self): del self._name p = Person() print(p.name) p.name = '22' print(p.name)
描述符提供了拦截属性访问的一种替代方法;它们与前面小节所讨论的特性有很大的关系。实际上,特性是描述符的一种——从技术上讲, property 内置函数只是创建一个特定类型的描述符的一种简化方式,而这种描述符在属性访问时运行方法函数。
从功能上讲,描述符协议允许我们把一个特定属性的get和set操作指向我们提供的一个单独类对象的方法:它们提供了一种方式来插入在访问属性的时候自动运行的代码,并且它们允许我们拦截属性删除并且为属性提供文档(如果愿意的话)。
描述符作为独立的类创建,并且它们就像方法函数一样分配给类属性。和任何其他的类属性一样,它们可以通过子类和实例继承。通过为描述符自身提供一个 self ,以及提供客户类的实例,都可以提供访问拦截方法。因此,它们可以自己保留和使用状态信息,以及主体实例的状态信息。例如,一个描述符可能调用客户类上可用的方法,以及它所定义的特定于描述符的方法。
和特性一样,描述符也管理一个单个的、特定的属性。尽管它不能广泛地捕获所有的属性访问,但它提供了对获取和赋值访问的控制,并且允许我们自由地把简单的数据修改为计算值从而改变一个属性,而不会影响已有的代码。特性实际上只是创建一种特定描述符的方便方法,并且,正如我们所见到的,它们可以直接作为描述符编写。
class Descriptor: """ owner: 指定了描述符实例要附加到的类 instance: 要么是访问的属性所属的实例(用于instance.attr ), 要么当所访问的属性直接属于类的时候是None(用于class.attr) """ def __get__(self,instance,owner):pass # 返回attr value def __set__(self,instance,value):pass #返回None def __delete__(self,instance):pass #返回None class Des: def __get__(self,instance,owner): print(self,instance,owner,sep='\n') class Sub: attr = Des() x = Sub() x.attr
输出:
<__main__.Des object at 0x00000260602FBE80>
<__main__.Sub object at 0x00000260602FBE50> #不一样哦,
<class '__main__.Sub'>
在上面的例子中,当获取 x.attr 的时候,Python自动运行 Des类的__get__
方法
还要注意不要把描述符 __delete__
方法和通用的 __del__
方法搞混淆了。调用前者是试图删除所有者类的一个实例上的管理属性名称;后者是一种通用的实例析构器方法,当任何类的一个实例将要进行垃圾回收的时候调用。 __delete__
与我们将要在本章后面遇到的__delattr__
泛型属性删除方法关系更近。
和特性不同,使用描述符直接忽略 __set__
方法不足以让属性成为只读的,因为描述符名称可以赋给一个实例。
In [1]: class D: ...: def __get__(*args):print('get') ...: In [2]: class C: ...: a = D() ...: In [3]: X = C() In [4]: X.a get In [5]: C.a get In [6]: X.a = 100 In [7]: X.a Out[7]: 100 In [8]: C.a get
这就是Python中所有实例属性赋值工作的方式,并且它允许在它们的实例中类选择性地覆盖类级默认值。要让基于描述符的属性成为只读的,捕获描述符类中的赋值并引发一个异常来阻止属性赋值——当要赋值的属性是一个描述符的时候,Python有效地绕过了
常规实例层级的赋值行为,并且把操作指向描述符对象.
class D:
def __get__(*args):print('get')
def __set__(*args):raise AttributeError('cannot set')
class C:
a = D()
x = C()
x.a #'get'
x.a = 100 # 'AttributeError: cannot set'
class Name: def __get__(self,instance,owner): print('fetch') return instance._name def __set__(self,instance,value): print('change') instance._name = value def __delete__(self,instance): print('remove') del instance._name class Person: def __init__(self,name): self._name = name name = Name() if __name__ == '__main__': bob = Person('bob smith') print(bob.name) bob.name = 'Robert smit' print(bob.name) del bob.name
输出:
fetch
bob smith
change
fetch
Robert smit
remove
self是Name类实例
instance 是 Person 类实例
owner 是 Person 类,不是实例
还要注意到,当一个描述符类在客户类之外无用的话,将描述符的定义嵌入客户类之中,这在语法上是完全合理的。
class Person: class Name: def __get__(self,instance,owner): print('fetch') return instance._name def __set__(self,instance,value): print('change') instance._name = value def __delete__(self,instance): print('remove') del instance._name def __init__(self,name): self._name = name name = Name() if __name__ == '__main__': bob = Person('bob smith') print(bob.name) bob.name = 'Robert smit' print(bob.name) del bob.name
实际上,描述符也可以用来在每次获取属性的时候计算它们的值。
class DescSquare: def __init__(self, start): # 每一个DescSquare都有一个独立的实例状态 self.value = start def __get__(self, instance, owner): #获取时进行计算 return self.value ** 2 def __set__(self, instance, value): #设置,这里没进行操作,只是简单的赋值,好处就是能保留传进来的原始值。 self.value = value class Client1: x = DescSquare(0) if __name__ == '__main__': c = Client1() print(c.x) #0 c.x = 5 print(c.x) #25
有没有点像vue的computed,不过python中的计算属性,不是响应式的。
__getattr__
和__getattribute__
到目前为止,我们已经学习了特性和描述符——管理特定属性的工具。 __getattr__
和__getattribute__
操作符重载方法提供了拦截类实例的属性获取的另一种方法。
就像特性和描述符一样,它们也允许我们插入当访问属性的时候自动运行的代码。然而,我们将会看到,这两个方法有更广泛的应用。
__getattr__
针对未定义的属性运行——也就是说,属性没有存储在实例上,或者没有从其类之一继承。__getattribute__
针对每个属性,因此,当使用它的时候,必须小心避免通过把属性访问传递给超类而导致递归循环。这两个方法是一组属性拦截方法的代表,这些方法还包括 __setattr__
和 __delattr__
。
__getattr__
将会拦截对__str__
和 __repr__
这样的运算符重载方法的访问,但是,在Python 3.0中不会这样.__getattr__
(或其近亲__getattribute__
)路由。装饰是为函数和类指定管理代码的一种方式。装饰器本身的形式是处理其他的可调用对象的可调用的对象(本身是函数或类)
简而言之,装饰器提供了一种方法,在函数和类定义语句的末尾插入自动运行代码——对于函数装饰器,在 def 的末尾;对于类装饰器,在 class 的末尾。
对于更为通用的任务,程序员可以编写自己的任意装饰器。例如,函数装饰器可能通过添加跟踪调用、在调试时执行参数验证测试、自动获取和释放线程锁、统计调用函数的次数以进行优化等的代码来扩展函数。你可以想象添加到函数调用中的任何行为,都可以作为定制函数装饰器的备选。
另外一方面,函数装饰器设计用来只增强一个特定函数或方法调用,而不是一个完整的对象接口。类装饰器更好地充当后一种角色——因为它们可以拦截实例创建调用,它们可以用来实现任意的对象接口扩展或管理任务。
很多类装饰器与我们见到的委托编程模式有很大的相似之处。
def tracer(func):
call = 0
print('do?')
def wrapper(*args,**kwargs):
nonlocal call
call += 1
print('call {} to {}'.format(call,func.__name__))
func(*args,**kwargs)
return wrapper
@tracer #装饰的修饰在这个位置的时候,就会运行print("do?)语句了。然后返回wrapper.
def spam(a,b,c):
print(a+b+c)
当主体函数或类定义的时候,装饰器应用一次;在对类或函数的每次调用的时候,不必添加额外的代码(在未来可能必须改变)。
更概括地说,有一种常用的编码模式可以包含这一思想——装饰器返回了一个包装器,包装器把最初的函数保持到一个封闭的作用域中(有没有点像闭包函数)
大多数实例都使用包装器来拦截随后对函数和类的调用,但这并非使用装饰器的唯一方法:
换句话说,函数装饰器可以用来管理函数调用和函数对象,类装饰器可以用来管理类实例和类自身。
装饰的对象是函数
函数装饰器是一种关于函数的运行时声明,函数的定义需要遵守此声明(具体更清晰的理解,见第一小节。)。装饰器在紧挨着定义一个函数或方法的 def 语句之前的一行编写,并且它由 @ 符号以及紧随其后的对于元函数的一个引用组成——这是管理另一个函数的一个函数(或其他的可调用对象)。
@decorator
def f(arg):
pass
f(99)
等同如下:
def f(arg):
pass
f = decorator(f)
f(99)
装饰器自身是一个返回可调用对象的可调用对象。也就是说,它返回了一个对象,当随后装饰的函数通过其最初的名称调用的时候,将会调用这个对象——不管是拦截了随后调用的一个包装器对象,还是最初的函数以某种方式的扩展。
def tracer(func): call = 0 def wrapper(*args,**kwargs): nonlocal call call += 1 print('call {} to {}'.format(call,func.__name__)) return func(*args,**kwargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) if __name__ == '__main__': spam(1,2,3) spam(3,4,5) """ call 1 to spam 6 call 2 to spam 12 """
在执行spam(1,2,3)
时,等同如下:
t = tracer(spam)
t(spam)(1,2,3)
t(spam)(4,5,6)
每次tracer修饰不同函数,都会产生一个封闭的作用域。(比如即修饰spam函数,又修饰其他函数如spam2函数,就会又两个封闭作用域。)
nonlocal 语句,允许修改封闭的函数作用域变量,所以它们可以充当针对每次装饰的、可修改的数据。比如上面的call变量。
另一种替代nonlocal的方法,就是使用函数属性。在py3中:
def tracert(func):
def wrapper(*args,**kwargs):
wrapper.counts +=1
print("call %s to %s"%(wrapper.calls,func.__name__))
return func(*args,**kwargs)
wrapper.counts = 0
return wrapper
class Tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args,**kwargs): self.calls += 1 print("call {} to {}".format(self.calls,self.func.__name__)) self.func(*args,**kwargs) tracer = Tracer @tracer def spam(a,b,c): print(a+b+c) if __name__ == '__main__': #spam是类的实例。 spam(1,2,3) spam(3,4,5) """ call 1 to spam 6 call 2 to spam 12 """
__call__
运算符重载方法。__call__
方法可能运行最初的func。按照这种方式编写代码的时候,每个装饰的函数都会产生一个新的实例来保持状态。from functools import wraps class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args): print(self) self.calls += 1 print("call {} to {}".format(self.calls,self.func.__name__)) self.func() class Person: @tracer def rise(self): print('I am rising') if __name__ == '__main__': p = Person() p.rise() """ C:\Users\xy\Desktop\projects\gaodengshuxue\example>python main.py <__main__.tracer object at 0x00000218D66EBFD0> call 0 to rise Traceback (most recent call last): File "C:\Users\xy\Desktop\projects\gaodengshuxue\example\main.py", line 21, in <module> p.rise() File "C:\Users\xy\Desktop\projects\gaodengshuxue\example\main.py", line 11, in __call__ self.func() TypeError: Person.rise() missing 1 required positional argument: 'self """
上面是失败的!!!
因为调用__call__
是传进去的self是tracer的实例对象,但是在__call__
最后一行有问题:self.func=》是在tracer实例上的,导致执行p.rise()方法中,rise中self没有指向任何实例。。
解决方法一:使用描述符的__get__
方法在调用的时候接受描述符类和主体类实例。
描述符也能够拥有 __set_
_ 和 __del__
访问方法,但是,我们在这里不需要它们。现在,由于描述符的 __get__
方法在调用的时候接收描述符类和主体类实例,因此当我们需要装饰器的状态以及最初的类实例来分派调用的时候,它很适合于装饰方法。
class Tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self, *args, **kwargs): self.calls +=1 print('calls %s to %s'%(self.calls,self.func.__name__)) return self.func(*args,**kwargs) def __get__(self,instance,owner): """ owner: 指定了描述符实例要附加到的类 instance: 要么是访问的属性所属的实例(用于instance.attr ), 要么当所访问的属性直接属于类的时候是None(用于class.attr) """ return Wrapper(self,instance) class Wrapper: def __init__(self,desc,subj): """ desc: Tracer的实例对象 subj: Person的实例对象。 """ self.desc = desc self.subj = subj def __call__(self, *args, **kwargs): return self.desc(self.subj,*args,**kwargs) tracer = Tracer @tracer def spam(a,b,c): print(a+b+c) spam(1,2,3) spam(a=4,b=5,c=6) class Person: def __init__(self,pay): self.pay = pay @tracer def giveRaise(self,percent): self.pay *= (1.0 + percent) print(self.pay) return self.pay print('-------------------') bob = Person(5600) sue = Person(8600) bob.giveRaise(0.1) sue.giveRaise(0.3)
spam变量名经过@tracer装饰后,指向了Tracer的实例对象。当继续调用spam()时,执行了__call__
方法。函数没有涉及到到__get__
方法
bob = Person(5600)创建Person对象没错,关键在与bob.giveRaise()的调用:
修饰类方法的时候(还没创建实例前),这个giveRaise就创建一个Tracer的实例。
当在执行bob.giveRaise的时候,先调用了__get__
方法,它返回了一个Wrapper的实例对象,并且这个对象,即封装了Tracer实例对象,也封装了Person实例对象。
执行bob.giveRaise的最后一步是,再调用了__call__
方法。注意触发的是上一步返回的实例对象的__call__
方法,即Wrapper实例的__call__
方法。
def __call__(self, *args, **kwargs):
"""
desc: Tracer的实例对象
subj: Person的实例对象。
"""
return self.desc(self.subj,*args,**kwargs)
这个__call__
,触发了Tracer实例对象的__call__
方法,传递进去了Person的实例对象,此时才不会报错。
解决方法二: 修改装饰器形式:
def tracer(func): calls = 0 def onCall(*args,**kwargs): nonlocal calls calls +=1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kwargs) return onCall @tracer def spam(a,b,c): print(a+b+c) spam(1,2,3) spam(a=4,b=5,c=6) class Person: def __init__(self,pay): self.pay = pay @tracer def giveRaise(self,percent): self.pay *= (1.0 + percent) print(self.pay) return self.pay print('-------------------') bob = Person(5600) sue = Person(8600) #两个类实例,其实共享同一个calls变量,但是是不同Person的实例。 bob.giveRaise(0.1) sue.giveRaise(0.3) """ call 1 to spam 6 call 2 to spam 15 ------------------- call 1 to giveRaise 6160.000000000001 call 2 to giveRaise 11180.0 """
教训: 如果你想要装饰器在简单函数和类方法上都有效,最好使用基于嵌套函数的编码模式,而不是带有调用拦截的类。(能简单就简单点)
装饰的对象是类
@decorator
class C:
pass
x = C(99)
等同于下面的语法——类自动地传递给装饰器函数,并且装饰器的结果返回来分配给类名:
class C:
pass
C = decorator(C)
x = C(99)
直接的效果就是,随后调用类名会创建一个实例,该实例会触发装饰器所返回的可调用对象,而不是调用最初的类自身。
尽管先编码,但装饰器的结果是当随后创建一个实例的时候才运行的.
def decorator(cls): print("do1?") #在装饰器装饰的时候就执行力了。然后返回Wrapper类。 class Wrapper: def __init__(self,*args): print("do2?") #在这一步x = C('china')执行的 self.wrapped = cls(*args) def __getattr__(self,name): return getattr(self.wrapped,name) return Wrapper @decorator class C: def __init__(self,name): self.name = name x = C('china') print(x.name) #china """ #附: #getattr用法 name = 'a' getattr(A,'name') 'a """
装饰器把类的名称重新绑定到另一个类,这个类在一个封闭的作用域中保持了最初的类,并且当调用它的时候,创建并嵌入了最初的类的一个实例。当随后从该实例获取一个属性的时候,包装器的 __getattr__
拦截了它,并且将其委托给最初的类的嵌入的实例。
此外,每个被装饰的类都创建一个新的作用域,它记住了最初的类
下面使用实现一个单体类: Spam只被实例化了一次。
class singleton: def __init__(self,aClass): #aClass是被修饰的类 print('do?') # 在装饰的时候,就实例化了。然后返回了一个singleton的实例对象给Spam self.aClass = aClass self.instance = None def __call__(self, *args, **kwargs): print('1') # s = Spam('joker')时才执行的。 if self.instance == None: self.instance = self.aClass(*args) return self.instance #返回Spam实例对象。 @singleton class Spam: def __init__(self,val): self.val = val if __name__ == "__main__": print(Spam) # Spam在被装饰的时候,Spam变量名就指向了Singleton的实例对象。 s = Spam('joker') s2 = Spam('joker2') print(s,s2) #都是Spam的同一个实例
上面一个单体示例使用类装饰器来管理一个类的所有实例。类装饰器的另一个常用场景是每个产生实例的接口。类装饰器基本上可以在实例上安装一个包装器逻辑层,来以某种方式管理对其接口的访问。
__getattr__
运算符重载方法作为包装嵌入的实例的整个对象接口的一种方法,以便实现委托编码模式。我们在前一章介绍的管理的属性中看到过类似的例子。还记得吧,当获取未定义的属性名的时候, __getattr__
会运行;我们可以使用这个钩子来拦截一个控制器类中的方法调用,并将它们传递给一个嵌入的对象。
为了便于参考,这里给出最初的非装饰器委托示例,它在两个内置类型对象上工作:
class Wrapper:
def __init__(self,object):
self.wrapped = object
def __getattr__(self, attrname):
t = getattr(self.wrapped,attrname)
#追踪: append ;对象: <built-in method append of list object at 0x000002496492D700>
print("追踪:",attrname,";对象:",t)
return t
if __name__ == '__main__':
x = Wrapper([1,2,3])
x.append(4) #x.append 获取到这个<built-in method append of list object at 0x000002496492D700>
#之后的(4)才是执行这个<built-in method append of list object at 0x000002496492D700> 方法
print(x.wrapped)
记住: 是x.append触发Wrapper的拦截器,然后该拦截器根据名称返回了这个lis.append的函数体。
类装饰器为编写这种 __getattr__
技术来包装一个完整接口提供了一个替代的、方便的方法。
def Tracer(aClass): print(aClass) #这就是出传递进来的Spam或Person class Wrapper: def __init__(self,*args,**kwargs): print("do?") #装饰器在装饰的时候,没有进入到这里 self.fetches = 0 self.wrapped = aClass(*args,**kwargs) def __getattr__(self, attrname): print("追踪:"+attrname) self.fetches +=1 return getattr(self.wrapped,attrname) print('outdo?') #装饰器在装饰的时候,只返回一个Wrapper. return Wrapper #等同于将Spam或Person封装在Wrapper中,并返回Wrapper. @Tracer class Spam: def display(self): print('spam!' * 2) @Tracer class Person: def __init__(self,name,hours,rate): self.name = name self.hours = hours self.rate = rate def pay(self): return self.hours * self.rate if __name__ =="__main__": s = Spam() s.display() p = Person("joker",1,1) p.pay() print(s.fetches) #注意s.fetches不会触发__getattr__拦截器
注意: s.fetches不会触发__getattr__
拦截器. 为什么呢,在管理属性那一章节,我们说过:
__getattr__
针对未定义的属性运行——也就是说,属性没有存储在实例上,或者没有从其类之一继承。所以上面的例子Wrapper实例对象有fetches,但是没有display,还有pay。
class Tracer: def __init__(self,aClass): print("do?") #在装饰的时候就执行了,将Spam类保存到这里。 self.aClass = aClass def __call__(self, *args, **kwargs): print('call?') #在s = Spam()的时候就执行了 # s = Spam(),还没运行到()时,Spam表示Tracer的实例对象,此时Tracer的实例对象在进行()运算,就调用了__call__ self.wrapped = self.aClass(*args,**kwargs) return self def __getattr__(self, attrname): print("追踪:"+attrname) #s.diplay的时候,还没运行到()的时候,被__getattr__拦截, # 然后返回了之前__call__()中实例化的Spam对象的display方法。然后该方法进行()运算。 return getattr(self.wrapped,attrname) @Tracer class Spam: def display(self): print('Spam'*2) if __name__ == "__main__": s = Spam() s.display()
对于一个给定的类的多个实例并不是很有效:每个实例构建调用会触发 __call__
,这会覆盖前面的实例(装饰的时候,Tracer已经实例化了,所以wrapped会被覆盖。)。直接效果是 Tracer 只保存了一个实例,即最后创建的一个实例。
为了支持多步骤的扩展,装饰器语法允许我们向一个装饰的函数或方法添加包装器逻辑的多个层。当使用这一功能的时候,每个装饰器必须出现在自己的一行中。这种形式的装饰器语法:
@A
@B
@C
def f(...):
...
如下这样运行:
def f(...):
...
f = A(B(C()))
就像对函数一样,多个类装饰器导致了多个嵌套的函数调用,并且可能导致围绕实例创建调用的包装器逻辑的多个层。
@spam
@eggs
class C:
...
x = C()
等同如下代码:
class C:
...
C = spam(eggs(C))
x = C()
再次,每个装饰器都自由地返回最初的类或者一个插入的包装器对象。有了包装器,当最终请求最初 C 类的一个实例的时候,这一调用会重定向到 spam 和 eggs 装饰器提供的包装层对象,二者可能有任意的不同角色。
函数装饰器和类装饰器似乎都能接受参数,尽管实际上这些参数传递给了真正返回装饰器的一个可调用对象,而装饰器反过来又返回一个可调用对象。
@decorator(A,B)
def F(arg):
...
F(99)
等同如下:
def F(arg):
...
F = decorator(A,B)(F)
F(99)
装饰器参数在装饰时就解析了,并且它们通常用来保持状态信息供随后的调用使用。
可能采用如下的形式:
def decorator(A,B):
# save or use A,B in there
def actualDecorator(F):
#save or use function F
# return a callable: nested def ,calls with __call__,etc.
return callable
return acturalDecorator
换句话说,装饰器参数往往意味着可调用对象的3个层级:
这3个层级的每一个都可能是一个函数或类,并且可能以作用域或类属性的形式保存了状态。
实例一:
def timer(label=''): def decorator(func): print('decorator') #在装饰的时候,就执行到这一层了,然后返回wrapper def wrapper(*args,**kwargs): start = time.time() result = func(*args,**kwargs) end = time.time() - start print("%s: spend %s "%(label,end)) return result return wrapper return decorator @timer('first do') def listcomp(N): print('111') return [x*2 for x in range(N)] listcomp(500000)
相当于listcomp指向了wrapper.
实例二:
import time def timer(label=''): class Timer: def __init__(self,func): print("do?") #在装饰的时候,就实例化了。 self.func = func def __call__(self, *args, **kwargs): start = time.time() result = self.func(*args,**kwargs) end = time.time() - start print("spend:%s"%(end)) return result return Time @timer('first do') def listcomp(N): print('111') return [x*2 for x in range(N)] t = listcomp(500000) #调用的时候,执行__call__ print(t)
import time class Timer: def __init__(self,func): print(func) self.func = func self.alltime = 0 def __call__(self,*args,**kargs): start = time.time() result = self.func(*args,**kargs) elapsed = time.time()-start self.alltime +=elapsed print("%s: %0.5f, %0.5f"%(self.func.__name__,elapsed,self.alltime)) return result timer = Timer @timer def listcomp(N): time.sleep(3) return [x*2 for x in range(N)] listcomp(500000) """ <function listcomp at 0x00000281AA193E20> listcomp: 3.05713, 3.05713 """
每当在运行时检测到程序错误时,Python就会引发异常。可以在程序代码中捕捉和响应错误,或者忽略已发生的异常。如果忽略错误,Python默认的异常处理行为将启动:停止程序,打印出错消息。如果不想启动这种默认行为,就要写tr y语句来捕捉异常并从异常中恢复:当检测到错误时,Python会跳到try处理器,而程序在try之后会重新继续执行。
异常也可用于发出有效状态的信号,而不需在程序间传递结果标志位,或者刻意对其进行测试。例如,搜索的程序可能在失败时引发异常,而不是返回一个整数结果代码(而且这段代码很有可能不会有–个有效的结果)。
有时,发生了某种很罕见的情况,很难调整代码去处理。通常会在异常处理器中处理这些罕见的情况,从而省去编写应对特殊情况的代码。
正如将要看到的一样,try/finally语句可确保一定会进行需要的结束运算,无论程序中是否有异常。
最后,因为异常是一种高级的“goto”,它可以作为实现非常规的控制流程的基础。例如,虽然反向跟踪(backtracking)并不是语言本身的一部分,但它能够通过Python的异常来实现,此外需要一些辅助逻辑来退回赋值语句。Python中没有“go to”语句(谢天谢地! ),但是,异常有时候可以充当类似的角色。
如果我们的代码没有刻意捕捉异常,它将会一直向上返回到程序顶层,并启用默认的异常处理器:就是打印标准出错消息。此时,你也许已经熟悉了标准出错消息。这些消息包括引发的异常还有堆栈跟踪:也就是异常发生时激活的程序行和函数清单。
不过,在有些情况下,这并不是我们想要的。例如,服务器程序一般需要在内部错误发生时依然保持工作。如果你不想要默认的异常行为,就需要把调用包装在try语句内,自行捕捉异常。
>>> try:
... 1/0
... except ZeroDivisionError:
... print("不能为0")
...
不能为0
**要手动触发异常,直接执行raise语句。**用户触发的异常的捕捉方式和Python引发的异常一样。
>>> a = 0
>>> try:
... if a ==0:
... raise ZeroDivisionError
... else:
... print(10/a)
... except TypeError:
... print("需要为数字")
...
ZeroDivisionError
try:
#其他语句
except 异常1:
#其他语句
except 异常2:
#其他语句
except:
#其他语句
else:
#其他语句
在这个语句中:
换句话说,except分句会捕捉try代码块执行时所发生的任何异常,而else子句只在try代码块执行时不发生异常才会执行。
except没有限制,else最好有且至多一个
分句形式 | 含义 |
---|---|
except: | 捕获所有(其他)异常类型 |
except name: | 只捕获特定的异常 |
except name as name1: | 异常别名 |
except name,value: | 捕获所列的异常和其额外的数据(或实例) |
except (name1,name2): | 捕获任何列出的异常 |
except (name1,name2),value: | 捕获任何列出的异常和其额外的数据(或实例) |
else: | 如果没有引发异常,就运行 |
finally: | 总是会运行此代码块 |
空的except尽管方便,也可能捕获和代码无关、意料之外的系统异常。此时python3有个替代方案:捕获一个名为Exception的异常,这个异常忽略了系统退出相关的异常。
try:
action()
except Exception:
...
try:
//其他语句
finally:
//执行语句
try语句的另一种形式是特定的形式,和最终动作有关。如果在try中包含了finally子句,Python一定会在try语句后执行其语句代码块,无论try代码块执行时是否发生了异常。
利用这个变体,Python可先执行tr y首行下的语句代码块。接下来发生的事情,取决于try代码块中是否发生异常。
我们可以在同一个try语句中混合finally、except以及else子句。也就是说,我们现在可以编写下列形式的语句:
try:
#do
except Exception1:
#handle1
except Exception2:
#handler2
else:
#handler3
finally:
#do2
要显式地触发异常,可以使用raise语句,其一般形式相当简单。raise语句的组成是:raise关键字,后面跟着可选的要引发的类或者类的一个实例:
raise IndexError
raise IndexError()
利用raise,可以重新引发异常
try:
#do
except IndexError:
print("error")
raise #将IndexError重新抛出去了。
通过这种方式执行raise时,会重新引发异常,并将其传递给更高层的处理器(或者顶层的默认处理器,它会停止程序,打印标准出错消息)。
rasie exception from otherexception
当使用from的时候,第二个表达式指定了另一个异常类或实例,它会附加到引发异常的_cause_属性。如果引发的异常没有捕获,Python把异常也作为标准出错消息的一部分打印出来:
try:
1/0
except Exception as E:
raise TypeError('Bad') from E
最后上层能看到的是TypeError异常。
**当在一个异常处理器内部引发一个异常的时候,隐式地遵从类似的过程:前一个异常附加到新的异常的_context_
属性,并且如果该异常未捕获的话,再次显示在标准出错消息中。**这是一个高级的并且多少还有些含糊的扩展,因此,请参阅Python的手册以了解详细内容。
简而言之,with/as语句的设计是作为常见try/finally用法模式的替代方案。就像try lfinally语句,with/as语句也是用于定义必须执行的终止或“清理”行为,无论处理步骤中是否发生异常。不过,和try/finally不同的是,with语句支持更丰富的基于对象的协议,可以为代码块定义支持进入和离开动作。
Python以环境管理器强化一些内置工具,例如,自动自行关闭的文件,以及对锁的自动上锁和开锁,程序员也可以用类编写自己的环境管理器。
with expression [as variable]
with-block
在这里的expression要返回一个对象,从而支持环境管理协议(稍后会谈到这个协议的更多内容)。如果选用的as子句存在时,此对象也可返回一个值,赋值给变量名variable。
注意: variable并非赋值为expression的结果。expression的结果是支持环境协议的对象,而variable则是赋值为其他的东西。然后,expression返回的对象可在with-block开始前,先执行启动程序,并且在该代码块完成后,执行终止程序代码,无论该代码块是否引发异常。
有些内置的Python对象已得到强化,支持了环境管理协议,因此可以用于with语句。例如,文件对象有环境管理器,可在with代码块后自动关闭文件,无论是否引发异常。
with open(r'c:\misc\data.txt') as myfile:
for line in myfile:
print(line)
因为myfile对象也支持with语句所使用的环境管理协议。在这个with语句执行后,环境管理机制保证由myfile所引用的文件对象会自动关闭,即使处理该文件时,for循环引发了异常也是如此。
尽管文件对象在垃圾回收时自动关闭,然而,并不总是能够很容易地知道会何时发生。
要实现环境管理器,使用特殊的方法来接入with语句,该方法属于运算符重载的范畴。用在with语句中对象所需的接口有点复杂,而多数程序员只需知道如何使用现有的环境管理器。不过,对那些可能想写新的环境管理器的工具创造者而言,我们快速浏览其中细节吧。
以下是with语句实际的工作方式。
__enter__
和_exit__
方法。_enter_
方法会被调用。如果as子句存在,其返回值会赋值给As子句中的变量,否则,直接丢弃。__exit__ (type,value,traceback)
方法就会被调用(带有异常细节)。这些也是由sys.exc_info
返回的相同值(Python手册和本书这部分稍后会做说明)。如果此方法返回值为假,则异常会重新引发。否则,异常会终止。正常情况下异常是应该被重新引发,这样的话才能传递到with语句之外。__exit__
方法依然会被调用,其type、value以及traceback参数都会以None传递。class TraceBlock: def message(self, arg): print("run:",arg) def __enter__(self): print("start with block") return self def __exit__(self, exc_type, exc_value, exc_tb): if exc_type is None: print("exited normally") else: print("raise an exception", exc_type) return False with TraceBlock() as tb: tb.message("——————————————尝试1——————————") print("——————————————reached——————————————") with TraceBlock() as tb: tb.message("-------------尝试2------------") raise TypeError print("-------------not reached-------------")
输出:
(venv) C:\Users\29700\Desktop\gams>python main.py
start with block
run: ——————————————尝试1——————————
——————————————reached——————————————
exited normally
start with block
run: -------------尝试2------------
raise an exception <class 'TypeError'>
Traceback (most recent call last):
File "main.py", line 21, in <module>
raise TypeError
TypeError
环境管理器是有些高级的机制,还不是Python的正式组成部分,所以我们在这里跳过了其他细节(参考Python的标准手册来了解细节。例如,新的contextlib标准模块)
字符串异常(新版本好像不行)
myexc = "My exception string"
try:
raise myexc
except myexc:
print("捕获")
基于类的异常
I= IndexError("index error") print(I.args) #('index error',) class E(Exception):pass try: raise E('spam') except E as e: print(e,e.args) #spam ('spam',) class Bad(Exception): def __str__(self): return "bad bad" try: raise Bad except Bad as e: print(e) #bad bad
字符串是不可变的序列,并且他们不可以在原地修改。
s = '' #空字符串 s = "spam's" #双引号和单引号相同 s = "s\np\ta\x00m" #转义序列 s = """...""" #三重引号字符串块 s = r'\temp\new' #Raw字符串 s = b'spam' #python3.0中的字节字符串 s = u'spam' #仅在python2.6中使用Unicode字符串 s1 + s2 #合并 s * 3 #重复 s[i] #索引,获取第i位字符 s[i:j] #分片,还有s[:]表示全部,s[i:]从i位到最后。 len(s) #求长度 "a %s is xxx"%kind #格式化 “a{} parrot”.format(kind) #格式化 s.find('pa') #字符串查找。还有s.rfind() s.rstrip() #将字符串最右侧的空格移除 s.lstrip() #将字符串最左侧的空格移除 s.strip() #将字符串两侧的空格移除。 s.replace('pa','tj') #替换,全部替换。 s.split(',') #切割,返回list. s.isdigit() #判断是否是数字字符串,比如 “1231145”字符串,类似正则/^[0-9]$/g s.lower() #都转为小写,比如A->a,只对[A-Z]有效。 s.upper() #都转为大写 s.capitalize() 将字符串的第一个字母变成大写,其他字母变小写。对于 8 位字节编码需要根据本地环境。 s.casefold() #casefold() 方法返回一个字符串,其中所有字符均为小写。字符集更大。 s.center(10,'aaa') #center()方法以字符串宽度(width)为中心。使用指定的填充字符(fillchar)填充完成。默认填充字符(fillchar)是一个空格。 s.count(subStr) #统计子串在s中出现的次数。 s.encode(encoding='UTF-8',errors='strict') #指定编码格式。 s.endswith(‘ga’)# endswith(‘ga’) 方法用于判断字符串是否以指定后缀结尾 s.exandtabs(tabsize) #将制表符的大小设置为指定的空格数。 s.isalnum() #如果s至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False s.isalpha() #检查文本中的所有字符是否都是字母 s.isascii() #如果字符串为空或字符串中的所有字符都是 ASCII,则返回 True,否则返回 False。ASCII 字符的码位在 U+0000-U+007F 范围内。 s.isdecimal() #isdecimal()方法检查字符串是否只包含十进制字符。这种方法只存在于unicode对象。 s.isidentifier() #判断字符串是否是有效的 Python 标识符,即可用此方法来判断变量名是否合法。'' s.islower() #检测字符串是否由小写字母组成。 s.isupper() #检测字符串是否由大写字母组成。 s.isnumeric() #检查字符串是否仅包含数字字符。此方法仅用于unicode对象上。 s.isprintable() #检查文本中的所有字符是否可打印,比如\n,\t就不是可打印的。 s.isspace() #检查文本中的所有字符是否都是空格: s.title() #将每个单词的首字母大写:"Welcome to my world" ->'Welcome To My World "&".join(strList) # '-'.join(["Bill", "Steve", "Elon"]) ---> 'Bill-Steve-Elon' s.index() #从左开始查索引,s.rindex()从右。 s.swapcase() #将小写字母大写,大写字母小写:
注意 - 与Python 2不同,所有字符串在Python 3中都用Unicode表示
format_map
class Default(dict):
def __missing__(self, key):
return key
'{name} was born in {country}'.format_map(Default(name='Guido'))
#'Guido was born in country'
"""
类似于 str.format(**mapping),不同之处在于 mapping 会被直接使用而不是复制到一个 dict。 适宜使用此方法的一个例子是当 mapping 为 dict 的子类的情况
"""
ljust和rjust
txt = "banana"
x = txt.ljust(20)
print(x, "-----$")
#banana -----$
maketrans和translate
#创建一个映射表,并在translate()方法中使用它来将任何“S”字符替换为“P”字符:
txt = "Hello Sam!";
mytable = txt.maketrans("S", "P");
print(txt.translate(mytable));
partition
"""
partition 搜索单词 "bananas",并返回包含三个元素的元组:
1 - “匹配”之前的所有内容
2 - “匹配”
3 - “匹配”之后的所有内容
rpartition() 方法搜索指定字符串的最后一次出现,并将该字符串拆分为包含三个元素的元组。
"""
txt = "I could eat bananas all day, bananas are my favorite fruit"
x = txt.partition("bananas")
print(x)
#('I could eat ', 'bananas', ' all day, bananas are my favorite fruit')
removesuffix和removeprefix.一个是后,一个是前。
'MiscTests'.removesuffix('Tests')
#'Misc'
'TmpDirMixin'.removesuffix('Tests')
#'TmpDirMixin'
splitlines() -将字符串拆分为一个列表,其中每一行都是一个列表项:(类似s.split(‘\n’),但比split分割行好用。)
txt = "Thank you for your visiting\nWelcome to China"
x = txt.splitlines()
print(x)
#['Thank you for the music', 'Welcome to the jungle']
s = """
111111
222222
33333
"""
print(s.splitlines())
#['', '111111', '222222', '33333']
startswitch
txt = "Hello, welcome to my world."
x = txt.startswith("Hello")
print(x) #True
zfill - 用零填充字符串,直到长度为 10 个字符
txt = "50"
x = txt.zfill(10)
print(x)#0000000050
字符串和字节串
就是怎么将‘str’ -> b’str’
str = 'agag'
#法一
bytes(str,encoding="utf8")
#法二
str.encode()
转回去类似
s = b'12315'
#法一
str(s,encoding='utf8')、
#法二:
s.decode()
字典有时又叫做关联数组(associative array)或者是散列表(hash)。它们通过键将一系列值联系起来,这样就可以使用键从字典中取出一项。就像列表那样,同样可以使用索引操作从字典中获取内容。但是索引采取键的形式,而不是相对偏移。
#1. 声明字典的几种方式: d1 = {} d2 = dict() d3 = dict({'name':1}) print(d3) d4 = dict(name=1,age=2) #{'name': 1, 'age': 2} print(d4) d5 = dict.fromkeys(['a','b']) print(d5) #{'a': None, 'b': None} keyslist = ['name','age'] valslist = ['joker',30] d7 = dict(zip(keyslist,valslist)) #{'name': 'joker', 'age': 30} print(d7) #2.支持嵌套 d = {'person': {'name':'joker','age':30}} #3.获取字典值得几种方式。 d = {'person': {'name':'joker','age':30}} print(d['person']) print(d['person']['name']) #4.键存在测试 d = {'person': {'name':'joker','age':30}} print('person' in d) #True print('humman' in d) #False #5. 获取第一层所有键 d.keys() #6.获取第一层所有值 d.values() #7. 获取键+值 d = {"name":"joker","age":30} print(d.items()) #dict_items([('name', 'joker'), ('age', 30)]) #8. 字典浅拷贝 person = {"name":"joker","age":30} d = {"person":person} d2 = d.copy() print(d2) person['name'] = 'ttt' print(d2) #{'person': {'name': 'ttt', 'age': 30}} #9.d.get(key,default) d = {'name':'joker'} v = d.get('age') print(v) #None v = d.get('age',30) print(v) #30,设置默认. #10.合并,浅合并 d1 = {'name':'joker'} d2 = {'age':30,'obj': {'sex':0}} d1.update(d2) print(d1) #{'name': 'joker', 'age': 30, 'obj': {'sex': 0}} print(d2) #{'age': 30, 'obj': {'sex': 0}} d2['obj']['sex'] =1 print(d1) #{'name': 'joker', 'age': 30, 'obj': {'sex': 1}} #11.删除k-v d1= {'name':'joker','age':30} d1.pop('age') print(d1) #{'name': 'joker'} del d1['name'] #12.长度 len(d1) #13. list key list(d.keys()) #14.key的与操作,还有或 d1 = {'name':'joker','age':30} d2 = {'sex':0,'id':100,'age':20} print(d1.keys() & d2.keys()) #{'age'} print(d1.keys() | d2.keys()) #set集合{'age', 'name', 'id', 'sex'} #14. 字典解析 d = {x:x*2 for x in range(5)} print(d) #{0: 0, 1: 2, 2: 4, 3: 6, 4: 8}
Python 在查找"名称"时,是按照 LEGB 规则查找的:
Local–>Enclosed–>Global–>Built in
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。