python:浅说判断

导读:本篇文章讲解 python:浅说判断,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一,简单的思考——判断函数参数是否为一般合理的字符串

假设有这样一个函数,接收英文人名参数,然后对其进行保存。再保存前,显然要判断接收到的参数是否合理。

空字符串""是不合理的,由于空字符串并不是空对象,它始终还是一个字符串:

type("")
>>> <class 'str'>

所以就不能直接将它与 python 中使用来表示 NullNone对象进行比较判断:

def test(arg):
    if isinstance(arg, type(None)):
        print(f"{arg} 是 None 类型")
    else:
        print(f"{arg} 不是 None 类型")

test(arg="")
>>>  不是 None 类型

最简单的方法就是直接判断它是否为"":

def test(arg):
    if arg == "":
        print(f"{arg} 是空字符串,不合理")
    else:
        print(f"{arg} 不是空字符串")

        
test(arg="")
>>>  是空字符串

字符串是有长度的,等效的方法是判断其长度是否为0:

def test(arg):
    if len(arg) == 0:
        print(f"{arg} 是空字符串,不合理")
    else:
        print(f"{arg} 不是空字符串")

test(arg="")
>>>  是空字符串

然而,在一般应用中,由全空格组成的字符串" "也应该算作不合理的,所以仅通过判断它的长度是否合理的方法就失效了:

def test(arg):
    if len(arg) == 0:
        print(f"{arg} 是空字符串,不合理")
    else:
        print(f"{arg} 不是空字符串,字符串长度为{len(arg)},但也不合理")

test(arg="   ")
>>>     不是空字符串,字符串长度为3,但也不合理

这可以根据str.isspace()方法的返回值判断字符串是否仅由空格组成来解决:

>>> def test(arg):
    if arg.isspace():
        print(f"{arg} 不是空字符串,字符串长度为{len(arg)},但也不合理")
    else:
        print(f"{arg} 不全由空格组成,但也不合理")

>>> test(arg="   ")
    不是空字符串,字符串长度为3,但也不合理
>>> test(arg="   aaa")
   aaa 不全由空格组成,但也不合理

而且,在一般应用中,字符串头尾两边包含空格也是不应该被允许的。调用str.strip()等方法能去除字符串头尾的空格,这些方法返回移除字符串头尾默认指定的空格字符后生成的新字符串:

def test(arg):
    if arg.strip():
        print(f"{arg} 去除头尾的空格后不是空字符串,原字符串长度为{len(arg)},但也不合理")
    else:
        print(f"{arg} 去除头尾的空格后是空字符串,不合理")

        
test(arg="   ")
>>>  去除头尾的空格后是空字符串,不合理
test(arg="")
>>>  去除头尾的空格后是空字符串,不合理
test(arg="   aaa")
>>>    aaa 去除头尾的空格后不是空字符串,原字符串长度为6,但也不合理

既然strip新生成的字符串可能是"",为什么最开始不像下面这样直接判断呢:

def test(arg):
    if arg:
        print(f"{arg} 不是空字符串")
    else:
        print(f"{arg} 是空字符串")

test(arg="")
>>> 是空字符串
test(arg="   ")
>>>     不是空字符串
test(arg="   aaa")
>>>    aaa 不是空字符串

>>> "" == True
False
>>> "" != True
True

其实效果是一样的,但这样会更加的简洁,更加符合PEP 20 – The Zen of Python的精神。

这涉及到 Truthy 和 Falsy 的概念。

二,真值与假值

1,True 与 False 的本质

计算结果或值本身不是真值,它们只是被评估为真值 True 或假值 False。

在最开始的几个例子的条件判断中,我们对带有操作数和运算符的表达式进行判断:表达式的计算结果为True 或 False 。就像下面这样:

def test():
    if 5 < 3:
        print("True")
    else:
        print("False")
 
test()
>>> False
  • obj.method()是一种特殊的表达式。

当我们不涉及运算符和操作数而直接判断某变量时,则会直接将这个单独的变量与真值或假值进行比较,就可能会出现下面的情况:

a = 1
if a:
	print(a)
>>> 1

a = 0
if a:
	print(a)
>>> 	# 没有输出

