语言对比之python和go的占位符和空白符
简介
本文试图简单了解不同语言的符号系统,同样的符号可能有不同含义,本文稍长。
1 连接符 _ 在py3的用处
1.1 在解释器使用
python自动将解释器中最后一个表达式的值存储到名为 _ 的 特定变量中。
如果需要,还可将这些值分配给其他变量
>>> 6
6
>>> _
6
1.2 忽略值
如果您不想在解包时使用特定值,只需将该值分配给 _
忽略意味着将值分配给 特殊变量 underscore(_) 我们将值分配给 _
因为在未来的代码中没有使用它
>>> a, _, b = (1,2,3)
>>> _
2
>>> a, *_, b = (1,2,3,4,5,6)
>>> _
[2,3,4,5]
1.3 在循环中使用
在循环中 _ 作为变量
for _ in range(5):
print(_)
1.4 分隔数字的位数
100万的表示
n = 1_000_000
>>> n = 1_000_000
>>> n
1000000
>>> _ = 1_000_000
>>> _
1000000
二进制
ob_0010
八进制
0o_64
十六进制
>>> 0x_23_ab
9131
1.5 命名
单前下划线
_variable
class T:
def __init__(self):
self._num = 7
单前下划线 不影响 您访问类的变量,但影响从模块导入的名称,如 :
# funcimport
def func1():
return "test"
def _func2():
return 1000
from funcimport import *
func1() # 可以运行
test
_func2() # 运行失败
NameError name is not defined
可以从模块导入使用
import funcimport
funcimport._func2()
1000
单后下划线
variable_
一般可用于python的关键字命名冲突避免
None_
print_
都是自定义变量,双前下划线
__variable
双前下划线变量 将不能被子类继承 和更改,也不能都实例 访问。
如果您仔细查看属性列表,您会发现一个名为_Sample__c的属性。这就是名字 mangling。这是为了避免在子类中覆盖变量。
>>> class A:
... def __init__(self):
... self.a = 1
... self._b = 2
... self.__c = 3
>>> aa = A()
>>> aa.a
1
>>> aa._b
2
>>> aa.__c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__c'
如继承了A的 B 也有 __c属性,但是无法访问
>>> dir(B)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__'
, '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__
reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> dir(bb)
['_A__c', '_B__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne_
_', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__
', '__weakref__', '_b', 'a', 'a1']
访问的方式必须是特定的 子类名称
>>> bb._B__c
33
>>> bb._A__c
3
内部函数同样的如属性的访问
>>> class C(B):
... def __init__(self):
... self.a = 111
... self._b = 222
... self.__c = 333
... def __innerfunc(self):
... return 322
>>> cc = C()
>>> cc._C__c
333
>>> cc._C__innerfunc
<bound method C.__innerfunc of <__main__.C object at 0x000001B3359D7EE0>>
>>> cc._C__innerfunc()
322
>>> cc.__innerfunc()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute '__innerfunc'
双前后下划线
variable
您会发现以双下划线开头和结尾的不同名称。它们被称为魔术方法或dunder 方法。
1.6 省略号提示符
省略号 …
具有以下含义:
交互式终端中输入特殊代码行时默认的 Python 提示符。
包括:缩进的代码块,成对的分隔符之内(圆括号、方括号、花括号或三重引号),或是指定一个装饰器之后。
Ellipsis 内置常量。与省略号字面值 “…” 相同。 该特殊值主要是与用户定义的容器数据类型的扩展切片语法结合使用。
Ellipsis 是 types.EllipsisType 类型的唯一实例。
types.EllipsisType
Ellipsis 的类型。
3.10 新版功能.
大于号: > 和 >> 一般为运算符,
而 >>>
交互式终端中默认的 Python 提示符。往往会显示于能以交互方式在解释器里执行的样例代码之前
如下例子:
def minsInt():
...
pass
return 10 < 1
def minsInts():
...
pass
return 10 << 1
def maxsInt():
...
return 10 > 2
def maxsInts():
...
pass
return 10 >> 2
if __name__ == '__main__':
print(minsInt())
print(minsInts())
print(maxsInt())
print(maxsInts())
1.7 运算符 * 和在函数参数中的使用。
python 中 * 在运算符通常被用到 乘法运算。
10 * 2
20
10 ** 2
100
在 函数中,与 *args 结合使用,表示多个可选参数的使用。
与 **kwargs 表示多个映射参数使用。
例如 pdb的调用
pdb.runcall(function, *args, **kwds)
使用给定的参数调用 function (以函数或方法对象的形式提供,不能是字符串)。
runcall() 返回的是所调用函数的返回值。调试器提示符将在进入函数后立即出现(function, *args, **kwargs) 会被转换成
例如并行任务库 concurrent.futures 中的submit函数:
submit(fn, /, *args, **kwargs)
调度可调用对象 fn,以 fn(*args, **kwargs) 方式执行并返回一个代表该可调用对象的执行的 Future 对象。
例子:
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(pow, 323, 1235)
print(future.result())
- 的流氓用法,
在函数中 如果 * 单独存在,
-
如果在函数中首位,则表示该函数 位置之后的 参数都必须是关键字参数。
def keyParams(*, name:str, words:str): print(f"people who name:{name} said words:{words}.") if __name__ == "__main__": keyParams(name="jack", words="happly new year.") keyParams(words="happly new year.",name="jack")
如此调用将报错
keyParams("jack",words="happly new year.")
如果在函数的中间位置,则其后的位置都是关键字参数。
与此对应的是位置参数,/ ,在函数的参数中,/ 之前的都必须是位置参数,也就是在调用该函数时,只能使用位置传入参数。
综合用法如下:
def keyParams(date:str, /, space:str, *, name:str, words:str):
print(f"{date} at {space} people who name:{name} said words:{words}.")
keyParams("today","school", name="jack", words="happly new year.")
keyParams("today","school", words="happly new year.", name="jack")
错误示范:
如此调用将报错 TypeError: keyParams() got some positional-only arguments passed as keyword arguments: ‘date’
SyntaxError: positional argument follows keyword argument
# TypeError: keyParams() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
keyParams(date="today","school","jack", words="happly new year.")
因为 date 参数 today 必须使用位置传参,而这里使用了关键字传参。
而 name jack 必须使用 关键字传参,这里却使用了 位置传参。
综上,在py函数中,正常的参数是可选的,如果用户不填则默认为None值。
data 必须是 位置传参
sapce 是默认传参方式,可以是位置或关键字传参。
name 和 words 必须是关键字传参,位置不是固定的。
如果在函数的参数位置 使用了 / 或 * 则限制的了相关位置的传参方式
正常使用的参数可选,默认为None。
/ 之前的必须是位置传参。
* 之后的必须是关键字传参。
2 go语言中空白占位符 _ 的使用场景
- _ 空白标识符
在for 循环中,或定义时,_ 常用于忽略某些不希望被关注的值。
这样,你就不必声明一个你不会使用的变量: Go 不允许它。相反,使用 _ 忽略所述变量。
for range在loops 和maps 的上下文中,我们已经多次提到空白标识符 。
空白标识符可以分配或声明任何类型的任何值,该值将被无害地丢弃。
这有点像写入 Unix/dev/null文件:它表示一个只写值,用作需要变量但实际值无关紧要的占位符。
它的用途超出了我们已经看到的用途。
导入时使用 _ : 用例 用于未使用的导入。
如果正常导入时,导入后不进行使用,将在编译检查时被自动删除,例如下:
import bars "tools/bars"
使用空白标识符则不会被删除,而只是使用其附加功能
import _ "tools/bars"
在调用检查路径下的文件时,错误比文件信息更重要.
例如,当调用一个返回值和错误的函数时,但只有错误是重要的,使用空白标识符来丢弃不相关的值。
if_,err:= os.Stat(filePaht);os.IsNotExist(err) {
fmt.Printf("%s not exist path.\n", filePath)
}
- 导入包的附加功能
导入fmt或之 类的,但是未使用,最终应该被使用或删除:
空白分配将代码标识为正在进行的工作。
比如 io但有时导入一个包只是为了它的副加作用是有用的,没有其他任何明确的用途。
例如,在其init功能期间,该net/http/pprof 包会注册提供调试信息的 HTTP 处理程序。
它有一个导出的 API,但大多数客户端只需要处理程序注册并通过网页访问数据。
要仅为其副作用导入包,请将包重命名为空白标识符:
import _ "net/http/pprof"
这种形式的导入清楚地表明正在导入包是为了它的附加功能,因为没有其他可能使用该包:
在这个文件没有名称可以指向和使用它。(如果是这样,而我们没有使用该名称,编译器将踢忽略该程序。)
- 接口检查 interface
一个类型不需要显式声明它实现了一个接口。相反,类型仅通过实现接口的方法来实现接口。
实际上,大多数接口转换都是静态的,因此会在编译时进行检查。
例如, 传递* os.File给 函数io.Reader将不会编译,除非 * os.File实现了该io.Reader接口。
某些接口检查确实会在运行时发生。一个实例在encoding/json 包中,它定义了一个Marshaler 接口。
当 JSON 编码器接收到实现该接口的值时,编码器调用该值的编组方法将其转换为 JSON,而不是执行标准转换。
编码器在运行时使用类型断言检查此属性。
如果只需要询问一个类型是否实现了一个接口,而不实际使用接口本身,也许作为错误检查的一部分,使用空白标识符来忽略类型声明的值:
if _,ok := val.(json.Marshaler); ok {
fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}
出现这种情况的一个地方是当需要在包内保证实现它实际满足接口的类型时。
如果一个类型——例如—— json.RawMessage需要一个自定义的 JSON 表示,它应该实现 json.Marshaler,但是没有静态转换会导致编译器自动验证它。
如果类型无意中无法满足接口,JSON 编码器仍然可以工作,但不会使用自定义实现。为了保证实现的正确性,可以在包中使用一个使用空白标识符的全局声明:
var _ json.Marshaler = (* RawMessage)(nil)
在此声明中,涉及将 a 转换为 a 的赋值 * RawMessage要求Marshaler 实现* RawMessage,Marshaler并且将在编译时检查该属性。
如果json.Marshaler接口发生变化,这个包将不再编译,我们会注意到它需要更新。
此构造中出现空白标识符表明该声明仅用于类型检查,而不是创建变量。
不过,不要对满足接口的每种类型都这样做。按照惯例,此类声明仅在代码中不存在静态转换时才使用,这种情况很少见。
2.0 在go语言运算符 * 的含义
在go中 * 可以做为运算符。
fmt.Println(10 * 2)
或者与c类似地做为指针运算,取得某个内存地址的值。
然后操作它,构造函数返回地址,然后通过指针取得地址的值。
var inta int = 11
fmt.Printf(" %T, %#v \n", &inta, &inta)
fmt.Println(&inta)
fmt.Println(*&inta)
再赋值给其他变量
ptrInta := &inta
fmt.Println(*ptrInta)
fmt.Println(ptrInta)
运行结果:
*int, (*int)(0xc0000180c8)
0xc0000180c8
11
11
0xc0000180c8
更麻烦一些的用法 比如我们定一个int的结构体,操作结构体
type intinta struct{ int }
func IntIntPtr(n int) *intinta {
return &intinta{n}
}
在项目中一般使用一层指针就足够了,它可以有更高级多层指针奇特的用法,但是不推荐,这样可能导致代码审查员的苦恼和指责。自己玩玩就行了
var intinta = &ptrInta
fmt.Println(*intinta)
fmt.Println(&intinta)
fmt.Println(&*&*ptrInta)
fmt.Println(*IntIntPtr())
newPtr := IntIntPtr().int
fmt.Println(*&newPtr)
输出
0xc0000180c8
0xc00000a038
0xc0000180c8
{12}
12
2.1 函数签名 … 在go语言中的调用
如下的官方的Contents例子, defer语句安排一个函数调用( 延迟函数)在函数执行defer返回之前立即运行。
这是一种不寻常但有效的处理情况的方法,例如无论函数采用哪条路径返回都必须释放的资源。典型的例子是解锁互斥量 或 关闭文件对象。
Contents 以字符串形式返回文件的内容。
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
// f.Close 将在我们完成后执行 Close。
defer f.Close()
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
//为切片添加另一个切片
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
// 如果我们返回这里,f 将被关闭。
return "", err
}
}
// 如果我们返回这里,f 将被关闭。
return string(result), nil
}
- 数组的初始化
在go中数组是值。将一个数组分配给另一个数组会复制所有元素。
特别是,如果将数组传递给函数,它将收到数组的副本,而不是指向它的指针。
数组的大小是其类型的一部分。类型[10]int 和[20]int是截然不同的。 在fmt 的类型输出中,分别为 [10]int, [20]int
value 属性可能 很昂贵;如果你想要类似 C 的行为和效率,你可以传递一个指向数组的指针。
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
// 注意显式地址运算符
x : = Sum(&array)
a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
在两个切片相互添加时,
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)
定一个空数组
fmt.Println([...]int{})
- 函数签名
比如Printf 函数的打印对象中, 使用…interface{} 其最终参数的类型来指定任意数量的参数(任意类型)可以出现在格式之后。
func Printf(format string, v ...interface{}) (n int, err error) {}
Println 以 fmt.Println 的方式打印到标准记录器。
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...)) // 输出带参数 (int, string)
}
…after告诉编译器将其视为参数列表;否则它只会 作为单个切片参数传递。
调试打印的内容比我们在这里介绍的还要多。有关详细信息,请参阅godoc包的文档fmt。
前缀签名 … 参数可以是特定类型的,如下例子, …int 整数列表中最小值的最小函数。
func Min(a ...int) int {
min := int(^uint(0) >> 1)
fmt.Println("min for start:", min)
for _, i := range a {
if i < min {
min = i
}
}
return min
}
在传入时,使用另外后缀签名的方式
mint := Min([]int{1, 2, 3, 4, 5, 6}...)
fmt.Println("min int:", mint)
3 小结
各个语言中, * 符号的共同用处是运算符。
不同的是,py中还被用于指数运算以及参数分割。
在go语言却主要用于指针。
在py中 _ 常做为分隔符的部分使用,比如参数,函数名称,类的熟悉,大的整型数值。
而 _ 在go中被用做空白标识符。 用以 多重赋值中的空白标识符 和 未使用具体结构体的导入量(通常只使用其中导入的init函数)。
而 … 的含义也不尽相同。
- 点赞
- 收藏
- 关注作者
评论(0)