6.3 KiB
Looking back at what Python 3.4 did for enum
Plus explore some of the underutilized but still useful Python features.
This is the fifth in a series of articles about features that first appeared in a version of Python 3.x. Python 3.4 was first released in 2014, and even though it has been out for a long time, many of the features it introduced are underused and pretty cool. Here are three of them.
enum
One of my favorite logic puzzles is the self-descriptive Hardest Logic Puzzle Ever. Among other things, it talks about three gods who are called A, B, and C. Their identities are True, False, and Random, in some order. You can ask them questions, but they only answer in the god language, where "da" and "ja" mean "yes" and "no," but you do not know which is which.
If you decide to use Python to solve the puzzle, how would you represent the gods' names and identities and the words in the god language? The traditional answer has been to use strings. However, strings can be misspelled with disastrous consequences.
If, in a critical part of your solution, you compare to the string jaa
instead of ja
, you will have an incorrect solution. While the puzzle does not specify what the stakes are, that's probably best avoided.
The enum
module gives you the ability to define these things in a debuggable yet safe manner:
import enum
@enum.unique
class Name(enum.Enum):
A = enum.auto()
B = enum.auto()
C = enum.auto()
@enum.unique
class Identity(enum.Enum):
RANDOM = enum.auto()
TRUE = enum.auto()
FALSE = enum.auto()
@enum.unique
class Language(enum.Enum):
ja = enum.auto()
da = enum.auto()
One advantage of enums is that in debugging logs or exceptions, the enum is rendered helpfully:
name = Name.A
identity = Identity.RANDOM
answer = Language.da
print("I suspect", name, "is", identity, "because they answered", answer)
[/code] [code]` I suspect Name.A is Identity.RANDOM because they answered Language.da`
functools.singledispatch
While developing the "infrastructure" layer of a game, you want to deal with various game objects generically but still allow the objects to customize actions. To make the example easier to explain, assume it's a text-based game. When you use an object, most of the time, it will just print You are using <x>
. But using a special sword might require a random roll, and it will fail otherwise.
When you acquire an object, it is usually added to the inventory. However, a particularly heavy rock will smash a random object; if that happens, the inventory will lose that object.
One way to approach this is to have methods use
and acquire
on objects. More and more of these methods will be added as the game's complexity increases, making game objects unwieldy to write.
Instead, functools.singledispatch
allows you to add methods retroactively—in a safe and namespace-respecting manner.
You can define classes with no behavior:
class Torch:
name="torch"
class Sword:
name="sword"
class Rock:
name="rock"
[/code] [code]
import functools
@functools.singledispatch
def use(x):
print("You use", x.name)
@functools.singledispatch
def acquire(x, inventory):
inventory.add(x)
For the torch, those generic implementations are enough:
inventory = set()
def deploy(thing):
acquire(thing, inventory)
use(thing)
print("You have", [item.name for item in inventory])
deploy(Torch())
[/code] [code]
You use torch
You have ['torch']
However, the sword and the rock need some specialized functionality:
import random
@use.register(Sword)
def use_sword(sword):
print("You try to use", sword.name)
if random.random() < 0.9:
print("You succeed")
else:
print("You fail")
deploy(sword)
[/code] [code]
You try to use sword
You succeed
You have ['sword', 'torch']
[/code] [code]
import random
@acquire.register(Rock)
def acquire_rock(rock, inventory):
to_remove = random.choice(list(inventory))
inventory.remove(to_remove)
inventory.add(rock)
deploy(Rock())
[/code] [code]
You use rock
You have ['sword', 'rock']
The rock might have crushed the torch, but your code is much easier to read.
pathlib
The interface to file paths in Python has been "smart-string manipulation" since the beginning of time. Now, with pathlib
, Python has an object-oriented way to manipulate paths:
`import pathlib`[/code] [code]
gitconfig = pathlib.Path.home() / ".gitconfig"
text = gitconfig.read_text().splitlines()
Admittedly, using /
as an operator to generate path names is a little cutesy, but it ends up being nice in practice. Methods like .read_text()
allow you to get text out of small files without needing to open and close file handles manually.
This lets you concentrate on the important stuff:
for line in text:
if not line.strip().startswith("name"):
continue
print(line.split("=")[1])
[/code] [code]` Moshe Zadka`
Welcome to 2014
Python 3.4 was released about seven years ago, but some of the features that first showed up in this release are cool—and underused. Add them to your toolkit if you haven't already.
via: https://opensource.com/article/21/5/python-34-features
作者:Moshe Zadka 选题:lujun9972 译者:译者ID 校对:校对者ID