Python 3 入门 - 3. 还可以这样?

对于新手而言,官方的 Python 教程 是一个非常不错的学习资源,内容安排由浅入深。但对于已经有其他语言编程经验的人来说,学习Python更快的方法可能是先 “找不同”。Python某些特性确实非常 “特别”,甚至会让人惊呼 “还可以这样?” 这里简单罗列一些这样的特性。


既能面向对象,又能面向过程

Python本质上是一门面向对象的语言,即使 int 等内置的基本数据类型都是 class,但使用Python既可以进行面向对象的编程,也可以进行面向过程的编程,甚至能够像Shell脚本一样使用,整个python脚本里可以既没有类定义也没有函数定义。例如:

# helloworld.py
# shell-script style

s1 = "Hello %s."
s2 = "world"

print(s1 % s2)
# helloworld.py
# 面向过程编程

def foo(s):
    return "Hello %s." % s

if __name__ == "__main__":
    s1 = foo("world")
    print(s1)
# helloworld.py
# 面向对象编程

class Hello:
    def say_hello_to(self, s):
        print("Hello %s." % s)


if __name__ == "__main__":
    hello = Hello()
    hello.say_hello_to("world")

没有 main() 函数

你可能已经注意到,上面三种形式的 helloworld.py 都没有main()函数。但它们都可以通过 python helloworld.py 的方式执行,并输出相同的内容。后面两种形式的代码中,都有 if __name__ == "__main__": 的代码块,当 .py 文件以脚本形式被执行时,内置变量 __name__ 会被赋值为 "__main__",从而此段代码会被执行。但当 .py 文件被以模块形式导入时,__name__ 会被赋值为模块名称,相应的,这段代码就不会执行。

我们加上一行查看 __name__ 的语句:

# helloworld.py

class Hello:
    def say_hello_to(self, s):
        print("Hello %s." % s)

# 查看内部变量 __name__ 的值
print("__name__ is '" + __name__ + "'\n")

if __name__ == "__main__":
    hello = Hello()
    hello.say_hello_to("world")

以脚本方式执行:

C:\Users\user\PyProjects>python helloworld.py
__name__ is '__main__'

Hello world.

以模块方式导入:

C:\Users\user\PyProjects>python
Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import helloworld
__name__ is 'helloworld'

>>>

函数定义与类定义语句也会被执行

上面的最后一个例子中,输出 __name__ 的语句既没有放在 __main__ 代码段中,也没有放在类的方法中,而是放到了类定义之后、'__main__' 代码段之前的 “顶级” 代码的位置。那么,把这行代码放在最开始或者最后面,它还会执行吗?

事实上,当 .py 文件被加载时,文件中所有的语句都会被顺序执行,包括函数定义 def 语句和类定义 class 语句。理解了这一点,即使你看到某个文件里出现了两段 '__main__' 代码段,也不会觉得奇怪了。


使用缩进而不是花括号 {} 区分代码段的层级

前面的例子已经显而易见。关于缩进,在《Python语言参考》的词法分析一章中有专门的说明。不过一般而言,关于缩进只要记住下面三点:

  • 不要使用 tab
  • 理论上只要缩进量保持一致即可,无论是1个空格还是8个空格
  • IDE会自动将 tab 转换为4个空格

关于编码风格的重要提示:

https://docs.python.org/zh-cn/3/tutorial/controlflow.html#intermezzo-coding-style

Python 项目大多都遵循 PEP 8 的风格指南;它推行的编码风格易于阅读、赏心悦目。Python 开发者均应抽时间悉心研读;以下是该提案中的核心要点:

  • 缩进,用 4 个空格,不要用制表符。
    4 个空格是小缩进(更深嵌套)和大缩进(更易阅读)之间的折中方案。制表符会引起混乱,最好别用。
  • 换行,一行不超过 79 个字符。
    这样换行的小屏阅读体验更好,还便于在大屏显示器上并排阅读多个代码文件。
  • 用空行分隔函数和类,及函数内较大的代码块。
  • 最好把注释放到单独一行。
  • 使用文档字符串。
  • 运算符前后、逗号后要用空格,但不要直接在括号内使用: a = f(1, 2) + g(3, 4)。
  • 类和函数的命名要一致;按惯例,命名类用 UpperCamelCase,命名函数与方法用 lowercase_with_underscores。命名方法中第一个参数总是用 self (类和方法详见 初探类)。
  • 编写用于国际多语环境的代码时,不要用生僻的编码。Python 默认的 UTF-8 或纯 ASCII 可以胜任各种情况。
  • 同理,就算多语阅读、维护代码的可能再小,也不要在标识符中使用非 ASCII 字符。

