python 基础入门

#### 小记各种小语法

```...```  // 多行的string

r' '   // 范围转义的字符串

\"  // 转义富豪

b' ' //byte

f'{x:.2f}' // string format的一种写法

[,,,,,] // list 格式

[[],,,,,]// 二维数组

list[0] , list[-1] // 数组访问, 数组第一个元素,数组最后一个元素

(,,,,) // tuple格式 注意定义长度只有一个元素的 tuple 要这样(a,) 否则会产生歧义

(,,,,[]) // 可变tuple格式,只是里面的list可变

{:,:}// dict格式

{'KEY':'VALUE',:}['KEY'] // dict取值 这种方式如果key找不到就会报错,所以可以用get()函数

{}['key'] = ... // dict存值 

{1,2,3} // set格式

{[1,2,3,4,5,5]}// set通过list创建

b**n  // b的n次方

// 函数格式:一定要注意锁进
>>> def my_abs(x):
...     if x<0 :
...             return -x
...     else:
...             return x
... 
>>> my_abs(-1)
>>> 1

// pass 占位符,定义空函数用得到,当然还有别的用处

// 函数可变参数的格式: 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
def xxx(*num): 
nums = [1,2,3]
xxx(*nums) // list或tuple转化为可变参数传递.*nums表示把nums这个list的所有元素作为可变参数传进去。

// 关键字参数的格式:关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def xxx(**kw):
detail = {"name":"jing","age":13}
 xxx(**detail) // 把该detail转换为关键字参数传进去,**detail表示把detail这个dicts转化为关键字参数穿进去。注意kw获得的是detail的copy,kw的改变不会影响到detail。

// 参数格式
def xx(*num) //  1个可变参数
def xx(**num) //  1个关键字参数
def xx(a,b,*,city,age) //  2个位置+2 个命名关键字参数
def xx(a,b,*numbers,city,age) // 2个位置+1个可变 + 2 个命名关键字参数
def xx(a,b,c=0,*args,**kw) //  3个位置(第三个有默认值)+1个可变+1个关键字参数
def xx(a,b,c=0,*,d,**kw) // 3个位置(第三个有默认值) + 1个命名关键字+1个可变参数

// 切片语法
list[:] # 从取第一个到最后一个元素
list[:2] # 取第一个到index = 2但不包含2的元素数组
list[-2:] # 取倒数第二个元素到最后一个元素 
list[::5] # 先取所有元素然后每隔5个取一个
list[:10:2] # 先取0~index = 10但不包含10的元素然后每隔2个取一个
'aqerqerqer'[::2] # 字符串也支持这个语法

// 迭代语法
for x,y in dict.items() :
for x in list:

// 列表生成式:快速生成list(在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else。)
[x+y for x in list for y in list2] # 2曾循环,当然三四五层也可以eg:[x+y+z for x in list1 for y in list2 for z in list3]
[x**2 for x in list if x%2 == 0] # 如果是偶数则计算平方,然后只输出偶数平方数组
[x**2 if x%2 == 0 else -x for x in list] # 如果是偶数则输出平方不是则输出-x 然后组成list输出
[k + '=' + v for k,v in dict.items()]  # 遍历dict然后按规则(k=v)输出list

// 迭代器生成
for 循环+ yield 关键字
(x+y for x in list for y in list2)   #迭代生成式 语法和列表生成式 一啊样就是 【】 变 ()

// isinstance(要判断的对象,标尺对象)
isinstance([],list)
isinstance("",str)
isinstance({}, dict)
isinstance((), tuple)

from collections.abc import Iterable
isinstance([], Iterable)
from collections.abc import Iterator
isinstance((x for x in range(10)), Iterator)

// 倒入模块:
import sys as s // 意思是倒入sys模块并起了个别名叫s

// 运算符
// : 取整的除法 10 // 3 = 3
/  : 精确的除法9 / 3 = 3.0


# python 世界观

可变元素: dict list set
不可变元素:tuple ,string,number,。。。

1. 对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。而是这些方法会创建新的对象并返回。
2. 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”.
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
>>> 1
3.  python调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError.
4.  python函数可以返回多个值,但是只是一个假象,实则返回的是tuple,只是方便书写
>>>import math
>>>def move(x, y, step, angle=0):
>>>    nx = x + step * math.cos(angle)
>>>    ny = y - step * math.sin(angle)
>>>    return nx, ny

>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
>>> (151.96152422706632, 70.0)
5.  定义函数时,需要确定函数名和参数个数,如果有必要,可以先对参数的数据类型做检查;函数可以同时返回多个值,但其实就是一个tuple。

6. 定义默认参数要牢记一点:默认参数必须指向不变对象!Python函数在定义的时候,
默认参数的值就被计算出来了,因为默认参数是个变量,它指向一个可变对象的时候,
连续调用该函数,如果改变了默认参数的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的初始默认参数了。

