Gostaria de saber se é possível controlar a definição da função Python com base nas configurações globais (por exemplo, SO). Exemplo:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Então, se alguém estiver usando Linux, a primeira definição de my_callback
será usada e a segunda será ignorada silenciosamente.
Não se trata de determinar o sistema operacional, mas de definição de função / decoradores.
my_callback = windows(<actual function definition>)
- portanto, o nomemy_callback
será substituído, independentemente do que o decorador possa fazer. A única maneira pela qual a versão Linux da função poderia terminar nessa variável é se awindows()
retornasse - mas a função não tem como saber sobre a versão Linux. Eu acho que a maneira mais típica de conseguir isso é ter as definições de função específicas do SO em arquivos separados e, condicionalmente,import
apenas um deles.functools.singledispatch
, que faz algo semelhante ao que você deseja. Lá, oregister
decorador conhece o expedidor (porque é um atributo da função de expedição e específico para esse expedidor específico), para que ele possa retornar o expedidor e evitar os problemas com sua abordagem.uuid.getnode()
,. (Dito isto, a resposta de Todd aqui é bastante boa.)Respostas:
Se o objetivo é ter o mesmo tipo de efeito em seu código que #ifdef WINDOWS / #endif tem ... aqui está uma maneira de fazer isso (estou em um mac btw).
Caso simples, sem encadeamento
Portanto, com esta implementação, você obtém a mesma sintaxe que possui na sua pergunta.
O que o código acima está fazendo, essencialmente, é atribuir zulu a zulu se a plataforma corresponder. Se a plataforma não corresponder, retornará o zulu se tiver sido definido anteriormente. Se não foi definido, ele retorna uma função de espaço reservado que gera uma exceção.
Os decoradores são conceitualmente fáceis de descobrir se você tiver em mente que
é análogo a:
Aqui está uma implementação usando um decorador parametrizado:
Decoradores parametrizados são análogos a
foo = mydecorator(param)(foo)
.Eu atualizei a resposta um pouco. Em resposta aos comentários, ampliei seu escopo original para incluir aplicativos em métodos de classe e para cobrir funções definidas em outros módulos. Nesta última atualização, pude reduzir bastante a complexidade envolvida na determinação de se uma função já foi definida.
[Uma pequena atualização aqui ... Eu simplesmente não pude deixar isso para trás - foi um exercício divertido] Eu tenho testado mais isso e descobri que geralmente funciona em chamadas - não apenas em funções comuns; você também pode decorar declarações de classe, chamadas ou não. E ele suporta funções internas de funções, para que coisas como essa sejam possíveis (embora provavelmente não seja um bom estilo - este é apenas um código de teste):
O exemplo acima demonstra o mecanismo básico dos decoradores, como acessar o escopo do chamador e como simplificar vários decoradores que têm comportamento semelhante ao ter uma função interna contendo o algoritmo comum definido.
Suporte de encadeamento
Para suportar o encadeamento desses decoradores indicando se uma função se aplica a mais de uma plataforma, o decorador pode ser implementado da seguinte maneira:
Dessa forma, você apoia o encadeamento:
fonte
macos
ewindows
estiver definido no mesmo módulo quezulu
. Acredito que isso também resultará na função ser deixada comoNone
se a função não estivesse definida para a plataforma atual, o que levaria a alguns erros de tempo de execução muito confusos.Embora a
@decorator
sintaxe pareça agradável, você obtém exatamente o mesmo comportamento desejado com um simplesif
.Se necessário, isso também permite impor facilmente que algum caso corresponda.
fonte
def callback_windows(...)
edef callback_linux(...)
, entãoif windows: callback = callback_windows
, etc. Mas, de qualquer maneira, é muito mais fácil ler, depurar e manter.elif
, pois nunca será o caso esperado que mais de um delinux
/windows
/macOS
seja verdadeiro. Na verdade, eu provavelmente apenas definiria uma única variávelp = platform.system()
e depois usariaif p == "Linux"
etc., em vez de vários sinalizadores booleanos. Variáveis que não existem não podem sair de sincronia.elif
certamente tem suas vantagens - especificamente, um à direitaelse
+raise
para garantir que pelo menos um caso fez jogo. Quanto à avaliação do predicado, prefiro tê-los pré-avaliados - evita duplicação e desacopla a definição e o uso. Mesmo que o resultado não seja armazenado em variáveis, agora existem valores codificados que podem sair da sincronização da mesma forma. Eu posso não lembrar as várias cordas mágicas para os diferentes meios, por exemplo,platform.system() == "Windows"
contrasys.platform == "win32"
, ...Enum
ou apenas um conjunto de constantes.Abaixo está uma implementação possível para esse mecânico. Conforme observado nos comentários, pode ser preferível implementar uma interface "master dispatcher", como a vista em
functools.singledispatch
, para acompanhar o estado associado às várias definições sobrecarregadas. Minha esperança é que essa implementação, pelo menos, ofereça algumas dicas sobre os problemas com os quais você pode ter que lidar ao desenvolver essa funcionalidade para uma base de código maior.Eu testei apenas que a implementação abaixo funciona conforme especificado nos sistemas Linux, portanto, não posso garantir que esta solução permita adequadamente a criação de funções especializadas na plataforma. Por favor, não use esse código em uma configuração de produção sem antes testá-lo completamente.
Para usar esse decorador, precisamos trabalhar com dois níveis de indireção. Primeiro, devemos especificar a qual plataforma queremos que o decorador responda. Isso é realizado pela linha
implement_linux = implement_for_os('Linux')
e pela contraparte da janela acima. Em seguida, precisamos repassar a definição existente da função que está sendo sobrecarregada. Esta etapa deve ser realizada no site de definição, conforme demonstrado abaixo.Para definir uma função especializada em plataforma, você pode agora escrever o seguinte:
As chamadas para
some_function()
serão enviadas adequadamente para a definição específica da plataforma fornecida.Pessoalmente, eu não recomendaria o uso dessa técnica no código de produção. Na minha opinião, é melhor ser explícito sobre o comportamento dependente da plataforma em cada local onde essas diferenças ocorrem.
fonte
implement_for_os
não retorna um decorador, mas retorna uma função que produzirá o decorador uma vez fornecida com a definição anterior da função em questão.Eu escrevi meu código antes de ler outras respostas. Depois que terminei meu código, achei que o código de @ Todd é a melhor resposta. De qualquer forma, postei minha resposta porque me senti divertido enquanto resolvia esse problema. Aprendi coisas novas graças a essa boa pergunta. A desvantagem do meu código é que existe uma sobrecarga para recuperar dicionários toda vez que funções são chamadas.
fonte
Uma solução limpa seria criar um registro de função dedicado que é despachado
sys.platform
. Isso é muito parecido comfunctools.singledispatch
. O código fonte desta função fornece um bom ponto de partida para implementar uma versão personalizada:Agora pode ser usado semelhante a
singledispatch
:O registro também funciona diretamente nos nomes das funções:
fonte