Python 类 语法总结
文章目录
两句废话
最近复习 Python,把自己看到的一些 Python 语法中需要注意的点总结在这里。
名称和对象
由于 Python 的赋值操作为引用赋值,所以多个变量名可以绑定到同一个对象上。这种特性的好处表现为,因为别名在某些方面表现得像指针,所以传递一个对象的代价很低,而且函数修改了作为参数传递的对象,调用者也看得到更改。
Python 作用于和命名空间
这个地方的知识点没怎么看懂,先写在这里,后面有机会再填坑
namespace 是一个从名字到对象的映射。和其他编程语言的作用一样,命名空间可以隔离不同命名空间的变量。
-
一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。每次递归调用都会又它的本地命名空间。
-
一个 作用域 是一个命名空间可直接访问的 Python 程序的文本区域。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。
-
如果一个名称被声明为全局变量,则所有引用和赋值将直接指向包含该模块的全局名称的中间作用域。 要重新绑定在最内层作用域以外找到的变量,可以使用
nonlocal
语句声明为非本地变量。 如果没有被nonlocal
声明为非本地变量,这些变量将是只读的(尝试写入这样的变量只会在最内层作用域中创建一个 新的 局部变量,而同名的外部变量保持不变)。 -
global
语句可被用来表明特定变量生存于 全局作用域 并且应当在其中被重新绑定;nonlocal
语句表明特定变量生存于 外层作用域 中并且应当在其中被重新绑定。例子:
def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam) # 代码输出 """ After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam """
局部 赋值(这是默认状态)不会改变 scope_test 对 spam 的绑定。 nonlocal 赋值会改变 scope_test 对 spam 的绑定,而 global 赋值会改变模块层级的绑定。
定义类
通过关键字 class
定义一个类,class 之后为类的名称并以冒号结尾
class ClassName:
"""类的帮助信息""" #类文档字符串
class_suite #类体
创建实例对象
类实例化类似函数调用方式(类的 实例化 是使用函数表示法)
# "创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
# "创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)
实例对象的属性引用
实例对象的属性引用通过 ObjectName.MethodName
实现,即通过 .
运算符实现
访问限制
Python 中对象可以通过访问符直接访问类的属性和方法。如果想内部属性不被外部访问,可以把属性的名称前加上两个下划线 __
,使其变为私有属性,只有类内部能够访问。单下划线开头的实例变量虽然外部可以被访问,但是约定这样的变量应该被是为私有变量。
使用双下划线开始会导致访问名称变成其他形式。这种修改的作用是继承——这种属性通过继承是无法被覆盖的。如:
class B:
def __init__(self):
self.__private = 0
def __private_method(self):
pass
def public_method(self):
pass
self.__private_method()
在 B 类中,私有属性会被分别重命名为 _B__private
和 _B__private_method
有时候要定义的一个 变量和某个保留关键字 冲突,这时候可以使用 单下划线 作为后缀,例如:
lambda_ = 2.0 # Trailing _ to avoid clash with lambda keyword
需要区别的是,Python 中以双下划线开头和结尾的对象,是特殊对象 __XXX__
继承
类的继承语法如下:
class 派生类名(基类名)
...
多重继承的语法如下:
class SubClassName (ParentClass1[, ParentClass2, ...]):
...
派生类同样支持 Java 和 C++ 中的方法的重写、方法重载以及运算符重载。
Python 对象销毁
Python 的垃圾回收采用 Java 垃圾回收相同的技术实现,即采用引用计数实现变量垃圾回收。
修改实例的字符串表示
要改变一个实例的字符串表示,可重新定义它的 __str__()
和 __repr__()
方法。例如:
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x!s}, {0.y!s})'.format(self)
内置的 repr() 函数返回这个字符串,跟我们使用交互式解释器显示的值是一样的。 str() 方法将实例转换为一个字符串,使用 str() 或 print() 函数会输出这个字符串。
>>> p = Pair(3, 4)
>>> p
Pair(3, 4) # __repr__() output
>>> print(p)
(3, 4) # __str__() output
>>>
自定义 repr() 和 str() 通常是很好的习惯,因为它能简化调试和实例输出。
让对象支持上下文管理协议
为了让一个对象兼容 with 语句,类的定义中需要实现 __enter__()
和 __exit__()
方法。 例如,考虑如下的一个类,它能为我们创建一个网络连接:
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
self.sock = None
from functools import partial
conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
# conn.__enter__() executes: connection open
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
# conn.__exit__() executes: connection closed
当出现 with 语句的时候,对象的 __enter__()
方法被触发, 它返回的值(如果有的话)会被赋值给 as 声明的变量。然后,with 语句块里面的代码开始执行。 当 with 代码块中的代码执行结束后,__exit__()
方法被触发进行清理工作。不管 with 中的代码块发生什么,该过程一定会被执行完,即使发生了异常。
使代码在 with 语句中执行,可以完成加锁和解锁的一些繁琐而必要的工作。
创建大量对象时节省内存方法
当程序要创建大量(可能上百万)的对象,会导致占用很大的内存。对于主要是用来当成 简单的数据结构 的类而言,你可以通过给类添加 __slots__
属性来极大的减少实例所占的内存。
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
当你定义 __slots__
后,Python就会为实例使用一种更加紧凑的内部表示。 但是会产生以下几点影响:
- 该不能再给实例添加新的属性了,只能使用在
__slots__
中定义的那些属性名 - 定义了slots后的类不再支持一些普通类特性了,比如多继承。Python的很多特性都依赖于普通的基于字典的实现
所以,尽管slots看上去是一个很有用的特性,很多时候还是得减少对它的使用冲动。
创建可管理的属性
如果想给某个实例的属性增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。可以将它定义为一个 property,即用 @property
装饰器修饰。给一个属性增加简单的类型检查的例子:
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function (optional)
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
"""
>>> a = Person('Guido')
>>> a.first_name # Calls the getter
'Guido'
>>> a.first_name = 42 # Calls the setter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "prop.py", line 14, in first_name
raise TypeError('Expected a string')
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can`t delete attribute
>>>
"""
Properties还是一种定义动态计算attribute的方法。 这种类型的attributes并不会被实际的存储,而是在需要的时候计算出来。
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
@property
def perimeter(self):
return 2 * math.pi * self.radius
"""
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area # Notice lack of ()
50.26548245743669
>>> c.perimeter # Notice lack of ()
25.132741228718345
>>>
"""
不要写有大量重复代码的property定义, 重复代码会导致臃肿、易出错和丑陋的程序。通过使用装饰器或闭包,有很多种更好的方法来完成同样的事情。