变量 a 是真实存在的,它指向内存中一个确定的区域,其中就包含了该变量的值,我们能直接通过这个变量名来操作内存中的变量值。
既然变量 a 是真实存在的那么它就应该是“真的”,确实。之所以造成 if 对拥有不同变量值的同一变量产生不同的判断,有下面两个原因:

  1. 单个变量也可以像一个表达式那样被计算为 True 或 False 。
  2. Python 语言规则已经确定评估某些值为 True 或 False 。

这就能理解了:如果某些已经被评估为 True 或 False 的值被赋值给了变量,那么直接判断变量的真值就相当于直接判断其变量值的真值。

会被判断为 False 的情况:

  • 空序列和空集合:空列表 []、空元组 ()、空字典 {}、空集 set()、空字符串 ""、空范围 range(0)
  • 任何数字类型的零:整数 0、浮点数0.0、复数 0jDecimal(0)Fraction(0, 1)
  • 常数:NoneFalse

实际上 Python 的 bool (True, False) 是 int 的一个子类,所以布尔值就像整数一样:

>>> isinstance( True, int )
True
>>> isinstance( False, int )
True
>>> 1 == True
True
>>> 0 == False
True
>>> 3 == True
False
>>> (1==1)+(1==1)
2

python doc: Truth Value Testing

2,一些真值测试的说明

其实明白了 True 或 False 就是1 和 0,就能解释一些问题:

>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True

但是这怎么说:

>>> 0 < 0 == 0
False

如果接触过其他编程语言,比如C语言,就应该清楚一个概念:运算符的优先级。实际上,python 是没有比较运算符优先级概念,所有相同运算符(比较运算符、成员检测和标识号比较)的比较都具有相同的优先级,如果成对地使用,python默认会扩展比较:

0 < 0 == 0
等价于
(0 < 0) and (0 == 0)

所以,上面的疑惑可以这样解释:

>>> 0 < 0 == 0
False
>>> (0 < 0) and (0 == 0)
False
>>> 0 < 0
False
>>> 0 == 0
True
>>> False and True
False

产生的有趣现象:

因为:
x < y <= z 等价于 x < y and y <= z
又:
x < y 值为假时,会结束 and 右边的比较
所以:
x < y > z 这样的写法是完全合法的
例如:
>>> 1 < 5 > 2
True

Why does the expression 0 < 0 == 0 return False in Python?

3,参与比较对象到底是什么

在前面,我们对表达式、单个变量进行了真值测试。

在使用表达式进行真值测试时,会先进行表达式内部变量的比较,并将这个比较结果作为表达式的计算结果,然后再在真值测试中与 True 或 False 进行比较。

表达式中参与比较的对象,与单独参与真值比较的变量一样,终究还是一个个独立的内存中对象。

一切皆对象,每个对象都有各自的标识号、类型和值。

  • 标识号用于唯一标识对象。一个对象被创建后,它的标识号就绝不会改变。在 CPython 中,内置的 id(x) 函数返回一个代表对象标识号的整型数,也就是存放 x 对象的内存的地址。
  • 类型决定该对象所支持的操作。
  • 有些对象(可变对象:字典和列表)的值可改变,有些对象(不可变对象:数字、字符串和元组)的值不可改变。

看一个例子(后面解释):

>>> x = 0
>>> id(x)
140722093060336
>>> y = 0
>>> id(y)
140722093060336

Understanding Mutable and Immutable in Python
Two variables in Python have same id, but not lists or tuples

4,对象是如何参与比较的:is 、== 与 and

首先要明确的是:
1,比较运算符包括 <>==>=<=!= ,它们将比较两个对象的值以判断值的大小关系。

>>> 1 < 4 > 3
True
>>> "a" < "b"
True
>>> [1,2,3,4] < [1,2,4]
True
>>> x != x
False

2,成员检测运算符包括 innot in,它们将比较两个对象的值的包含关系。

>>> t_1 = (1,2,3,4)
>>> t_2 = (1,2,4)
>>>> t = 3
>>> t in t_1
True
>>> t not in t_2
True

3,布尔运算符包括 andor ,它们对两个对象进行布尔运算;not将对布尔值“取反”:

