Python中下划线 _ 的最全用法详解


'_'是什么?

_在python中可以作为一个标识符,用于定义变量和方法唯一名称。同时它也是Python中的一个软关键字,指在某些特定上下文中保留的关键字。截至 python3.10 共有3个软关键字:match, case_

我们知道Python3.10之前有35关键字:

False      await      else       import     pass
None       break      except     in         raise
True       class      finally    is         return
and        continue   for        lambda     try
as         def        from       nonlocal   while
assert     del        global     not        with
async      elif       if         or         yield

在开发时我们不能创建与它们同名的变量或函数。也就是不能作为标识符使用。

在Python 3.10中添加了模板匹配语法match case, 为了兼容之前的使用习惯,python将match, case_ 设为了软关键字。

软关键字与关键字不同在于,软关键字只有在其特定的模式匹配语句中才具备其关键字的语义,而平时我们仍然可以将其作为标识符使用,如创建同名的变量或函数。

如:match语句(需要安装python3.10版本)

In [1]: def http_errors(status):
   ...:     match status:
   ...:         case 400:
   ...:             print('Bad Request!')
   ...:         case 401:
   ...:             print('Unauthorized!')
   ...:         case 404:
   ...:             print('NotFound!')
   ...:         case 418:
   ...:             print("I'm a teapot")
   ...:         case _:
   ...:             print('Something is wrong with the internet!')
   ...:

In [2]: http_errors(418)
I'm a teapot

In [3]: match = 3

In [4]: match
Out[4]: 3

In [5]: and = 3
  Input In [5]
    and = 3
    ^
SyntaxError: invalid syntax

可见match在模式匹配语句中是被作为关键字使用的,平时也可以作为变量名使用。但是关键字则不能作为变量使用。


'_'的一般用法:

  1. 直接作为变量使用,或作为变量中单词的分隔符使用(如num_of_bugs):
    一般用在循环中表示循环次数,以免去想一个变量名称🤣:

    In [1]: for _ in range(5):
       ...:     print(_)
       ...: 
    0
    1
    2
    3
    4
    
  2. 在交互式python环境中, _ 可以储存最后一次评估运算(evaluation)的结果:

    In [12]: 2 + 3
    Out[12]: 5
    
    In [13]: _
    Out[13]: 5
    
    In [14]: eval('3 + 4')
    Out[14]: 7
    
    In [15]: _
    Out[15]: 7
    
  3. 在书写较长的数字时,可以使用_分割数字:

    In [16]: a = 1_000_000
    
    In [17]: a
    Out[17]: 1000000
    
  4. python3.10 中作为match case语句的默认情况使用:

    # 使用上面的http_errors()方法
    In [7]: http_errors(402)
    Something is wrong with the internet!
    
  5. 在web开发中进行本地化时,_() 可以作为gettext()方法的别名方便我们使用。


'_'在类和模块中的用法:

  1. _*:当使用from <module> import * 语句调用某个模块中的所有变量和方法时,以 _ 开头的变量和方法不会被导入进脚本中,不属于API。
    这类似于其他语言中的私有变量 private,同时意味着该方法或属性不应该被从外部直接调用

    首先,我们创建一个test2.py 文件,在其中添加 _* 格式的变量和方法:

    # test2.py
    _var = 3
    
    def _method():
        print(__name__)  # 返回当前文件名称
    
    def method():
        return _method()
    

    然后,我们在另外一个脚本中导入test2.py中的所有变量和方法进行使用:

    from test2 import *
    
    method()
    # test2
    _method()
    # NameError: name '_method' is not defined. Did you mean: 'method'?
    print(_var)
    # NameError: name '_var' is not defined
    

    可见,使用 _* 格式的变量和方法不会被导入到当前脚本中。
    不过,如果使用对test2.py的引用格式(import test2),则可以使用其中 _* 格式的变量,不过不建议这样使用(作者将其封装为私有变量就是不想让我们随意调用他们)

    import test2
    
    test2.method()
    # test2
    test2._method()
    # test2
    print(test2._var)
    # 3
    
  2. __*:通常会作为类中的私有变量的名称,访问该类时无法直接通过变量名访问。不过也并不是真的不能访问,使用 __* 命名的变量会在类中被重写为形如 _类名__* 的格式。
    这被称为私有名称转换:这样做是为了避免基类及派生类的 “私有” 属性之间产生名称冲突。我们可以通过转换后的变量名称来访问变量。
    但是并不推荐这样做,他们应该只在该类内被访问和使用

    class Animal(object):
        def __init__(self, name) -> None:
            self.name = name
            self.__age = 1
    
    a = Animal('a')
    print(a.name)  
    # a
    print(a.__dict__)
    # {'name': 'a', '_Animal__age': 1} 可见其中的__age已经变为了_Animal__age
    print(a.__age)
    # 报错:
    # AttributeError: 'Animal' object has no attribute '__age'
    print(a._Animal__age)
    # 1
    
    ## 创建继承Animal的子类Dog
    class Dog(Animal):
        pass
    
    b = Dog('b')
    print(b.__dict__)
    # {'name': 'b', '_Animal__age': 1} 其属性与父类相同
    
    ## 创建继承自Animal的子类Cat
    class Cat(Animal):
        def __init__(self, name) -> None:
            super().__init__(name)
            self.__age = 2
    
    c = Cat('c')
    print(c.__dict__)
    # {'name': 'c', '_Animal__age': 1, '_Cat__age': 2} 多了一个属于本类的私有属性
    

    由此可以看出python没有真正的私有化,其中的方法或变量都可以通过某些手段访问到。

  3. __*__:这种命名方式我们比较常见,就是我们在类中使用并重写的方法或变量,如__init__(self)
    他们是系统定义的特殊方法名称,通常称为‘dunder’,这些名称由解释器和标准库定义。所有特殊方法可见:https://docs.python.org/zh-cn/3/reference/datamodel.html#specialnames
    可以使用 __dir__() 方法查看一个对象的所有方法和变量,其中包括特殊方法:

    class Animal(object):
        def __init__(self, name) -> None:
            self.name = name
            self.__age = 1
    
    a = Animal('a')
    print(a.__dir__())
    # ['name', '_Animal__age', '__module__', '__init__', '__dict__', '__weakref__',
    # '__doc__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', 
    # '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', 
    # '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__subclasshook__', 
    # '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
    

参考:

https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
https://docs.python.org/zh-cn/3/reference/expressions.html#atom-identifiers
https://docs.python.org/zh-cn/3/reference/datamodel.html#specialnames

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