语句末尾有没有分号都行(误)

前面的例子中,我们在语句末尾都没有分号,这也是正确的写法。但如果我们在语句末尾加上分号,程序也不会出错。比如像下面这样:

# helloworld.py

class Hello:
    def say_hello_to(self, s):
        print("Hello %s." % s);


if __name__ == "__main__":
    hello = Hello();
    hello.say_hello_to("world");

事实上,使用分号可以将多条简单语句组合成一条复合语句,例如:

if __name__ == "__main__":
    # 增加一行调试语句
    import ipdb; ipdb.set_trace()

    hello = Hello()
    hello.say_hello_to("world")

但是,作为一个合格的python程序员,请尽量 不要使用分号


字符串可以用单引号,也可以用双引号,还可以用三重引号

Python的字符串对应的数据类型为 str,既可以使用单引号',也可以使用双引号 "。区别在于:用单引号时,字符串里的双引号无需转义;而使用双引号时,字符串里的单引号无需转义。

>>> s1 = 'Using \''
>>> s2 = "Using '"
>>> s3 = "Using \'"
>>> s1 == s2 == s3
True

这也意味着:'A' 是一个长度为1的字符串,而不是一个字符。

另外,Python中还可以使用三重引号 '''""" 来表示字符串,它的优势在于可以自动包括换行符,而无需转义。

>>> s1 = 'one line\nanother line'
>>> s2 = """one line
... another line"""
>>> s1 == s2
True
>>> print(s1)
one line
another line
>>> print(s2)
one line
another line
>>>

向下取整除法运算符 // 和幂运算符 **

Python提供一种特殊的除法运算符 // ,含义为向下取整除法。

>>> 15/4
3.75
>>> 15//4
3
>>> -11/4
-2.75
>>> -11//4
-3
>>>

Python的幂运算符也与其他很多语言不同,不是 ^ 而是 **

>>> 2**3
8
>>> 5**2
25
>>> 2**-1
0.5

Python中的 ^ 是异或运算符,例如:

>>> bin(0b1001 ^ 0b1111)
'0b110'
>>> hex(0x1 ^ 0xF)
'0xe'
>>> set1 = {1, 2, 3}
>>> set2 = {3, 4, 1}
>>> set1 ^ set2
{2, 4}

pass语句

pass 语句不执行任何操作。语法上需要一个语句,但程序不实际执行任何动作时,可以使用该语句。

class MyClass:
    # TODO: ....
    pass

def foo(bar):
    # TODO: ....
    pass

for item in my_list:
    # TODO: ....
    pass

没有switch-case语句

直到3.10,Python才出现了与switch-case语句类似的match语句。在3.10之前,可以用 if-elif-else 语句实现类似的逻辑,但有时候也可以使用一种更python的方式——字典——像下面这样:

animal_dict = {
    1: "Dog",
    2: "Cat",
    3: "Rabbit",
}

def getAnimal(animal_id):
    return animal_dict.get(animal_id, "Invalid Animal")

或者这样:

def Animal1():
    return "Dog"
def Animal2():
    return "Cat"
def Animal3():
    return "Rabbit"
def DefaultAnimal():
    return "Invalid Animal"

animal_dict = {
    'A': Animal1,
    'B': Animal2,
    'C': Animal3
}

def getAnimal(animal_id):
    foo = animal_dict.get(animal_id, DefaultAnimal)
    return foo()

for循环不需要计数器

Python里的许多数据类型都可以用for循环遍历,例如 list(列表)、tuple (元组)、 dict (字典)、 range (范围)、 str (字符串)、 bytes (字节串)、 set (集合)等。

    # 列表
    my_list = ['A', 'B', 'C', 'D']
    for item in my_list:
        print(item)

    # 元组
    my_tuple = ('1', '2', 3, 4)
    for item in my_tuple:
        print(item)

    # 集合
    my_set = {'dog', 'cat', 'rabbit'}
    for item in my_set:
        print(item)

    class MyClass:
        pass
    # 字典
    my_dict = {
        'A': 10, 
        2: MyClass(), 
        'bar': (1, 2)
    }
    for k,v in my_dict.items():
        print(repr(k), repr(v))

    # 范围
    my_range = range(10,15)
    for item in my_range:
        print(item)

    # 字符串
    my_string = "This is a string."
    for item in my_string:
        print(item)

    # 字节串
    my_bytes = b"This is a string."
    for item in my_bytes:
        print(chr(item))

for、while、try语句支持else子句

for 循环的 else 子句(如果有)在循环项耗尽时执行并终止循环(break 语句终止循环且不执行 else 子句体,continue 语句将在执行时将跳过子句体中的剩余部分并转往下一项继续执行,或者在没有下一项时转往 else 子句执行):

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

while 循环的 else 子句(如果有)在while条件为 False 时执行并终止循环( break 语句在执行时将终止循环且不执行 else 子句体。 continue 语句在执行时将跳过子句体中的剩余部分并返回检验while条件表达式):

>>> x = 0
>>> while x < 100:
...     if x % 20 == 0:
...         print(x)
...     x += 1
... else:
...     print("That's all.")
...
0
20
40
60
80
That's all.

try ... except 语句具有可选的 else 子句,该子句如果存在,它必须放在所有 except 子句 之后。 它适用于 try 子句 没有引发异常但又必须要执行 的代码。(参考:错误和异常

以下例子能够看出 elsefinally 的区别:

>>> def divide(x, y):
...      try:
...          result = x / y
...      except ZeroDivisionError:
...          print("division by zero!")
...      else:
...          print("result is", result)
...      finally:
...          print("executing finally clause")
...
>>> divide(3, 1)
result is 3.0
executing finally clause
>>> divide(3, 0)
division by zero!
executing finally clause
>>> divide(3, 'a')
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'int' and 'str'
>>>

任何非零整数都为真,零为假

这一点与C语言一样。进行条件判断时,条件也可以是字符串或列表的值,事实上,任何序列都可以;长度非零就为真,空序列则为假。举例而言,对于字符串类型的变量 x,下面两种写法是等价的:

x = str(some_var)
if x:
    # do something
x = str(some_var)
if x != None and x != '':
    # do something

需要注意,bool 类型继承自 intTrue == 1False == 0 均成立,但 False == ''False == [] 以及 True == 2 等等均不成立。

>>> True == 1
True
>>> False == 0
True
>>> False == ''
False
>>> False == []
False
>>> False == None
False

灵活的import语句

Python中的 import 语句类似其他语言的 #include ,用于导入其他模块中的名称,但import语句的写法更加多样。

可以直接import:

>>> import math
>>> math.ceil(10/3)
4

使用 import item.subitem.subsubitem 句法时,除最后一项外,每个 item 都必须是;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。

>>> import django.utils.encoding.iri_to_uri
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'django.utils.encoding.iri_to_uri'; 'django.utils.encoding' is not a package
>>> import django.utils.encoding
>>> django.utils.encoding.iri_to_uri('中文')
'%E4%B8%AD%E6%96%87'

可以导入模块中的一个或几个名称(通过这种方式导入 item 时,item 可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其他名称。import 语句首先测试包中是否定义了 item;如果未在包中定义,则假定 item 是模块,并尝试加载。如果找不到 item,则触发 ImportError 异常):

>>> from math import pi,degrees
>>> pi
3.141592653589793
>>> degrees(pi)
180.0

可以给导入的名称改名:

>>> from decimal import Decimal as D
>>> num = D(1.245)
>>> num
Decimal('1.24500000000000010658141036401502788066864013671875')

虽然也支持使用 * 导入全部名称,但是官方不提倡在生产代码中使用这种做法。

>>> from decimal import *
>>> getcontext().prec = 6
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')
>>> getcontext().prec = 28
>>> Decimal(1) / Decimal(7)
Decimal('0.1428571428571428571428571429')

运算符 is 和 is not

运算符 is 和 is not 用于检测对象的标识号:当且仅当 x 和 y 是同一对象时 x is y 为真。 一个对象的标识号可使用 id() 函数来确定。 x is not y 会产生相反的逻辑值。

>>> class A:
...     pass
...
>>> a = A()
>>> b = A()
>>> a is not b
True
>>> c = a
>>> a is c
True
>>> id(a)
1984982945168
>>> id(b)
1984983270208
>>> id(c)
1984982945168
>>> x = None
>>>> x == None
True
>>> x is None
True
>>> id(x)
140717859773656
>>> id(None)
140717859773656

对象中没有私有属性

Python中将所有形如 a.b 的名称中的 b 称为属性(Attribute),在类对象中,属性引用 obj.name 中的 name 就是类对象的属性。类对象的属性可以是一个变量,也可以是一个方法/函数。但是在Python中,所有类对象的属性都不是 “私有” 的。

https://docs.python.org/zh-cn/3/tutorial/classes.html#private-variables

那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为 名称改写。 任何形式为 __spam 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。

下面举例说明这种情况:

# demo.py
# 父类
class A:
    x = 1
    _y = 2
    __z = 3

    def foo1(self, v):
        print("foo1(%d) is called." % v)
    
    def _foo2(self, v):
        print("_foo2(%d) is called." % v)

    def __foo3(self, v):
        print("__foo3(%d) is called." % v)

# 子类
class B(A):
    pass


if __name__ == "__main__":
    # 创建子类对象
    b = B()

    b.foo1(b.x)
    b._foo2(b._y)
    b._A__foo3(b._A__z)

    try:
        b.__foo3()
    except AttributeError as e:
        print(repr(e))
C:\Users\user\PyProjects>python demo.py
foo1(1) is called.
_foo2(2) is called.
__foo3(3) is called.
AttributeError("'B' object has no attribute '__foo3'")


文档字符串

三重引号字符串通常用作文档字符串(docstring)。以上面演示Python没有 “私有” 属性的代码为例,我们加上docstring。

# demo.py
# 父类
class A:
    """
    Demo Class for "private" attributes.

        This is the Base Class.
    """
    x = 1
    _y = 2
    __z = 3

    def foo1(self, v):
        """
        The public method. print a message with v.

            Parameters:
                v (int): Any int number.

            Return:
                None
        """
        print("foo1(%d) is called." % v)
    
    def _foo2(self, v):
        """
        The method that should not call.
        """
        print("_foo2(%d) is called." % v)

    def __foo3(self, v):
        """
        The "private" method. 
        """
        print("__foo3(%d) is called." % v)

# 子类
class B(A):
    """
    Demo Class for "private" attributes.

        This is the Sub Class.
    """   
    pass


if __name__ == "__main__":
    # 创建子类对象
    b = B()

    b.foo1(b.x)
    b._foo2(b._y)
    b._A__foo3(b._A__z)

    try:
        b.__foo3()
    except AttributeError as e:
        print(repr(e))

然后我们在Python解释器中执行 help() 命令,看看是什么效果。

>>> import demo
>>> help(demo.A)
Help on class A in module demo:

class A(builtins.object)
 |  Demo Class for "private" attributes.
 |
 |      This is the Base Class.
 |
 |  Methods defined here:
 |
 |  foo1(self, v)
 |      The public method. print a message with v.
 |
 |          Parameters:
 |              v (int): Any int number.
 |
 |          Return:
 |              None
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  x = 1

可以看到,_foo2()__foo3() 的 docstring 都没有显示,不仅如此, _y__z 这两个成员变量也没有显示。

查看 __doc__ 变量:

>>> print(demo.A.__doc__)

    Demo Class for "private" attributes.

        This is the Base Class.

>>> print(demo.A.foo1.__doc__)

        The public method. print a message with v.

            Parameters:
                v (int): Any int number.

            Return:
                None

>>> print(demo.A._foo2.__doc__)

        The method that should not call.

>>> print(demo.A.__foo3.__doc__)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute '__foo3'
>>> print(demo.A._A__foo3.__doc__)

        The "private" method.

>>>

再看看 class B 的情况:

>>> help(demo.B)
Help on class B in module demo:

class B(A)
 |  Demo Class for "private" attributes.
 |
 |      This is the Sub Class.
 |
 |  Method resolution order:
 |      B
 |      A
 |      builtins.object
 |
 |  Methods inherited from A:
 |
 |  foo1(self, v)
 |      The public method. print a message with v.
 |
 |          Parameters:
 |              v (int): Any int number.
 |
 |          Return:
 |              None
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from A:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from A:
 |
 |  x = 1

使用pydoc命令:

C:\Users\user\PyProjects>python -m pydoc hello.demo
Help on module hello.demo in hello:

NAME
    hello.demo

DESCRIPTION
    # demo.py

CLASSES
    builtins.object
        A
            B

    class A(builtins.object)
     |  Demo Class for "private" attributes.
     |
     |      This is the Base Class.
     |
     |  Methods defined here:
     |
     |  foo1(self, v)
     |      The public method. print a message with v.
     |
     |          Parameters:
     |              v (int): Any int number.
     |
     |          Return:
     |              None
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |
     |  x = 1

    class B(A)
     |  Demo Class for "private" attributes.
     |
     |      This is the Sub Class.
     |
     |  Method resolution order:
     |      B
     |      A
     |      builtins.object
     |
     |  Methods inherited from A:
     |
     |  foo1(self, v)
     |      The public method. print a message with v.
     |
     |          Parameters:
     |              v (int): Any int number.
     |
     |          Return:
     |              None
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from A:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from A:
     |
     |  x = 1

FILE
    c:\users\user\pyprojects\hello\demo.py

默认支持UTF-8

与 Python 2 不同,Python 3 默认支持UTF-8编码,如果 .py 文件以 UTF-8 编码保存,则无需额外声明。如果是以其他编码方式保存,则需要在文件最开始声明编码,例如:

# -*- coding: gb18030 -*-

print("这是一个GB18030编码的文件")

相反,在 Python 2 中,如果使用 UTF-8 编码,则一定要在文件最开始声明:

# -*- coding: utf-8 -*-

# Here is your code.

如何查看和修改文件编码?
VSCode右下角,不仅可以查看文件编码,还可以对编码进行转换。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!很高兴回答你关于Python游戏脚本入门的问题。在Python中,多线程是一种并发处理的技术,它允许程序同时执行多个线程,从而提高程序的运行效率和响应能力。在游戏开发中,多线程可以用于处理游戏中的多个任务或实现并行计算。 要在Python中使用多线程,可以使用内置的`threading`模块。下面是一个简单的示例,演示如何在Python中创建和启动多个线程: ```python import threading def task(): # 这里是线程要执行的任务 print("Hello, I'm running in a thread!") # 创建线程对象 thread1 = threading.Thread(target=task) thread2 = threading.Thread(target=task) # 启动线程 thread1.start() thread2.start() ``` 在上面的示例中,我们首先定义了一个`task`函数,这是线程要执行的具体任务。然后,我们使用`threading.Thread`类创建了两个线程对象`thread1`和`thread2`,并将`task`函数作为参数传递给它们。最后,我们调用`start`方法来启动这两个线程。 多线程的执行是并发的,所以你可能会看到输出信息交替出现。在实际的游戏开发中,你可以利用多线程来处理不同的游戏逻辑、计算复杂的物理模拟或者处理网络通信等任务,从而提升游戏的性能和玩家体验。 但是需要注意的是,多线程编程需要注意线程之间的同步和资源竞争问题。在游戏开发中,你可能需要使用锁和同步原语来确保线程之间的安全操作。 希望这个简单的介绍对你有所帮助!如果你有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值