7. 位置参数必须放在默认参数前,可变参数放在命名关键字参数前,命名关键字参数放在关键字参数前。

8. 自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。

9. 代码规范: 
(1).正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
(2).类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如__author__,__name__,__doc__就是特殊变量,自己的变量一般不要用这种变量名;
(3).类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;(之所以说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量)
(4).外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
(5).python支持多重继承(这种模式叫MixIn),所以用于给别的类多重继承的类名MixIn结尾。eg:ForkingMixIn,ThreadingMixIn (Python自带),MixIn结尾用来标记!
(6).按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass(元类:可以创建类)
(6).
 10. 

1. 输入输出

# 输出函数
print()
# 输入函数(注意input返回的类型是string类型,内置的int()函数可以进行部分字符串的类型转化)
name =  input("placehold:\n")

2. 字符串的小细节

# 转义字符 \ 这个都知道,比如 想输出 my name is "JING"
a = "my name is \"JING\""
# 还有个写法就是 r''  单引号里面的特殊符号默认都用了\
a = r'my name is "JING"'
# 上面两个打印时一样的
print (a)
# 多行:用这个'''...''' 这个格式
>>> a = '''...aaa
... bbb
... ccc
... ddd'''
>>> a
'...aaa\nbbb\nccc\nddd'
# r 和 '''...''' 可以组合使用
>>> a = r'''...a
... b
... c
... d'''
>>> a
'...a\nb\nc\nd'

# 字符串的编解码 encode 返回的是bytes,decode返回的是string,b开头的是bytes
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
# 如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节:
>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'

# 计算字符串长度
>>> len('ABC')
3
>>> len('中文')
2
#  计算字节数
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6
 
# 字符串格式化 %d  %f %s %x(十六进制整数)
# 其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
print('%2d-%02d' % (3, 1))
>>> 3-01
print('%.2f' % 3.1415926)
>>> 3.14

>>> a = input()
111
>>> b = input()
666
>>> print("%d+%d=%d" % (int(a),int(b),int(a)+int(b)))
111+666=777

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串:
>>> print("%s+%s=%s"%(a,b,int(a)+int(b)))
111+666=777
# format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
# f-string 格式:f'{变量:占位符}{变量:占位符}'  占位符是.2f 
>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62

3. python中的除法

>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
>>> 10 % 3
1

4. list :可改变的 && tuple:tuple一旦初始化就不能修改

list = [0,2,3,4]
len(list) //长度
list.append('admin') // 追加
list.insert(1,'aaa') //  插入
list.pop() // 要删除list末尾的元素
list.pop(0) //  删除第一个元素 
list[0] = 123 // 替换指定位置元素

5. dict :dict的key必须是不可变对象。

# 取值
dict = {"1":1,"2":2}
dict.get("3",3) 
>>> 3 
dict["3"]
>>> KeyError: '3'
#判断key存在
ishave =  '3' in dict
# 删除 pop
dict.pop("3")

set:set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象!!!!,所以无法把list放入set

# 创建
set = {1,2,2,2,4}
set = {[1,2,3,4,6,5,4,5]}
# 添加
set.add(5)
# 移除
set.remove(5)
# set 是集合所以可以 交集,并集,补集
{1,2,3} & {2,4,5}
>>> {2}
{1,2,3} | {2,4,5}
>>>
{1,2,3,4,5}

6. 列表/字典/元组推导式 比下面的两个函数更优雅~

Map会将一个函数映射到一个输入列表的所有元素上(lambdas :匿名函数)
语法:map(方法,数组)
items = [1, 2, 3, 4, 5]
squared = []
for i in items:
    squared.append(i**2)
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))

稍复杂的使用:(方法也是对象,funcs:方法数组)

def multiply(x):
        return (x*x)
def add(x):
        return (x+x)

funcs = [multiply, add]
for i in range(5):
    value = map(lambda x: x(i), funcs)
    print(list(value))
    #        上面print时,加了list转换,是为了python2/3的兼容性
    #        在python2中map直接返回列表,但在python3中返回迭代器
    #        因此为了兼容python3, 需要list转换一下
# Output:
# [0, 0]
# [1, 2]
# [4, 4]
# [9, 6]
# [16, 8]

filter过滤列表中的元素

number_list = range(-5, 5)
less_than_zero = filter(lambda x: x < 0, number_list)
print(list(less_than_zero))  

Reduce对一个列表进行一些计算并返回结果

from functools import reduce
product = reduce( (lambda x, y: x * y), [1, 2, 3, 4] )

# Output: 24

9.函数

# 最简单的函数定义
def enroll():
  print("111")

enroll()
# 有参数
def enroll(name,age):
   print(f"name is {name},age is {age}")