>>> True and True
True
>>> True and False
False
>>> False and True
False
>>> True or True
True
>>> True or False
True
>>> False or True
True
>>> False or False
False
>>> not True
False
>>> not False
True

4,标识号比较符包括 isis not ,它们将比较两个对象的标识号以判断是否为同一对象。

>>> True is True
True
>>> True is False
False
>>> False is False
True
>>> True is not False
True

现在来解释“两个变量具有相同的对象标识”这一问题,它涉及到 Cpython 底层的实现,这里只展示结论:
python会维护一个范围是 [-5…256] 的整数数组,当创建变量并其变量值在这个范围内时,将直接引用而不是创建一个新的对象,这就可能导致多个整数变量拥有相同的标识符。

  • Cpython的底层实现还有一个Python string
    interning
    机制:如果变量赋值长度20以内的字符串时,会先检查是否已经有相同内容的字符串对象,有则直接引用:
>>> a = "8" * 20
>>> b = "8" * 20
>>> a is b
True
>>> id(a)
2875922638608
>>> id(b)
2875922638608
>>> a == b
True

为了提高性能,可以使用sys.intern()对字符串进行实习,这个函数允许你比较它们的内存地址,而不是逐字符地比较字符串:
>>> from sys import intern
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b
False
>>> id(a)
1603648396784
>>> id(b)
1603648426160

>>> a = intern(a)
>>> b = intern(b)
>>> a is b
True
>>> id(a)
1603648396784
>>> id(b)
1603648396784

python期望所有对象都应该是自反射的 ,即 x is y 等价于 x == y,因为同一标识号对象的值是一致的。尽管对象的内存地址在任何给定时间都是唯一的,但它在相同代码的运行之间会有所不同。
大多数情况下,具有相同值的不同对象将存储在不同的内存地址中。这意味着我们不应使用 is 运算符来比较值,应将它用于测试某些东西是否为None

def t(x):
    if x:				# 检查 x 是否被赋值
        print('if x')
    if x is not None:	# 检查 x 是否为 none
        print('if x is not None')

>>> t(None)
>>> t("")
if x is not None
>>> t(21)
if x
if x is not None

python doc: 6.10. Comparisons

“is” operator behaves unexpectedly with integers
Why don’t tuples get the same ID when assigned the same values?
Python string interning
Why you should almost never use “is” in Python
Identity Testing
Python ‘!=’ Is Not ‘is not’: Comparing Objects in Python

三,类型判断

在前面,判断函数参数是否为一般合理的字符串的比较好的方法是:

def test(arg):
    if arg.strip():
        print(f"{arg} 去除头尾的空格后不是空字符串,原字符串长度为{len(arg)}")
        
    else:
        print(f"{arg} 去除头尾的空格后是空字符串,不合理")

实际上这是有风险的:strip仅仅是字符串对象的一个方法。

如果用户在输入名字时明确知道需要输入字符串,那么就没有问题。但如果这个函数在某处被调用时传入了其他数据类型的值,将会引发错误。比如:

>>> def test(arg):
    print(f"原字符串是{arg}。")
    res_arg = arg.strip()
    print(f"去除头尾的空格后结果字符串是{res_arg}。")
    if arg:
        if len(arg) > len(res_arg):
            print(f"原字符串不是空字符串,但不合理")
        else:
            print(f"原字符串不是空字符串,且合理")
    else:
        print(f"原字符串是空字符串,不合理")

        
>>> test(arg="")
原字符串是。
去除头尾的空格后结果字符串是。
原字符串是空字符串,不合理
>>> test(arg="   ")
原字符串是   。
去除头尾的空格后结果字符串是。
原字符串不是空字符串,但不合理
>>> test(" aaa  bbb    ")
原字符串是 aaa  bbb    。
去除头尾的空格后结果字符串是aaa  bbb。
原字符串不是空字符串,但不合理
>>> test(arg=123)
原字符串是123。
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    test(arg=123)
  File "<pyshell#73>", line 3, in test
    res_arg = arg.strip()
AttributeError: 'int' object has no attribute 'strip'

所以,是否可以先判断一下数据类型。

