In Python 2.5, is there a way to create a decorator that decorates a class? Specifically, I want to use a decorator to add a member to a class and change the constructor to take a value for that member.
Looking for something like the following (which has a syntax error on ‘class Foo:’:
I would second the notion that you may wish to consider a subclass instead of the approach you’ve outlined. However, not knowing your specific scenario, YMMV :-)
What you’re thinking of is a metaclass. The __new__ function in a metaclass is passed the full proposed definition of the class, which it can then rewrite before the class is created. You can, at that time, sub out the constructor for a new one.
Replacing the constructor is perhaps a bit dramatic, but the language does provide support for this kind of deep introspection and dynamic modification.
def addID(original_class):
orig_init = original_class.__init__
# Make copy of original __init__, so we can call it without recursiondef __init__(self, id,*args,**kws):
self.__id = id
self.getId = getId
orig_init(self,*args,**kws)# Call the original __init__
original_class.__init__ = __init__ # Set the class' __init__ to the new onereturn original_class
Apart from the question whether class decorators are the right solution to your problem:
In Python 2.6 and higher, there are class decorators with the @-syntax, so you can write:
@addID
class Foo:
pass
In older versions, you can do it another way:
class Foo:
pass
Foo = addID(Foo)
Note however that this works the same as for function decorators, and that the decorator should return the new (or modified original) class, which is not what you’re doing in the example. The addID decorator would look like this:
def addID(original_class):
orig_init = original_class.__init__
# Make copy of original __init__, so we can call it without recursion
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
orig_init(self, *args, **kws) # Call the original __init__
original_class.__init__ = __init__ # Set the class' __init__ to the new one
return original_class
You could then use the appropriate syntax for your Python version as described above.
But I agree with others that inheritance is better suited if you want to override __init__.
def dec(klass):
old_foo = klass.foo
@wraps(klass.foo)def decorated_foo(self,*args ,**kwargs):print('@decorator pre %s'% msg)
old_foo(self,*args,**kwargs)print('@decorator post %s'% msg)
klass.foo = decorated_foo
return klass
@dec# No parenthesesclassFoo...
通常,您想向装饰器添加参数:
from functools import wraps
def dec(msg='default'):def decorator(klass):
old_foo = klass.foo
@wraps(klass.foo)def decorated_foo(self,*args ,**kwargs):print('@decorator pre %s'% msg)
old_foo(self,*args,**kwargs)print('@decorator post %s'% msg)
klass.foo = decorated_foo
return klass
return decorator
@dec('foo decorator')# You must add parentheses now, even if they're emptyclassFoo(object):def foo(self,*args,**kwargs):print('foo.foo()')@dec('subfoo decorator')classSubFoo(Foo):def foo(self,*args,**kwargs):print('subfoo.foo() pre')
super(SubFoo, self).foo(*args,**kwargs)print('subfoo.foo() post')@dec('subsubfoo decorator')classSubSubFoo(SubFoo):def foo(self,*args,**kwargs):print('subsubfoo.foo() pre')
super(SubSubFoo, self).foo(*args,**kwargs)print('subsubfoo.foo() post')SubSubFoo().foo()
输出:
@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator
我使用了一个函数装饰器,因为我发现它们更加简洁。这是一个装饰类的类:
classDec(object):def __init__(self, msg):
self.msg = msg
def __call__(self, klass):
old_foo = klass.foo
msg = self.msg
def decorated_foo(self,*args,**kwargs):print('@decorator pre %s'% msg)
old_foo(self,*args,**kwargs)print('@decorator post %s'% msg)
klass.foo = decorated_foo
return klass
一个更强大的版本,用于检查这些括号,并在装饰的类上不存在该方法时起作用:
from inspect import isclass
def decorate_if(condition, decorator):return decorator if condition elselambda x: x
def dec(msg):# Only use if your decorator's first parameter is never a classassertnot isclass(msg)def decorator(klass):
old_foo = getattr(klass,'foo',None)@decorate_if(old_foo, wraps(klass.foo))def decorated_foo(self,*args ,**kwargs):print('@decorator pre %s'% msg)if callable(old_foo):
old_foo(self,*args,**kwargs)print('@decorator post %s'% msg)
klass.foo = decorated_foo
return klass
return decorator
I’d agree inheritance is a better fit for the problem posed.
I found this question really handy though on decorating classes, thanks all.
Here’s another couple of examples, based on other answers, including how inheritance affects things in Python 2.7, (and @wraps, which maintains the original function’s docstring, etc.):
def dec(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
@dec # No parentheses
class Foo...
Often you want to add parameters to your decorator:
from functools import wraps
def dec(msg='default'):
def decorator(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
@dec('foo decorator') # You must add parentheses now, even if they're empty
class Foo(object):
def foo(self, *args, **kwargs):
print('foo.foo()')
@dec('subfoo decorator')
class SubFoo(Foo):
def foo(self, *args, **kwargs):
print('subfoo.foo() pre')
super(SubFoo, self).foo(*args, **kwargs)
print('subfoo.foo() post')
@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
def foo(self, *args, **kwargs):
print('subsubfoo.foo() pre')
super(SubSubFoo, self).foo(*args, **kwargs)
print('subsubfoo.foo() post')
SubSubFoo().foo()
Outputs:
@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator
I’ve used a function decorator, as I find them more concise. Here’s a class to decorate a class:
class Dec(object):
def __init__(self, msg):
self.msg = msg
def __call__(self, klass):
old_foo = klass.foo
msg = self.msg
def decorated_foo(self, *args, **kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
A more robust version that checks for those parentheses, and works if the methods don’t exist on the decorated class:
from inspect import isclass
def decorate_if(condition, decorator):
return decorator if condition else lambda x: x
def dec(msg):
# Only use if your decorator's first parameter is never a class
assert not isclass(msg)
def decorator(klass):
old_foo = getattr(klass, 'foo', None)
@decorate_if(old_foo, wraps(klass.foo))
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
if callable(old_foo):
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
The assert checks that the decorator has not been used without parentheses. If it has, then the class being decorated is passed to the msg parameter of the decorator, which raises an AssertionError.
@decorate_if only applies the decorator if condition evaluates to True.
The getattr, callable test, and @decorate_if are used so that the decorator doesn’t break if the foo() method doesn’t exist on the class being decorated.
I actually think this is a pretty interesting implementation. Because it subclasses the class it decorates, it will behave exactly like this class in things like isinstance checks.
It has an added benefit: it’s not uncommon for the __init__ statement in a custom django Form to make modifications or additions to self.fields so it’s better for changes to self.fields to happen after all of __init__ has run for the class in question.
Very clever.
However, in your class you actually want the decoration to alter the constructor, which I don’t think is a good use case for a class decorator.
Here is an example which answers the question of returning the parameters of a class. Moreover, it still respects the chain of inheritance, i.e. only the parameters of the class itself are returned. The function get_params is added as a simple example, but other functionalities can be added thanks to the inspect module.
import inspect
class Parent:
@classmethod
def get_params(my_class):
return list(inspect.signature(my_class).parameters.keys())
class OtherParent:
def __init__(self, a, b, c):
pass
class Child(Parent, OtherParent):
def __init__(self, x, y, z):
pass
print(Child.get_params())
>>['x', 'y', 'z']
Django has method_decorator which is a decorator that turns any decorator into a method decorator, you can see how it’s implemented in django.utils.decorators:
$ python2.7 pkg/main.py
Traceback(most recent call last):File"pkg/main.py", line 2,in<module>print string.ascii_uppercase
AttributeError:'module' object has no attribute 'ascii_uppercase'
$ python2.7 pkg/main2.py
Traceback(most recent call last):File"pkg/main2.py", line 3,in<module>print string.ascii_uppercase
AttributeError:'module' object has no attribute 'ascii_uppercase'
以及Python 3.5(有无)(假设print两个文件中的语句均已更改):
$ python3.5 pkg/main.py
Traceback(most recent call last):File"pkg/main.py", line 2,in<module>print(string.ascii_uppercase)AttributeError: module 'string' has no attribute 'ascii_uppercase'
$ python3.5 pkg/main2.py
Traceback(most recent call last):File"pkg/main2.py", line 3,in<module>print(string.ascii_uppercase)AttributeError: module 'string' has no attribute 'ascii_uppercase'
I have answered a question regarding absolute imports in Python, which I thought I understood based on reading the Python 2.5 changelog and accompanying PEP. However, upon installing Python 2.5 and attempting to craft an example of properly using from __future__ import absolute_import, I realize things are not so clear.
Straight from the changelog linked above, this statement accurately summarized my understanding of the absolute import change:
Let’s say you have a package directory like this:
pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py
This defines a package named pkg containing the pkg.main and pkg.string submodules.
Consider the code in the main.py module. What happens if it executes the statement import string? In Python 2.4 and earlier, it will first look in the package’s directory to perform a relative import, finds pkg/string.py, imports the contents of that file as the pkg.string module, and that module is bound to the name "string" in the pkg.main module’s namespace.
So I created this exact directory structure:
$ ls -R
.:
pkg/
./pkg:
__init__.py main.py string.py
__init__.py and string.py are empty. main.py contains the following code:
import string
print string.ascii_uppercase
As expected, running this with Python 2.5 fails with an AttributeError:
$ python2.5 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'
However, further along in the 2.5 changelog, we find this (emphasis added):
In Python 2.5, you can switch import‘s behaviour to absolute imports using a from __future__ import absolute_import directive. This absolute-import behaviour will become the default in a future version (probably Python 2.7). Once absolute imports are the default, import string will always find the standard library’s version.
I thus created pkg/main2.py, identical to main.py but with the additional future import directive. It now looks like this:
from __future__ import absolute_import
import string
print string.ascii_uppercase
Running this with Python 2.5, however… fails with an AttributeError:
$ python2.5 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'
This pretty flatly contradicts the statement that import string will always find the std-lib version with absolute imports enabled. What’s more, despite the warning that absolute imports are scheduled to become the “new default” behavior, I hit this same problem using both Python 2.7, with or without the __future__ directive:
$ python2.7 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2.7 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'
as well as Python 3.5, with or without (assuming the print statement is changed in both files):
$ python3.5 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'
$ python3.5 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'
I have tested other variations of this. Instead of string.py, I have created an empty module — a directory named string containing only an empty __init__.py — and instead of issuing imports from main.py, I have cd‘d to pkg and run imports directly from the REPL. Neither of these variations (nor a combination of them) changed the results above. I cannot reconcile this with what I have read about the __future__ directive and absolute imports.
It seems to me that this is easily explicable by the following (this is from the Python 2 docs but this statement remains unchanged in the same docs for Python 3):
sys.path
(…)
As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter. If the script directory is not available (e.g. if the interpreter is invoked interactively or if the script is read from standard input), path[0] is the empty string, which directs Python to search modules in the current directory first.
So what am I missing? Why does the __future__ statement seemingly not do what it says, and what is the resolution of this contradiction between these two sections of documentation, as well as between described and actual behavior?
The changelog is sloppily worded. from __future__ import absolute_import does not care about whether something is part of the standard library, and import string will not always give you the standard-library module with absolute imports on.
from __future__ import absolute_import means that if you import string, Python will always look for a top-level string module, rather than current_package.string. However, it does not affect the logic Python uses to decide what file is the string module. When you do
python pkg/script.py
pkg/script.py doesn’t look like part of a package to Python. Following the normal procedures, the pkg directory is added to the path, and all .py files in the pkg directory look like top-level modules. import string finds pkg/string.py not because it’s doing a relative import, but because pkg/string.py appears to be the top-level module string. The fact that this isn’t the standard-library string module doesn’t come up.
To run the file as part of the pkg package, you could do
python -m pkg.script
In this case, the pkg directory will not be added to the path. However, the current directory will be added to the path.
You can also add some boilerplate to pkg/script.py to make Python treat it as part of the pkg package even when run as a file:
if __name__ == '__main__' and __package__ is None:
__package__ = 'pkg'
However, this won’t affect sys.path. You’ll need some additional handling to remove the pkg directory from the path, and if pkg‘s parent directory isn’t on the path, you’ll need to stick that on the path too.
The difference between absolute and relative imports come into play only when you import a module from a package and that module imports an other submodule from that package. See the difference:
$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pkg/main1.py", line 1, in <module>
import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>>
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>>
In particular:
$ python2 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 1, in <module>
from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>>
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Note that python2 pkg/main2.py has a different behaviour then launching python2 and then importing pkg.main2 (which is equivalent to using the -m switch).
If you ever want to run a submodule of a package always use the -m switch which prevents the interpreter for chaining the sys.path list and correctly handles the semantics of the submodule.
Also, I much prefer using explicit relative imports for package submodules since they provide more semantics and better error messages in case of failure.
def all_perms(elements):if len(elements)<=1:yield elements
else:for perm in all_perms(elements[1:]):for i in range(len(elements)):# nb elements[0:1] works in both string and list contextsyield perm[:i]+ elements[0:1]+ perm[i:]
的文档中列出了几种其他方法itertools.permutations。这是一个:
def permutations(iterable, r=None):# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r isNoneelse r
if r > n:return
indices = range(n)
cycles = range(n, n-r,-1)yield tuple(pool[i]for i in indices[:r])while n:for i in reversed(range(r)):
cycles[i]-=1if cycles[i]==0:
indices[i:]= indices[i+1:]+ indices[i:i+1]
cycles[i]= n - i
else:
j = cycles[i]
indices[i], indices[-j]= indices[-j], indices[i]yield tuple(pool[i]for i in indices[:r])breakelse:return
另一个基于itertools.product:
def permutations(iterable, r=None):
pool = tuple(iterable)
n = len(pool)
r = n if r isNoneelse r
for indices in product(range(n), repeat=r):if len(set(indices))== r:yield tuple(pool[i]for i in indices)
If you’re using an older Python (<2.6) for some reason or are just curious to know how it works, here’s one nice approach, taken from http://code.activestate.com/recipes/252178/:
def all_perms(elements):
if len(elements) <=1:
yield elements
else:
for perm in all_perms(elements[1:]):
for i in range(len(elements)):
# nb elements[0:1] works in both string and list contexts
yield perm[:i] + elements[0:1] + perm[i:]
A couple of alternative approaches are listed in the documentation of itertools.permutations. Here’s one:
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
And another, based on itertools.product:
def permutations(iterable, r=None):
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
for indices in product(range(n), repeat=r):
if len(set(indices)) == r:
yield tuple(pool[i] for i in indices)
#!/usr/bin/env python
def perm(a, k=0):
if k == len(a):
print a
else:
for i in xrange(k, len(a)):
a[k], a[i] = a[i] ,a[k]
perm(a, k+1)
a[k], a[i] = a[i], a[k]
perm([1,2,3])
As I’m swapping the content of the list it’s required a mutable sequence type as input. E.g. perm(list("ball")) will work and perm("ball") won’t because you can’t change a string.
This Python implementation is inspired by the algorithm presented in the book Computer Algorithms by Horowitz, Sahni and Rajasekeran.
回答 5
此解决方案实现了一个生成器,以避免将所有排列保留在内存中:
def permutations (orig_list):ifnot isinstance(orig_list, list):
orig_list = list(orig_list)yield orig_list
if len(orig_list)==1:returnfor n in sorted(orig_list):
new_list = orig_list[:]
pos = new_list.index(n)del(new_list[pos])
new_list.insert(0, n)for resto in permutations(new_list[1:]):if new_list[:1]+ resto <> orig_list:yield new_list[:1]+ resto
This solution implements a generator, to avoid holding all the permutations on memory:
def permutations (orig_list):
if not isinstance(orig_list, list):
orig_list = list(orig_list)
yield orig_list
if len(orig_list) == 1:
return
for n in sorted(orig_list):
new_list = orig_list[:]
pos = new_list.index(n)
del(new_list[pos])
new_list.insert(0, n)
for resto in permutations(new_list[1:]):
if new_list[:1] + resto <> orig_list:
yield new_list[:1] + resto
回答 6
以实用的风格
def addperm(x,l):return[ l[0:i]+[x]+ l[i:]for i in range(len(l)+1)]def perm(l):if len(l)==0:return[[]]return[x for y in perm(l[1:])for x in addperm(l[0],y)]print perm([ i for i in range(3)])
def addperm(x,l):
return [ l[0:i] + [x] + l[i:] for i in range(len(l)+1) ]
def perm(l):
if len(l) == 0:
return [[]]
return [x for y in perm(l[1:]) for x in addperm(l[0],y) ]
print perm([ i for i in range(3)])
def permute_in_place(a):
a.sort()yield list(a)if len(a)<=1:return
first =0
last = len(a)while1:
i = last -1while1:
i = i -1if a[i]< a[i+1]:
j = last -1whilenot(a[i]< a[j]):
j = j -1
a[i], a[j]= a[j], a[i]# swap the values
r = a[i+1:last]
r.reverse()
a[i+1:last]= r
yield list(a)breakif i == first:
a.reverse()returnif __name__ =='__main__':for n in range(5):for a in permute_in_place(range(1, n+1)):print a
printfor a in permute_in_place([0,0,1,1,1]):print a
print
The following code is an in-place permutation of a given list, implemented as a generator. Since it only returns references to the list, the list should not be modified outside the generator.
The solution is non-recursive, so uses low memory. Work well also with multiple copies of elements in the input list.
def permute_in_place(a):
a.sort()
yield list(a)
if len(a) <= 1:
return
first = 0
last = len(a)
while 1:
i = last - 1
while 1:
i = i - 1
if a[i] < a[i+1]:
j = last - 1
while not (a[i] < a[j]):
j = j - 1
a[i], a[j] = a[j], a[i] # swap the values
r = a[i+1:last]
r.reverse()
a[i+1:last] = r
yield list(a)
break
if i == first:
a.reverse()
return
if __name__ == '__main__':
for n in range(5):
for a in permute_in_place(range(1, n+1)):
print a
print
for a in permute_in_place([0, 0, 1, 1, 1]):
print a
print
回答 8
我认为一种很明显的方式可能是:
def permutList(l):ifnot l:return[[]]
res =[]for e in l:
temp = l[:]
temp.remove(e)
res.extend([[e]+ r for r in permutList(temp)])return res
def permutList(l):
if not l:
return [[]]
res = []
for e in l:
temp = l[:]
temp.remove(e)
res.extend([[e] + r for r in permutList(temp)])
return res
回答 9
list2Perm =[1,2.0,'three']
listPerm =[[a, b, c]for a in list2Perm
for b in list2Perm
for c in list2Perm
if( a != b and b != c and a != c )]print listPerm
list2Perm = [1, 2.0, 'three']
listPerm = [[a, b, c]
for a in list2Perm
for b in list2Perm
for c in list2Perm
if ( a != b and b != c and a != c )
]
print listPerm
from math import factorial
def permutations(l):
permutations=[]
length=len(l)for x in xrange(factorial(length)):
available=list(l)
newPermutation=[]for radix in xrange(length,0,-1):
placeValue=factorial(radix-1)
index=x/placeValue
newPermutation.append(available.pop(index))
x-=index*placeValue
permutations.append(newPermutation)return permutations
permutations(range(3))
I used an algorithm based on the factorial number system– For a list of length n, you can assemble each permutation item by item, selecting from the items left at each stage. You have n choices for the first item, n-1 for the second, and only one for the last, so you can use the digits of a number in the factorial number system as the indices. This way the numbers 0 through n!-1 correspond to all possible permutations in lexicographic order.
from math import factorial
def permutations(l):
permutations=[]
length=len(l)
for x in xrange(factorial(length)):
available=list(l)
newPermutation=[]
for radix in xrange(length, 0, -1):
placeValue=factorial(radix-1)
index=x/placeValue
newPermutation.append(available.pop(index))
x-=index*placeValue
permutations.append(newPermutation)
return permutations
permutations(range(3))
This method is non-recursive, but it is slightly slower on my computer and xrange raises an error when n! is too large to be converted to a C long integer (n=13 for me). It was enough when I needed it, but it’s no itertools.permutations by a long shot.
回答 11
请注意,此算法具有n factorial时间复杂度,其中n是输入列表的长度
打印运行结果:
global result
result =[]def permutation(li):if li ==[]or li ==None:returnif len(li)==1:
result.append(li[0])print result
result.pop()returnfor i in range(0,len(li)):
result.append(li[i])
permutation(li[:i]+ li[i+1:])
result.pop()
Note that this algorithm has an n factorial time complexity, where n is the length of the input list
Print the results on the run:
global result
result = []
def permutation(li):
if li == [] or li == None:
return
if len(li) == 1:
result.append(li[0])
print result
result.pop()
return
for i in range(0,len(li)):
result.append(li[i])
permutation(li[:i] + li[i+1:])
result.pop()
def all_perms(elements):if len(elements)<=1:yield elements # Only permutation possible = no permutationelse:# Iteration over the first element in the result permutation:for(index, first_elmt)in enumerate(elements):
other_elmts = elements[:index]+elements[index+1:]for permutation in all_perms(other_elmts):yield[first_elmt]+ permutation
One can indeed iterate over the first element of each permutation, as in tzwenn’s answer. It is however more efficient to write this solution this way:
def all_perms(elements):
if len(elements) <= 1:
yield elements # Only permutation possible = no permutation
else:
# Iteration over the first element in the result permutation:
for (index, first_elmt) in enumerate(elements):
other_elmts = elements[:index]+elements[index+1:]
for permutation in all_perms(other_elmts):
yield [first_elmt] + permutation
This solution is about 30 % faster, apparently thanks to the recursion ending at len(elements) <= 1 instead of 0.
It is also much more memory-efficient, as it uses a generator function (through yield), like in Riccardo Reyes’s solution.
回答 13
这是受Haskell实现的启发,该实现使用列表理解:
def permutation(list):if len(list)==0:return[[]]else:return[[x]+ ys for x in list for ys in permutation(delete(list, x))]def delete(list, item):
lc = list[:]
lc.remove(item)return lc
This is inspired by the Haskell implementation using list comprehension:
def permutation(list):
if len(list) == 0:
return [[]]
else:
return [[x] + ys for x in list for ys in permutation(delete(list, x))]
def delete(list, item):
lc = list[:]
lc.remove(item)
return lc
回答 14
常规执行(无收益-将在内存中做所有事情):
def getPermutations(array):if len(array)==1:return[array]
permutations =[]for i in range(len(array)):# get all perm's of subarray w/o current item
perms = getPermutations(array[:i]+ array[i+1:])for p in perms:
permutations.append([array[i],*p])return permutations
收益实施:
def getPermutations(array):if len(array)==1:yield array
else:for i in range(len(array)):
perms = getPermutations(array[:i]+ array[i+1:])for p in perms:yield[array[i],*p]
Regular implementation (no yield – will do everything in memory):
def getPermutations(array):
if len(array) == 1:
return [array]
permutations = []
for i in range(len(array)):
# get all perm's of subarray w/o current item
perms = getPermutations(array[:i] + array[i+1:])
for p in perms:
permutations.append([array[i], *p])
return permutations
Yield implementation:
def getPermutations(array):
if len(array) == 1:
yield array
else:
for i in range(len(array)):
perms = getPermutations(array[:i] + array[i+1:])
for p in perms:
yield [array[i], *p]
The basic idea is to go over all the elements in the array for the 1st position, and then in 2nd position go over all the rest of the elements without the chosen element for the 1st, etc. You can do this with recursion, where the stop criteria is getting to an array of 1 element – in which case you return that array.
from numpy import empty, uint8
from math import factorial
def perms(n):
f =1
p = empty((2*n-1, factorial(n)), uint8)for i in range(n):
p[i,:f]= i
p[i+1:2*i+1,:f]= p[:i,:f]# constitution de blocsfor j in range(i):
p[:i+1, f*(j+1):f*(j+2)]= p[j+1:j+i+2,:f]# copie de blocs
f = f*(i+1)return p[:n,:]
For performance, a numpy solution inspired by Knuth, (p22) :
from numpy import empty, uint8
from math import factorial
def perms(n):
f = 1
p = empty((2*n-1, factorial(n)), uint8)
for i in range(n):
p[i, :f] = i
p[i+1:2*i+1, :f] = p[:i, :f] # constitution de blocs
for j in range(i):
p[:i+1, f*(j+1):f*(j+2)] = p[j+1:j+i+2, :f] # copie de blocs
f = f*(i+1)
return p[:n, :]
Copying large blocs of memory saves time –
it’s 20x faster than list(itertools.permutations(range(n)) :
In [1]: %timeit -n10 list(permutations(range(10)))
10 loops, best of 3: 815 ms per loop
In [2]: %timeit -n100 perms(10)
100 loops, best of 3: 40 ms per loop
回答 16
from __future__ import print_function
def perm(n):
p =[]for i in range(0,n+1):
p.append(i)whileTrue:for i in range(1,n+1):print(p[i], end=' ')print("")
i = n -1
found =0while(not found and i>0):if p[i]<p[i+1]:
found =1else:
i = i -1
k = n
while p[i]>p[k]:
k = k -1
aux = p[i]
p[i]= p[k]
p[k]= aux
for j in range(1,(n-i)/2+1):
aux = p[i+j]
p[i+j]= p[n-j+1]
p[n-j+1]= aux
ifnot found:break
perm(5)
from __future__ import print_function
def perm(n):
p = []
for i in range(0,n+1):
p.append(i)
while True:
for i in range(1,n+1):
print(p[i], end=' ')
print("")
i = n - 1
found = 0
while (not found and i>0):
if p[i]<p[i+1]:
found = 1
else:
i = i - 1
k = n
while p[i]>p[k]:
k = k - 1
aux = p[i]
p[i] = p[k]
p[k] = aux
for j in range(1,(n-i)/2+1):
aux = p[i+j]
p[i+j] = p[n-j+1]
p[n-j+1] = aux
if not found:
break
perm(5)
def permute(xs, low=0):if low +1>= len(xs):yield xs
else:for p in permute(xs, low +1):yield p
for i in range(low +1, len(xs)):
xs[low], xs[i]= xs[i], xs[low]for p in permute(xs, low +1):yield p
xs[low], xs[i]= xs[i], xs[low]for p in permute([1,2,3,4]):print p
def permute(xs, low=0):
if low + 1 >= len(xs):
yield xs
else:
for p in permute(xs, low + 1):
yield p
for i in range(low + 1, len(xs)):
xs[low], xs[i] = xs[i], xs[low]
for p in permute(xs, low + 1):
yield p
xs[low], xs[i] = xs[i], xs[low]
for p in permute([1, 2, 3, 4]):
print p
def permute(items):
length = len(items)def inner(ix=[]):
do_yield = len(ix)== length -1for i in range(0, length):if i in ix:#avoid duplicatescontinueif do_yield:yield tuple([items[y]for y in ix +[i]])else:for p in inner(ix +[i]):yield p
return inner()
用法:
for p in permute((1,2,3)):print(p)(1,2,3)(1,3,2)(2,1,3)(2,3,1)(3,1,2)(3,2,1)
This algorithm is the most effective one, it avoids of array passing and manipulation in recursive calls, works in Python 2, 3:
def permute(items):
length = len(items)
def inner(ix=[]):
do_yield = len(ix) == length - 1
for i in range(0, length):
if i in ix: #avoid duplicates
continue
if do_yield:
yield tuple([items[y] for y in ix + [i]])
else:
for p in inner(ix + [i]):
yield p
return inner()
Usage:
for p in permute((1,2,3)):
print(p)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
回答 20
def pzip(c, seq):
result =[]for item in seq:for i in range(len(item)+1):
result.append(item[i:]+c+item[:i])return result
def perm(line):
seq =[c for c in line]if len(seq)<=1:return seq
else:return pzip(seq[0], perm(seq[1:]))
def pzip(c, seq):
result = []
for item in seq:
for i in range(len(item)+1):
result.append(item[i:]+c+item[:i])
return result
def perm(line):
seq = [c for c in line]
if len(seq) <=1 :
return seq
else:
return pzip(seq[0], perm(seq[1:]))
回答 21
另一种方法(无库)
def permutation(input):if len(input)==1:return input if isinstance(input, list)else[input]
result =[]for i in range(len(input)):
first = input[i]
rest = input[:i]+ input[i +1:]
rest_permutation = permutation(rest)for p in rest_permutation:
result.append(first + p)return result
def permutation(input):
if len(input) == 1:
return input if isinstance(input, list) else [input]
result = []
for i in range(len(input)):
first = input[i]
rest = input[:i] + input[i + 1:]
rest_permutation = permutation(rest)
for p in rest_permutation:
result.append(first + p)
return result
The trotter package is different from most implementations in that it generates pseudo lists that don’t actually contain permutations but rather describe mappings between permutations and respective positions in an ordering, making it possible to work with very large ‘lists’ of permutations, as shown in this demo which performs pretty instantaneous operations and look-ups in a pseudo-list ‘containing’ all the permutations of the letters in the alphabet, without using more memory or processing than a typical web page.
In any case, to generate a list of permutations, we can do the following.
import trotter
my_permutations = trotter.Permutations(3, [1, 2, 3])
print(my_permutations)
for p in my_permutations:
print(p)
def calcperm(arr, size):
result = set([()])
for dummy_idx in range(size):
temp = set()
for dummy_lst in result:
for dummy_outcome in arr:
if dummy_outcome not in dummy_lst:
new_seq = list(dummy_lst)
new_seq.append(dummy_outcome)
temp.add(tuple(new_seq))
result = temp
return result
@numba.njit()def permutations(A, k):
r =[[i for i in range(0)]]for i in range(k):
r =[[a]+ b for a in A for b in r if(a in b)==False]return r
permutations([1,2,3],3)[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
给人印象的表现:
%timeit permutations(np.arange(5),5)243µs ±11.1µs per loop (mean ± std. dev. of 7 runs,1 loop each)
time:406 ms
%timeit list(itertools.permutations(np.arange(5),5))15.9µs ±8.61 ns per loop (mean ± std. dev. of 7 runs,100000 loops each)
time:12.9 s
To save you folks possible hours of searching and experimenting, here’s the non-recursive permutaions solution in Python which also works with Numba (as of v. 0.41):
@numba.njit()
def permutations(A, k):
r = [[i for i in range(0)]]
for i in range(k):
r = [[a] + b for a in A for b in r if (a in b)==False]
return r
permutations([1,2,3],3)
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
To give an impression about performance:
%timeit permutations(np.arange(5),5)
243 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
time: 406 ms
%timeit list(itertools.permutations(np.arange(5),5))
15.9 µs ± 8.61 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
time: 12.9 s
So use this version only if you have to call it from njitted function, otherwise prefer itertools implementation.
I see a lot of iteration going on inside these recursive functions, not exactly pure recursion…
so for those of you who cannot abide by even a single loop, here’s a gross, totally unnecessary fully recursive solution
def all_insert(x, e, i=0):
return [x[0:i]+[e]+x[i:]] + all_insert(x,e,i+1) if i<len(x)+1 else []
def for_each(X, e):
return all_insert(X[0], e) + for_each(X[1:],e) if X else []
def permute(x):
return [x] if len(x) < 2 else for_each( permute(x[1:]) , x[0])
perms = permute([1,2,3])
回答 26
另一个解决方案:
def permutation(flag, k =1):
N = len(flag)for i in xrange(0, N):if flag[i]!=0:continue
flag[i]= k
if k == N:print flag
permutation(flag, k+1)
flag[i]=0
permutation([0,0,0])
def permutation(flag, k =1 ):
N = len(flag)
for i in xrange(0, N):
if flag[i] != 0:
continue
flag[i] = k
if k == N:
print flag
permutation(flag, k+1)
flag[i] = 0
permutation([0, 0, 0])
回答 27
我的Python解决方案:
def permutes(input,offset):if( len(input)== offset ):return[''.join(input)]
result=[]for i in range( offset, len(input)):
input[offset], input[i]= input[i], input[offset]
result = result + permutes(input,offset+1)
input[offset], input[i]= input[i], input[offset]return result
# input is a "string"# return value is a list of stringsdef permutations(input):return permutes( list(input),0)# Main Programprint( permutations("wxyz"))
def permutes(input,offset):
if( len(input) == offset ):
return [''.join(input)]
result=[]
for i in range( offset, len(input) ):
input[offset], input[i] = input[i], input[offset]
result = result + permutes(input,offset+1)
input[offset], input[i] = input[i], input[offset]
return result
# input is a "string"
# return value is a list of strings
def permutations(input):
return permutes( list(input), 0 )
# Main Program
print( permutations("wxyz") )
回答 28
def permutation(word, first_char=None):if word ==Noneor len(word)==0:return[]if len(word)==1:return[word]
result =[]
first_char = word[0]for sub_word in permutation(word[1:], first_char):
result += insert(first_char, sub_word)return sorted(result)def insert(ch, sub_word):
arr =[ch + sub_word]for i in range(len(sub_word)):
arr.append(sub_word[i:]+ ch + sub_word[:i])return arr
assert permutation(None)==[]assert permutation('')==[]assert permutation('1')==['1']assert permutation('12')==['12','21']print permutation('abc')
from collections importCounterdef permutations(nums):
ans =[[]]
cache =Counter(nums)for idx, x in enumerate(nums):
result =[]for items in ans:
cache1 =Counter(items)for id, n in enumerate(nums):if cache[n]!= cache1[n]and items +[n]notin result:
result.append(items +[n])
ans = result
return ans
permutations([1,2,2])>[[1,2,2],[2,1,2],[2,2,1]]
from collections import Counter
def permutations(nums):
ans = [[]]
cache = Counter(nums)
for idx, x in enumerate(nums):
result = []
for items in ans:
cache1 = Counter(items)
for id, n in enumerate(nums):
if cache[n] != cache1[n] and items + [n] not in result:
result.append(items + [n])
ans = result
return ans
permutations([1, 2, 2])
> [[1, 2, 2], [2, 1, 2], [2, 2, 1]]