enroll("xiaoming",13)
# 默认参数(小心坑!!!!)
def enroll(name,age = 2):
   print(f"name is {name},age is {age}")
enroll("xiaoming")
// 默认参数的坑
def add_end(l = []):
  l.append('end')
  return l
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
// 连续调用的时候就出现了问题,原因是Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
>>> add_end()
['END']
>>> add_end()
['END','END']
// 修复
def add_end(l = None):
  if l  is None:
    l  = []
  l .append('end')
  return l 

# 不按顺序传递参数的时候
def enroll(name,age = 2,city = 'beijing'):
   print(f"name is {name},age is {age},city is {city}")
enroll("xiaoming", city = 'henan')

# 可变参数,参数前加个* ,会把多余的参数封城一个tuple 赋予给*后面的变量
def calc(*number):
  sum = 0
  for num in number
    sum += num
  return sum
// 调用 
calc(1,2,3,4,5)
// or
list = [1,2,3]
calc(*list)  等价于 calc(list[0],list[1],list[2])


# 关键字参数
def calc(**kw):

// 调用 
calc("111"=1,"222" = 3)
// or
dict = {"111":1,"222":3}
calc(**dict)

#  命名关键字参数
def calc(name,age,*,"111","222")

// 调用 
calc("jing",13,"111"=1,"222" = 3)
calc("jing",13,1,3) // 报错(命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错,由于调用时缺少参数名"111","222",Python解释器把这4个参数均视为位置参数,但calc()函数仅接受2个位置参数。

// or
dict = {"111":1,"222":3}
calc("jing",13,**dict)

# 有缺省值的明明关键字参数
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
// 注意:使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
    # 缺少 *,city和job被视为位置参数
    pass

// 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
    print(name, age, args, city, job)

10. 面向对象

# 类实例
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', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

# 类:
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

# 实例:
>>> bart = Student()
# 打印
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student


总结:
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。
✨:类中定义的函数,第一个参数永远是实例变量self
✨:类中定义的函数,第一个参数永远是实例变量self
✨:类中定义的函数,第一个参数永远是实例变量self

# 访问限制:属性的名称前加上两个下划线__
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', 59)
>>> bart.__name
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'Student' object has no attribute '__name'

# 这样不能改变的同时也不能访问了,所以可以重写get
class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

# 有想要重新改变了可以重写set
class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

# 错误的写法
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
✨ :表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!不是一个变量!不是一个变量!外部代码给bart新增了一个__name变量。

总结:
set和get的好处是可以对参数做检查,避免传入无效的参数

name 是变量
__name是私有变量
__score__是特殊变量

// 继承和多态
class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    pass

class Cat(Animal):
    pass

dog = Dog()
dog.run()

cat = Cat()
cat.run()

// 重写父方法
class Dog(Animal):

    def run(self):
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

// 类型判断
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)
True
>>> b = Animal()
>>> isinstance(b, Dog)
False
# 获取对象信息 type() 函数
>>> type(123)

>>> type('str')

>>> type(None)

>>> type(abs)

>>> type(a)

>>> type(123)==type(456)
True
# 判断基本数据类型可以直接写int,str等
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
# isinstance
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

# 判断一个对象是否是函数
>>> 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

# 复杂一点,下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

# 使用dir():如果要获得一个对象的所有属性和方法,可以使用dir()函数
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
# 类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__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()
>>> 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 "", line 1, in 
AttributeError: 'MyObject' object has no attribute 'z'
# 解决:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
# 可以获得对象的方法:(python世界观!一切皆为对象,所有的变量只是变量,能指向常量,函数,lambda,属性)
>>> hasattr(obj, 'power') # 有属性'power'吗? power就是一个指向函数的变量
True
>>> getattr(obj, 'power') # 获取属性'power'
>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
# 应用
def readImage(fp):
    if hasattr(fp, 'read'):  # 如果对象有read方法则可以把它当作一个数据流方法
        return readData(fp) # readData里面调用了fp.read 所以上一步判断是不是支持read
    return None

总结:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

 dir(obj) # 获取obj的方法和属性
 hasattr(obj, 'x') # obj有属性'x'吗?
 setattr(obj, 'y', 19) # obj设置一个属性'y'
 getattr(obj, 'y') # 获取obj属性'y'
 len(obj) # 调用的是obj的 __len__ 方法,如果想让obj支持len()则实现__len__方法。


print(type(Animal().run))
print(type(Animal().run()))
print(type(Animal.run))
输出:



实例属性和类属性

class Student(object):
    name = 'Student' # 直接在class中定义属性,这种属性是类属性,归Student类所有
def __init__(self, name):
        self.name = name # 归实例所有