python提供下面两种方法来判断变量的数据类型。

1,简单点,用type()

内置的type(object)能够显示对象的类型:

>>> type("")
<class 'str'>
>>> type(None)
<class 'NoneType'>

在显示python基础数据类型时简单明了,但type()却无法处理继承自python基础数据类型的自定义数据类型:

>>> class Spam(int): pass
>>> x = Spam(0)
>>> type(x) == int # False
False
>>> type(x)
<class '__main__.Spam'>

2,准确点,用 issubclass() 或 instance()

虽然可以用内置的issubclass(class, classinfo)函数来解决type()解决不了的子类的类型显示问题:

>>> class Spam(int): pass
>>> issubclass(Spam, int)
True

但更好的办法是调用内置的isinstance(object, classinfo)函数

>>> y = ['test']
>>> isinstance(y, list)
True

>>> class Spam(int): pass
>>> class Span(dict): pass
>>> x = Spam(0)
>>> isinstance(x, int)
True
>>> isinstance(x, dict)
False
>>> isinstance(x, (int, dict))
True

isinstance()能更好地适应python的多态特性。

简单来说,多态体现在子类继承并重写父类方法后,表现为运算的意义取决运算的对象,关注点在对象的行为,而不是类型,意义在于隐藏接口差异性:

def processor(reader, converter, writer):
    while True:
        data = reader.read()
        if not data: break
        data = converter(data)
        writer.write(data)

class Reader:
    def read(self): ...              # Default behavior and tools
    def other(self): ...
class FileReader(Reader):
    def read(self): ...              # Read from a local file
class SocketReader(Reader):
    def read(self): ...              # Read from a network socket
...
processor(FileReader(...),   Converter,  FileWriter(...))
processor(SocketReader(...), Converter,  TapeWriter(...))
processor(FtpReader(...),    Converter,  XmlWriter(...))

这种隐藏差异但又关注对象的行为的思想称为“鸭子类型”:不管它是不是下鸭蛋、是不是长得像鸭子、是不是由金属锻造而成等等,只要它能够嘎嘎叫,那它就是鸭子。

3,python类型提示

python作为一门弱类型的动态类型语言,它在创建变量时会为变量值添加类型标志符去标识这个对象的类型,然后在变量被使用时(运行时)自动引用变量名指向的值并获得对应的类型。

这种既不用在定义变量时显式指定类型,又不用在使用变量时判断变量类型的特点,让对 python 的上手使用变得相当简单。

python 3.6引入了PEP 484 – Type Hints建议的类型提示特性。该特性允许使用特殊的语法实现变量类型提示,但实际不进行类型检查,仅限于提示器的作用:

def greeting(name: str) -> str:
    return 'Hello ' + name
  • 提示函数的参数 name 应该是 str 类型。
  • 提示函数的返回值应该是 str 类型。

有了这项特性,IDE将使用类型检查器等工具让编码的补全、提示能加智能,这让编码过程更加快速和清晰:
在这里插入图片描述
也减轻后期代码维护的压力:
在这里插入图片描述

4,更 pythonic 的方法,捕获异常

既然python是一门弱类型的动态类型语言,它本身就不强调使用时的类型检查,所以我们也就不必要强制类型检查,应该默认 python 能够处理得了类型问题。

如果实在是会因为可能存在处理不了的数据类型而导致程序出错,那就捕获可能的错误:

try:
    ...
except TypeError:
    ...
def test(arg: str):
    print(f"接收到{arg}。")
    try:
        res_arg = arg.strip()
        print(f"去除头尾的空格后结果字符串是{res_arg}。")
        if arg:
            if len(arg) > len(res_arg):
                print(f"原字符串不是空字符串,但不合理")
            else:
                print(f"原字符串不是空字符串,且合理")
        else:
            print(f"原字符串是空字符串,不合理")
    except (AttributeError, TypeError):
        print(f"参数不是字符串!")

        
>>> test(123)	# AttributeError
接收到123。
参数不是字符串!
>>> class A(int): pass
>>> a = A(0)
>>> test(a)		# TypeError
接收到0。
参数不是字符串!

Checking whether a variable is an integer or not [duplicate]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98066.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!