# 完整代码
>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
# 一个例子,统计实例的数目
class Student(object):
 count = 0

 def __init__(self, name):
  Student.count += 1
  self.name = name

总结:

实例属性属于各个实例所有,互不干扰;
类属性属于类所有,所有实例共享一个属性;
不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。

11. 枚举

定义枚举

# 1. 第一种
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 2. 第二种:@unique装饰器可以帮助我们检查保证没有重复值。
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

访问枚举

// 常量属性获取枚举常量Weekday.Tue
>>> print(Weekday.Tue)
Weekday.Tue
// 常量名称获取枚举常量Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
// 枚举常量的value
>>> print(Weekday.Tue.value)
2
// 根据value的值获得枚举常量。
>>> print(Weekday(1))
Weekday.Mon

使用例子

# -*- coding: utf-8 -*-
from enum import Enum, unique
class Gender(Enum):
    Male = 0
    Female = 1

class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

bart = Student('Bart', Gender.Male)

12. 元类

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)
>>> h = Hello()
>>> h.hello()

# Hello是一个class,它的类型就是type
# h是Hello一个实例,它的类型就是class Hello

// type()函数创建出新的类型
>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

要创建一个class对象,type()函数依次传入3个参数:
class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

# 通过type()函数创建的类和直接写class是完全一样的,
# 因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
// metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
✨✨✨✨:连接起来就是:先定义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__()方法接收到的参数依次是:
# 当前准备创建的类的对象;
# 类的名字;
# 类继承的父类集合;
# 类的方法集合。
// 有点复杂,用到的时候可以再看看!!
https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
✨✨✨✨:metaclass是Python中非常具有魔术性的对象,它可以改变类创建时的行为。这种强大的功能使用起来务必小心。

13. 单元测试

// mydict.py代码如下:
class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

// 测试模块(mydict_test.py)
import unittest
from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')  # 断言函数返回的结果与'value'相等

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty'] # 通过d['empty']访问不存在的key时,断言会抛出KeyError

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty   # 通过d.empty访问不存在的key时,我们期待抛出AttributeError

讲解:
以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。

// 运行单元测试:
1. 最简单的运行方式是在mydict_test.py的最后加上两行代码:
if __name__ == '__main__':
    unittest.main()

2. 命令行通过参数-m unittest直接运行单元测试:(命令行通过参数-m unittest直接运行单元测试)
python -m unittest mydict_test

# setUp与tearDown
以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:

# 完整例子代码:
# -*- coding: utf-8 -*-
import unittest
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        if self.score > 100 or self.score < 0:
            raise ValueError('invalid value: %d' % self.score)
        if self.score >= 80:
            return 'A'
        if self.score >= 60:
            return 'B'
        
        return 'C'

class TestStudent(unittest.TestCase):
    def test_80_to_100(self):
        s1 = Student('Bart', 80)
        s2 = Student('Lisa', 100)
        self.assertEqual(s1.get_grade(), 'A')
        self.assertEqual(s2.get_grade(), 'A')

    def test_60_to_80(self):
        s1 = Student('Bart', 60)
        s2 = Student('Lisa', 79)
        self.assertEqual(s1.get_grade(), 'B')
        self.assertEqual(s2.get_grade(), 'B')

    def test_0_to_60(self):
        s1 = Student('Bart', 0)
        s2 = Student('Lisa', 59)
        self.assertEqual(s1.get_grade(), 'C')
        self.assertEqual(s2.get_grade(), 'C')

    def test_invalid(self):
        s1 = Student('Bart', -1)
        s2 = Student('Lisa', 101)
        with self.assertRaises(ValueError):
            s1.get_grade()
        with self.assertRaises(ValueError):
            s2.get_grade()

if __name__ == '__main__':
    unittest.main()

文档测试

# mydict2.py
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

if __name__=='__main__':
    import doctest
    doctest.testmod()

// 运行
python mydict2.py
// 什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把__getattr__()方法注释掉,再运行就会报错:
$ python mydict2.py
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 10, in __main__.Dict
Failed example:
    d1.x
Exception raised:
    Traceback (most recent call last):
      ...
    AttributeError: 'Dict' object has no attribute 'x'
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 16, in __main__.Dict
Failed example:
    d2.c
Exception raised:
    Traceback (most recent call last):
      ...
    AttributeError: 'Dict' object has no attribute 'c'
**********************************************************************
1 items had failures:
   2 of   9 in __main__.Dict
***Test Failed*** 2 failures.

# 注意到最后3行代码。当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。

// 一个例子
def fact(n):
    '''
    Calculate 1*2*...*n
    
    >>> fact(1)
    1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):
        ...
    ValueError

    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

# Traceback (most recent call last):  后面的 ...是省略 乱七八糟的路径

✨✨✨✨:doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest。

你可能感兴趣的:(python 基础入门)